aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDaniel Borkmann <daniel@iogearbox.net>2017-12-31 10:12:24 -0500
committerDaniel Borkmann <daniel@iogearbox.net>2017-12-31 10:12:24 -0500
commit5620e1a8e2e6f96bdb72abfd68a56ce8cb94dd4d (patch)
tree1028ca0b34c579067d321d7ac10ce13606b66346 /tools
parentfb982666e380c1632a74495b68b3c33a66e76430 (diff)
parent752d7b4501c250bead233ab041738db84436b1af (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.c52
-rw-r--r--tools/bpf/bpftool/main.h2
-rw-r--r--tools/bpf/bpftool/prog.c3
-rw-r--r--tools/include/uapi/linux/bpf.h3
-rwxr-xr-xtools/testing/selftests/bpf/test_offload.py112
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
418static char *
419ifindex_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
436void 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
451void 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 {
96int build_pinned_obj_table(struct pinned_obj_table *table, 96int build_pinned_obj_table(struct pinned_obj_table *table,
97 enum bpf_obj_type type); 97 enum bpf_obj_type type);
98void delete_pinned_obj_table(struct pinned_obj_table *tab); 98void delete_pinned_obj_table(struct pinned_obj_table *tab);
99void print_dev_plain(__u32 ifindex, __u64 ns_dev, __u64 ns_inode);
100void print_dev_json(__u32 ifindex, __u64 ns_dev, __u64 ns_inode);
99 101
100struct cmd { 102struct 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
926struct bpf_map_info { 929struct 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
18import json 18import json
19import os 19import os
20import pprint 20import pprint
21import random
22import string
21import subprocess 23import subprocess
22import time 24import time
23 25
@@ -27,6 +29,7 @@ bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
27pp = pprint.PrettyPrinter() 29pp = pprint.PrettyPrinter()
28devs = [] # devices we created for clean up 30devs = [] # devices we created for clean up
29files = [] # files to be removed 31files = [] # files to be removed
32netns = [] # net namespaces to be removed
30 33
31def log_get_sec(level=0): 34def 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
131def tool(name, args, flags, JSON=True, fail=True): 134def 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
142def bpftool(args, JSON=True, fail=True): 148def 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
145def bpftool_prog_list(expected=None): 151def 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
161def ip(args, force=False, JSON=True, fail=True): 167def 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
166def tc(args, JSON=True, fail=True): 172def 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
169def ethtool(dev, opt, args, fail=True): 175def 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):
178def bpf_bytecode(bytecode): 184def bpf_bytecode(bytecode):
179 return "bytecode \"%s\"" % (bytecode) 185 return "bytecode \"%s\"" % (bytecode)
180 186
187def 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
181class DebugfsDir: 196class 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
376def pin_prog(file_name, idx=0): 400def 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
408def 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
385parser = argparse.ArgumentParser() 438parser = argparse.ArgumentParser()
386parser.add_argument("--log", help="output verbose log to given file") 439parser.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
474ns = mknetns()
475skip(ns is None, "Could not create a net namespace")
476cmd("ip netns delete %s" % (ns))
477netns = []
478
420try: 479try:
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
678finally: 768finally: