diff options
author | Daniel Borkmann <daniel@iogearbox.net> | 2017-12-31 10:12:24 -0500 |
---|---|---|
committer | Daniel Borkmann <daniel@iogearbox.net> | 2017-12-31 10:12:24 -0500 |
commit | 5620e1a8e2e6f96bdb72abfd68a56ce8cb94dd4d (patch) | |
tree | 1028ca0b34c579067d321d7ac10ce13606b66346 /tools | |
parent | fb982666e380c1632a74495b68b3c33a66e76430 (diff) | |
parent | 752d7b4501c250bead233ab041738db84436b1af (diff) |
Merge branch 'bpf-offload-report-dev'
Jakub Kicinski says:
====================
This series is a redo of reporting offload device information to
user space after the first attempt did not take into account name
spaces. As requested by Kirill offloads are now protected by an
r/w sem. This allows us to remove the workqueue and free the
offload state fully when device is removed (suggested by Alexei).
Net namespace is reported with a device/inode pair.
The accompanying bpftool support is placed in common code because
maps will have very similar info. Note that the UAPI information
can't be nicely encapsulated into a struct, because in case we
need to grow the device information the new fields will have to
be added at the end of struct bpf_prog_info, we can't grow
structures in the middle of bpf_prog_info.
v3:
- use dev_get_by_index();
- redo ns code (new patch 6).
v2:
- rework the locking in patch 1 (use RCU instead of locking
dependencies);
- grab RTNL for a short time in patch 6;
- minor update to the test in patch 8.
====================
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/bpf/bpftool/common.c | 52 | ||||
-rw-r--r-- | tools/bpf/bpftool/main.h | 2 | ||||
-rw-r--r-- | tools/bpf/bpftool/prog.c | 3 | ||||
-rw-r--r-- | tools/include/uapi/linux/bpf.h | 3 | ||||
-rwxr-xr-x | tools/testing/selftests/bpf/test_offload.py | 112 |
5 files changed, 161 insertions, 11 deletions
diff --git a/tools/bpf/bpftool/common.c b/tools/bpf/bpftool/common.c index b62c94e3997a..6601c95a9258 100644 --- a/tools/bpf/bpftool/common.c +++ b/tools/bpf/bpftool/common.c | |||
@@ -44,7 +44,9 @@ | |||
44 | #include <unistd.h> | 44 | #include <unistd.h> |
45 | #include <linux/limits.h> | 45 | #include <linux/limits.h> |
46 | #include <linux/magic.h> | 46 | #include <linux/magic.h> |
47 | #include <net/if.h> | ||
47 | #include <sys/mount.h> | 48 | #include <sys/mount.h> |
49 | #include <sys/stat.h> | ||
48 | #include <sys/types.h> | 50 | #include <sys/types.h> |
49 | #include <sys/vfs.h> | 51 | #include <sys/vfs.h> |
50 | 52 | ||
@@ -412,3 +414,53 @@ void delete_pinned_obj_table(struct pinned_obj_table *tab) | |||
412 | free(obj); | 414 | free(obj); |
413 | } | 415 | } |
414 | } | 416 | } |
417 | |||
418 | static char * | ||
419 | ifindex_to_name_ns(__u32 ifindex, __u32 ns_dev, __u32 ns_ino, char *buf) | ||
420 | { | ||
421 | struct stat st; | ||
422 | int err; | ||
423 | |||
424 | err = stat("/proc/self/ns/net", &st); | ||
425 | if (err) { | ||
426 | p_err("Can't stat /proc/self: %s", strerror(errno)); | ||
427 | return NULL; | ||
428 | } | ||
429 | |||
430 | if (st.st_dev != ns_dev || st.st_ino != ns_ino) | ||
431 | return NULL; | ||
432 | |||
433 | return if_indextoname(ifindex, buf); | ||
434 | } | ||
435 | |||
436 | void print_dev_plain(__u32 ifindex, __u64 ns_dev, __u64 ns_inode) | ||
437 | { | ||
438 | char name[IF_NAMESIZE]; | ||
439 | |||
440 | if (!ifindex) | ||
441 | return; | ||
442 | |||
443 | printf(" dev "); | ||
444 | if (ifindex_to_name_ns(ifindex, ns_dev, ns_inode, name)) | ||
445 | printf("%s", name); | ||
446 | else | ||
447 | printf("ifindex %u ns_dev %llu ns_ino %llu", | ||
448 | ifindex, ns_dev, ns_inode); | ||
449 | } | ||
450 | |||
451 | void print_dev_json(__u32 ifindex, __u64 ns_dev, __u64 ns_inode) | ||
452 | { | ||
453 | char name[IF_NAMESIZE]; | ||
454 | |||
455 | if (!ifindex) | ||
456 | return; | ||
457 | |||
458 | jsonw_name(json_wtr, "dev"); | ||
459 | jsonw_start_object(json_wtr); | ||
460 | jsonw_uint_field(json_wtr, "ifindex", ifindex); | ||
461 | jsonw_uint_field(json_wtr, "ns_dev", ns_dev); | ||
462 | jsonw_uint_field(json_wtr, "ns_inode", ns_inode); | ||
463 | if (ifindex_to_name_ns(ifindex, ns_dev, ns_inode, name)) | ||
464 | jsonw_string_field(json_wtr, "ifname", name); | ||
465 | jsonw_end_object(json_wtr); | ||
466 | } | ||
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h index 8f6d3cac0347..65b526fe6e7e 100644 --- a/tools/bpf/bpftool/main.h +++ b/tools/bpf/bpftool/main.h | |||
@@ -96,6 +96,8 @@ struct pinned_obj { | |||
96 | int build_pinned_obj_table(struct pinned_obj_table *table, | 96 | int build_pinned_obj_table(struct pinned_obj_table *table, |
97 | enum bpf_obj_type type); | 97 | enum bpf_obj_type type); |
98 | void delete_pinned_obj_table(struct pinned_obj_table *tab); | 98 | void delete_pinned_obj_table(struct pinned_obj_table *tab); |
99 | void print_dev_plain(__u32 ifindex, __u64 ns_dev, __u64 ns_inode); | ||
100 | void print_dev_json(__u32 ifindex, __u64 ns_dev, __u64 ns_inode); | ||
99 | 101 | ||
100 | struct cmd { | 102 | struct cmd { |
101 | const char *cmd; | 103 | const char *cmd; |
diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c index fd0873178503..98f871ed53d6 100644 --- a/tools/bpf/bpftool/prog.c +++ b/tools/bpf/bpftool/prog.c | |||
@@ -230,6 +230,8 @@ static void print_prog_json(struct bpf_prog_info *info, int fd) | |||
230 | info->tag[0], info->tag[1], info->tag[2], info->tag[3], | 230 | info->tag[0], info->tag[1], info->tag[2], info->tag[3], |
231 | info->tag[4], info->tag[5], info->tag[6], info->tag[7]); | 231 | info->tag[4], info->tag[5], info->tag[6], info->tag[7]); |
232 | 232 | ||
233 | print_dev_json(info->ifindex, info->netns_dev, info->netns_ino); | ||
234 | |||
233 | if (info->load_time) { | 235 | if (info->load_time) { |
234 | char buf[32]; | 236 | char buf[32]; |
235 | 237 | ||
@@ -287,6 +289,7 @@ static void print_prog_plain(struct bpf_prog_info *info, int fd) | |||
287 | 289 | ||
288 | printf("tag "); | 290 | printf("tag "); |
289 | fprint_hex(stdout, info->tag, BPF_TAG_SIZE, ""); | 291 | fprint_hex(stdout, info->tag, BPF_TAG_SIZE, ""); |
292 | print_dev_plain(info->ifindex, info->netns_dev, info->netns_ino); | ||
290 | printf("\n"); | 293 | printf("\n"); |
291 | 294 | ||
292 | if (info->load_time) { | 295 | if (info->load_time) { |
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index db1b0923a308..4e8c60acfa32 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h | |||
@@ -921,6 +921,9 @@ struct bpf_prog_info { | |||
921 | __u32 nr_map_ids; | 921 | __u32 nr_map_ids; |
922 | __aligned_u64 map_ids; | 922 | __aligned_u64 map_ids; |
923 | char name[BPF_OBJ_NAME_LEN]; | 923 | char name[BPF_OBJ_NAME_LEN]; |
924 | __u32 ifindex; | ||
925 | __u64 netns_dev; | ||
926 | __u64 netns_ino; | ||
924 | } __attribute__((aligned(8))); | 927 | } __attribute__((aligned(8))); |
925 | 928 | ||
926 | struct bpf_map_info { | 929 | struct bpf_map_info { |
diff --git a/tools/testing/selftests/bpf/test_offload.py b/tools/testing/selftests/bpf/test_offload.py index c940505c2978..e3c750f17cb8 100755 --- a/tools/testing/selftests/bpf/test_offload.py +++ b/tools/testing/selftests/bpf/test_offload.py | |||
@@ -18,6 +18,8 @@ import argparse | |||
18 | import json | 18 | import json |
19 | import os | 19 | import os |
20 | import pprint | 20 | import pprint |
21 | import random | ||
22 | import string | ||
21 | import subprocess | 23 | import subprocess |
22 | import time | 24 | import time |
23 | 25 | ||
@@ -27,6 +29,7 @@ bpf_test_dir = os.path.dirname(os.path.realpath(__file__)) | |||
27 | pp = pprint.PrettyPrinter() | 29 | pp = pprint.PrettyPrinter() |
28 | devs = [] # devices we created for clean up | 30 | devs = [] # devices we created for clean up |
29 | files = [] # files to be removed | 31 | files = [] # files to be removed |
32 | netns = [] # net namespaces to be removed | ||
30 | 33 | ||
31 | def log_get_sec(level=0): | 34 | def log_get_sec(level=0): |
32 | return "*" * (log_level + level) | 35 | return "*" * (log_level + level) |
@@ -128,22 +131,25 @@ def rm(f): | |||
128 | if f in files: | 131 | if f in files: |
129 | files.remove(f) | 132 | files.remove(f) |
130 | 133 | ||
131 | def tool(name, args, flags, JSON=True, fail=True): | 134 | def tool(name, args, flags, JSON=True, ns="", fail=True): |
132 | params = "" | 135 | params = "" |
133 | if JSON: | 136 | if JSON: |
134 | params += "%s " % (flags["json"]) | 137 | params += "%s " % (flags["json"]) |
135 | 138 | ||
136 | ret, out = cmd(name + " " + params + args, fail=fail) | 139 | if ns != "": |
140 | ns = "ip netns exec %s " % (ns) | ||
141 | |||
142 | ret, out = cmd(ns + name + " " + params + args, fail=fail) | ||
137 | if JSON and len(out.strip()) != 0: | 143 | if JSON and len(out.strip()) != 0: |
138 | return ret, json.loads(out) | 144 | return ret, json.loads(out) |
139 | else: | 145 | else: |
140 | return ret, out | 146 | return ret, out |
141 | 147 | ||
142 | def bpftool(args, JSON=True, fail=True): | 148 | def bpftool(args, JSON=True, ns="", fail=True): |
143 | return tool("bpftool", args, {"json":"-p"}, JSON=JSON, fail=fail) | 149 | return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail) |
144 | 150 | ||
145 | def bpftool_prog_list(expected=None): | 151 | def bpftool_prog_list(expected=None, ns=""): |
146 | _, progs = bpftool("prog show", JSON=True, fail=True) | 152 | _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True) |
147 | if expected is not None: | 153 | if expected is not None: |
148 | if len(progs) != expected: | 154 | if len(progs) != expected: |
149 | fail(True, "%d BPF programs loaded, expected %d" % | 155 | fail(True, "%d BPF programs loaded, expected %d" % |
@@ -158,13 +164,13 @@ def bpftool_prog_list_wait(expected=0, n_retry=20): | |||
158 | time.sleep(0.05) | 164 | time.sleep(0.05) |
159 | raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs)) | 165 | raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs)) |
160 | 166 | ||
161 | def ip(args, force=False, JSON=True, fail=True): | 167 | def ip(args, force=False, JSON=True, ns="", fail=True): |
162 | if force: | 168 | if force: |
163 | args = "-force " + args | 169 | args = "-force " + args |
164 | return tool("ip", args, {"json":"-j"}, JSON=JSON, fail=fail) | 170 | return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns, fail=fail) |
165 | 171 | ||
166 | def tc(args, JSON=True, fail=True): | 172 | def tc(args, JSON=True, ns="", fail=True): |
167 | return tool("tc", args, {"json":"-p"}, JSON=JSON, fail=fail) | 173 | return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail) |
168 | 174 | ||
169 | def ethtool(dev, opt, args, fail=True): | 175 | def ethtool(dev, opt, args, fail=True): |
170 | return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail) | 176 | return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail) |
@@ -178,6 +184,15 @@ def bpf_pinned(name): | |||
178 | def bpf_bytecode(bytecode): | 184 | def bpf_bytecode(bytecode): |
179 | return "bytecode \"%s\"" % (bytecode) | 185 | return "bytecode \"%s\"" % (bytecode) |
180 | 186 | ||
187 | def mknetns(n_retry=10): | ||
188 | for i in range(n_retry): | ||
189 | name = ''.join([random.choice(string.ascii_letters) for i in range(8)]) | ||
190 | ret, _ = ip("netns add %s" % (name), fail=False) | ||
191 | if ret == 0: | ||
192 | netns.append(name) | ||
193 | return name | ||
194 | return None | ||
195 | |||
181 | class DebugfsDir: | 196 | class DebugfsDir: |
182 | """ | 197 | """ |
183 | Class for accessing DebugFS directories as a dictionary. | 198 | Class for accessing DebugFS directories as a dictionary. |
@@ -237,6 +252,8 @@ class NetdevSim: | |||
237 | self.dev = self._netdevsim_create() | 252 | self.dev = self._netdevsim_create() |
238 | devs.append(self) | 253 | devs.append(self) |
239 | 254 | ||
255 | self.ns = "" | ||
256 | |||
240 | self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname']) | 257 | self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname']) |
241 | self.dfs_refresh() | 258 | self.dfs_refresh() |
242 | 259 | ||
@@ -257,7 +274,7 @@ class NetdevSim: | |||
257 | 274 | ||
258 | def remove(self): | 275 | def remove(self): |
259 | devs.remove(self) | 276 | devs.remove(self) |
260 | ip("link del dev %s" % (self.dev["ifname"])) | 277 | ip("link del dev %s" % (self.dev["ifname"]), ns=self.ns) |
261 | 278 | ||
262 | def dfs_refresh(self): | 279 | def dfs_refresh(self): |
263 | self.dfs = DebugfsDir(self.dfs_dir) | 280 | self.dfs = DebugfsDir(self.dfs_dir) |
@@ -285,6 +302,11 @@ class NetdevSim: | |||
285 | time.sleep(0.05) | 302 | time.sleep(0.05) |
286 | raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs)) | 303 | raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs)) |
287 | 304 | ||
305 | def set_ns(self, ns): | ||
306 | name = "1" if ns == "" else ns | ||
307 | ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns) | ||
308 | self.ns = ns | ||
309 | |||
288 | def set_mtu(self, mtu, fail=True): | 310 | def set_mtu(self, mtu, fail=True): |
289 | return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu), | 311 | return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu), |
290 | fail=fail) | 312 | fail=fail) |
@@ -372,6 +394,8 @@ def clean_up(): | |||
372 | dev.remove() | 394 | dev.remove() |
373 | for f in files: | 395 | for f in files: |
374 | cmd("rm -f %s" % (f)) | 396 | cmd("rm -f %s" % (f)) |
397 | for ns in netns: | ||
398 | cmd("ip netns delete %s" % (ns)) | ||
375 | 399 | ||
376 | def pin_prog(file_name, idx=0): | 400 | def pin_prog(file_name, idx=0): |
377 | progs = bpftool_prog_list(expected=(idx + 1)) | 401 | progs = bpftool_prog_list(expected=(idx + 1)) |
@@ -381,6 +405,35 @@ def pin_prog(file_name, idx=0): | |||
381 | 405 | ||
382 | return file_name, bpf_pinned(file_name) | 406 | return file_name, bpf_pinned(file_name) |
383 | 407 | ||
408 | def check_dev_info(other_ns, ns, pin_file=None, removed=False): | ||
409 | if removed: | ||
410 | bpftool_prog_list(expected=0) | ||
411 | ret, err = bpftool("prog show pin %s" % (pin_file), fail=False) | ||
412 | fail(ret == 0, "Showing prog with removed device did not fail") | ||
413 | fail(err["error"].find("No such device") == -1, | ||
414 | "Showing prog with removed device expected ENODEV, error is %s" % | ||
415 | (err["error"])) | ||
416 | return | ||
417 | progs = bpftool_prog_list(expected=int(not removed), ns=ns) | ||
418 | prog = progs[0] | ||
419 | |||
420 | fail("dev" not in prog.keys(), "Device parameters not reported") | ||
421 | dev = prog["dev"] | ||
422 | fail("ifindex" not in dev.keys(), "Device parameters not reported") | ||
423 | fail("ns_dev" not in dev.keys(), "Device parameters not reported") | ||
424 | fail("ns_inode" not in dev.keys(), "Device parameters not reported") | ||
425 | |||
426 | if not removed and not other_ns: | ||
427 | fail("ifname" not in dev.keys(), "Ifname not reported") | ||
428 | fail(dev["ifname"] != sim["ifname"], | ||
429 | "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"])) | ||
430 | else: | ||
431 | fail("ifname" in dev.keys(), "Ifname is reported for other ns") | ||
432 | if removed: | ||
433 | fail(dev["ifindex"] != 0, "Device perameters not zero on removed") | ||
434 | fail(dev["ns_dev"] != 0, "Device perameters not zero on removed") | ||
435 | fail(dev["ns_inode"] != 0, "Device perameters not zero on removed") | ||
436 | |||
384 | # Parse command line | 437 | # Parse command line |
385 | parser = argparse.ArgumentParser() | 438 | parser = argparse.ArgumentParser() |
386 | parser.add_argument("--log", help="output verbose log to given file") | 439 | parser.add_argument("--log", help="output verbose log to given file") |
@@ -417,6 +470,12 @@ for s in samples: | |||
417 | skip(ret != 0, "sample %s/%s not found, please compile it" % | 470 | skip(ret != 0, "sample %s/%s not found, please compile it" % |
418 | (bpf_test_dir, s)) | 471 | (bpf_test_dir, s)) |
419 | 472 | ||
473 | # Check if net namespaces seem to work | ||
474 | ns = mknetns() | ||
475 | skip(ns is None, "Could not create a net namespace") | ||
476 | cmd("ip netns delete %s" % (ns)) | ||
477 | netns = [] | ||
478 | |||
420 | try: | 479 | try: |
421 | obj = bpf_obj("sample_ret0.o") | 480 | obj = bpf_obj("sample_ret0.o") |
422 | bytecode = bpf_bytecode("1,6 0 0 4294967295,") | 481 | bytecode = bpf_bytecode("1,6 0 0 4294967295,") |
@@ -549,6 +608,8 @@ try: | |||
549 | progs = bpftool_prog_list(expected=1) | 608 | progs = bpftool_prog_list(expected=1) |
550 | fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], | 609 | fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], |
551 | "Loaded program has wrong ID") | 610 | "Loaded program has wrong ID") |
611 | fail("dev" in progs[0].keys(), | ||
612 | "Device parameters reported for non-offloaded program") | ||
552 | 613 | ||
553 | start_test("Test XDP prog replace with bad flags...") | 614 | start_test("Test XDP prog replace with bad flags...") |
554 | ret, _ = sim.set_xdp(obj, "offload", force=True, fail=False) | 615 | ret, _ = sim.set_xdp(obj, "offload", force=True, fail=False) |
@@ -673,6 +734,35 @@ try: | |||
673 | fail(time_diff < delay_sec, "Removal process took %s, expected %s" % | 734 | fail(time_diff < delay_sec, "Removal process took %s, expected %s" % |
674 | (time_diff, delay_sec)) | 735 | (time_diff, delay_sec)) |
675 | 736 | ||
737 | # Remove all pinned files and reinstantiate the netdev | ||
738 | clean_up() | ||
739 | bpftool_prog_list_wait(expected=0) | ||
740 | |||
741 | sim = NetdevSim() | ||
742 | sim.set_ethtool_tc_offloads(True) | ||
743 | sim.set_xdp(obj, "offload") | ||
744 | |||
745 | start_test("Test bpftool bound info reporting (own ns)...") | ||
746 | check_dev_info(False, "") | ||
747 | |||
748 | start_test("Test bpftool bound info reporting (other ns)...") | ||
749 | ns = mknetns() | ||
750 | sim.set_ns(ns) | ||
751 | check_dev_info(True, "") | ||
752 | |||
753 | start_test("Test bpftool bound info reporting (remote ns)...") | ||
754 | check_dev_info(False, ns) | ||
755 | |||
756 | start_test("Test bpftool bound info reporting (back to own ns)...") | ||
757 | sim.set_ns("") | ||
758 | check_dev_info(False, "") | ||
759 | |||
760 | pin_file, _ = pin_prog("/sys/fs/bpf/tmp") | ||
761 | sim.remove() | ||
762 | |||
763 | start_test("Test bpftool bound info reporting (removed dev)...") | ||
764 | check_dev_info(True, "", pin_file=pin_file, removed=True) | ||
765 | |||
676 | print("%s: OK" % (os.path.basename(__file__))) | 766 | print("%s: OK" % (os.path.basename(__file__))) |
677 | 767 | ||
678 | finally: | 768 | finally: |