blob: 833b9c1ec4507388aaa03663589d402d33d25f50 [file] [log] [blame]
Jakub Kicinski417ec262017-12-01 15:09:00 -08001#!/usr/bin/python3
2
3# Copyright (C) 2017 Netronome Systems, Inc.
4#
5# This software is licensed under the GNU General License Version 2,
6# June 1991 as shown in the file COPYING in the top-level directory of this
7# source tree.
8#
9# THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
10# WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
11# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
13# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
14# THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
15
16from datetime import datetime
17import argparse
18import json
19import os
20import pprint
Jakub Kicinski752d7b42017-12-27 18:39:11 -080021import random
22import string
Jakub Kicinski7fedbb72018-01-17 19:13:31 -080023import struct
Jakub Kicinski417ec262017-12-01 15:09:00 -080024import subprocess
25import time
26
27logfile = None
28log_level = 1
29bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
30pp = pprint.PrettyPrinter()
31devs = [] # devices we created for clean up
32files = [] # files to be removed
Jakub Kicinski752d7b42017-12-27 18:39:11 -080033netns = [] # net namespaces to be removed
Jakub Kicinski417ec262017-12-01 15:09:00 -080034
35def log_get_sec(level=0):
36 return "*" * (log_level + level)
37
38def log_level_inc(add=1):
39 global log_level
40 log_level += add
41
42def log_level_dec(sub=1):
43 global log_level
44 log_level -= sub
45
46def log_level_set(level):
47 global log_level
48 log_level = level
49
50def log(header, data, level=None):
51 """
52 Output to an optional log.
53 """
54 if logfile is None:
55 return
56 if level is not None:
57 log_level_set(level)
58
59 if not isinstance(data, str):
60 data = pp.pformat(data)
61
62 if len(header):
63 logfile.write("\n" + log_get_sec() + " ")
64 logfile.write(header)
65 if len(header) and len(data.strip()):
66 logfile.write("\n")
67 logfile.write(data)
68
69def skip(cond, msg):
70 if not cond:
71 return
72 print("SKIP: " + msg)
73 log("SKIP: " + msg, "", level=1)
74 os.sys.exit(0)
75
76def fail(cond, msg):
77 if not cond:
78 return
79 print("FAIL: " + msg)
80 log("FAIL: " + msg, "", level=1)
81 os.sys.exit(1)
82
83def start_test(msg):
84 log(msg, "", level=1)
85 log_level_inc()
86 print(msg)
87
88def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
89 """
90 Run a command in subprocess and return tuple of (retval, stdout);
91 optionally return stderr as well as third value.
92 """
93 proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
94 stderr=subprocess.PIPE)
95 if background:
96 msg = "%s START: %s" % (log_get_sec(1),
97 datetime.now().strftime("%H:%M:%S.%f"))
98 log("BKG " + proc.args, msg)
99 return proc
100
101 return cmd_result(proc, include_stderr=include_stderr, fail=fail)
102
103def cmd_result(proc, include_stderr=False, fail=False):
104 stdout, stderr = proc.communicate()
105 stdout = stdout.decode("utf-8")
106 stderr = stderr.decode("utf-8")
107 proc.stdout.close()
108 proc.stderr.close()
109
110 stderr = "\n" + stderr
111 if stderr[-1] == "\n":
112 stderr = stderr[:-1]
113
114 sec = log_get_sec(1)
115 log("CMD " + proc.args,
116 "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
117 (proc.returncode, sec, stdout, sec, stderr,
118 sec, datetime.now().strftime("%H:%M:%S.%f")))
119
120 if proc.returncode != 0 and fail:
121 if len(stderr) > 0 and stderr[-1] == "\n":
122 stderr = stderr[:-1]
123 raise Exception("Command failed: %s\n%s" % (proc.args, stderr))
124
125 if include_stderr:
126 return proc.returncode, stdout, stderr
127 else:
128 return proc.returncode, stdout
129
130def rm(f):
131 cmd("rm -f %s" % (f))
132 if f in files:
133 files.remove(f)
134
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800135def tool(name, args, flags, JSON=True, ns="", fail=True):
Jakub Kicinski417ec262017-12-01 15:09:00 -0800136 params = ""
137 if JSON:
138 params += "%s " % (flags["json"])
139
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800140 if ns != "":
141 ns = "ip netns exec %s " % (ns)
142
143 ret, out = cmd(ns + name + " " + params + args, fail=fail)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800144 if JSON and len(out.strip()) != 0:
145 return ret, json.loads(out)
146 else:
147 return ret, out
148
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800149def bpftool(args, JSON=True, ns="", fail=True):
150 return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800151
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800152def bpftool_prog_list(expected=None, ns=""):
153 _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800154 if expected is not None:
155 if len(progs) != expected:
156 fail(True, "%d BPF programs loaded, expected %d" %
157 (len(progs), expected))
158 return progs
159
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800160def bpftool_map_list(expected=None, ns=""):
161 _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
162 if expected is not None:
163 if len(maps) != expected:
164 fail(True, "%d BPF maps loaded, expected %d" %
165 (len(maps), expected))
166 return maps
167
Jakub Kicinski417ec262017-12-01 15:09:00 -0800168def bpftool_prog_list_wait(expected=0, n_retry=20):
169 for i in range(n_retry):
170 nprogs = len(bpftool_prog_list())
171 if nprogs == expected:
172 return
173 time.sleep(0.05)
174 raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
175
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800176def bpftool_map_list_wait(expected=0, n_retry=20):
177 for i in range(n_retry):
178 nmaps = len(bpftool_map_list())
179 if nmaps == expected:
180 return
181 time.sleep(0.05)
182 raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))
183
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800184def ip(args, force=False, JSON=True, ns="", fail=True):
Jakub Kicinski417ec262017-12-01 15:09:00 -0800185 if force:
186 args = "-force " + args
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800187 return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns, fail=fail)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800188
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800189def tc(args, JSON=True, ns="", fail=True):
190 return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800191
192def ethtool(dev, opt, args, fail=True):
193 return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)
194
195def bpf_obj(name, sec=".text", path=bpf_test_dir,):
196 return "obj %s sec %s" % (os.path.join(path, name), sec)
197
198def bpf_pinned(name):
199 return "pinned %s" % (name)
200
201def bpf_bytecode(bytecode):
202 return "bytecode \"%s\"" % (bytecode)
203
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800204def mknetns(n_retry=10):
205 for i in range(n_retry):
206 name = ''.join([random.choice(string.ascii_letters) for i in range(8)])
207 ret, _ = ip("netns add %s" % (name), fail=False)
208 if ret == 0:
209 netns.append(name)
210 return name
211 return None
212
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800213def int2str(fmt, val):
214 ret = []
215 for b in struct.pack(fmt, val):
216 ret.append(int(b))
217 return " ".join(map(lambda x: str(x), ret))
218
219def str2int(strtab):
220 inttab = []
221 for i in strtab:
222 inttab.append(int(i, 16))
223 ba = bytearray(inttab)
224 if len(strtab) == 4:
225 fmt = "I"
226 elif len(strtab) == 8:
227 fmt = "Q"
228 else:
229 raise Exception("String array of len %d can't be unpacked to an int" %
230 (len(strtab)))
231 return struct.unpack(fmt, ba)[0]
232
Jakub Kicinski417ec262017-12-01 15:09:00 -0800233class DebugfsDir:
234 """
235 Class for accessing DebugFS directories as a dictionary.
236 """
237
238 def __init__(self, path):
239 self.path = path
240 self._dict = self._debugfs_dir_read(path)
241
242 def __len__(self):
243 return len(self._dict.keys())
244
245 def __getitem__(self, key):
246 if type(key) is int:
247 key = list(self._dict.keys())[key]
248 return self._dict[key]
249
250 def __setitem__(self, key, value):
251 log("DebugFS set %s = %s" % (key, value), "")
252 log_level_inc()
253
254 cmd("echo '%s' > %s/%s" % (value, self.path, key))
255 log_level_dec()
256
257 _, out = cmd('cat %s/%s' % (self.path, key))
258 self._dict[key] = out.strip()
259
260 def _debugfs_dir_read(self, path):
261 dfs = {}
262
263 log("DebugFS state for %s" % (path), "")
264 log_level_inc(add=2)
265
266 _, out = cmd('ls ' + path)
267 for f in out.split():
268 p = os.path.join(path, f)
269 if os.path.isfile(p):
270 _, out = cmd('cat %s/%s' % (path, f))
271 dfs[f] = out.strip()
272 elif os.path.isdir(p):
273 dfs[f] = DebugfsDir(p)
274 else:
275 raise Exception("%s is neither file nor directory" % (p))
276
277 log_level_dec()
278 log("DebugFS state", dfs)
279 log_level_dec()
280
281 return dfs
282
283class NetdevSim:
284 """
285 Class for netdevsim netdevice and its attributes.
286 """
287
288 def __init__(self):
289 self.dev = self._netdevsim_create()
290 devs.append(self)
291
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800292 self.ns = ""
293
Jakub Kicinski417ec262017-12-01 15:09:00 -0800294 self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname'])
295 self.dfs_refresh()
296
297 def __getitem__(self, key):
298 return self.dev[key]
299
300 def _netdevsim_create(self):
301 _, old = ip("link show")
302 ip("link add sim%d type netdevsim")
303 _, new = ip("link show")
304
305 for dev in new:
306 f = filter(lambda x: x["ifname"] == dev["ifname"], old)
307 if len(list(f)) == 0:
308 return dev
309
310 raise Exception("failed to create netdevsim device")
311
312 def remove(self):
313 devs.remove(self)
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800314 ip("link del dev %s" % (self.dev["ifname"]), ns=self.ns)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800315
316 def dfs_refresh(self):
317 self.dfs = DebugfsDir(self.dfs_dir)
318 return self.dfs
319
320 def dfs_num_bound_progs(self):
321 path = os.path.join(self.dfs_dir, "bpf_bound_progs")
322 _, progs = cmd('ls %s' % (path))
323 return len(progs.split())
324
325 def dfs_get_bound_progs(self, expected):
326 progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
327 if expected is not None:
328 if len(progs) != expected:
329 fail(True, "%d BPF programs bound, expected %d" %
330 (len(progs), expected))
331 return progs
332
333 def wait_for_flush(self, bound=0, total=0, n_retry=20):
334 for i in range(n_retry):
335 nbound = self.dfs_num_bound_progs()
336 nprogs = len(bpftool_prog_list())
337 if nbound == bound and nprogs == total:
338 return
339 time.sleep(0.05)
340 raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
341
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800342 def set_ns(self, ns):
343 name = "1" if ns == "" else ns
344 ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
345 self.ns = ns
346
Jakub Kicinski417ec262017-12-01 15:09:00 -0800347 def set_mtu(self, mtu, fail=True):
348 return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
349 fail=fail)
350
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800351 def set_xdp(self, bpf, mode, force=False, JSON=True, fail=True):
Jakub Kicinski417ec262017-12-01 15:09:00 -0800352 return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800353 force=force, JSON=JSON, fail=fail)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800354
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800355 def unset_xdp(self, mode, force=False, JSON=True, fail=True):
Jakub Kicinski417ec262017-12-01 15:09:00 -0800356 return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800357 force=force, JSON=JSON, fail=fail)
Jakub Kicinski417ec262017-12-01 15:09:00 -0800358
359 def ip_link_show(self, xdp):
360 _, link = ip("link show dev %s" % (self['ifname']))
361 if len(link) > 1:
362 raise Exception("Multiple objects on ip link show")
363 if len(link) < 1:
364 return {}
365 fail(xdp != "xdp" in link,
366 "XDP program not reporting in iplink (reported %s, expected %s)" %
367 ("xdp" in link, xdp))
368 return link[0]
369
370 def tc_add_ingress(self):
371 tc("qdisc add dev %s ingress" % (self['ifname']))
372
373 def tc_del_ingress(self):
374 tc("qdisc del dev %s ingress" % (self['ifname']))
375
376 def tc_flush_filters(self, bound=0, total=0):
377 self.tc_del_ingress()
378 self.tc_add_ingress()
379 self.wait_for_flush(bound=bound, total=total)
380
381 def tc_show_ingress(self, expected=None):
382 # No JSON support, oh well...
383 flags = ["skip_sw", "skip_hw", "in_hw"]
384 named = ["protocol", "pref", "chain", "handle", "id", "tag"]
385
386 args = "-s filter show dev %s ingress" % (self['ifname'])
387 _, out = tc(args, JSON=False)
388
389 filters = []
390 lines = out.split('\n')
391 for line in lines:
392 words = line.split()
393 if "handle" not in words:
394 continue
395 fltr = {}
396 for flag in flags:
397 fltr[flag] = flag in words
398 for name in named:
399 try:
400 idx = words.index(name)
401 fltr[name] = words[idx + 1]
402 except ValueError:
403 pass
404 filters.append(fltr)
405
406 if expected is not None:
407 fail(len(filters) != expected,
408 "%d ingress filters loaded, expected %d" %
409 (len(filters), expected))
410 return filters
411
412 def cls_bpf_add_filter(self, bpf, da=False, skip_sw=False, skip_hw=False,
413 fail=True):
414 params = ""
415 if da:
416 params += " da"
417 if skip_sw:
418 params += " skip_sw"
419 if skip_hw:
420 params += " skip_hw"
421 return tc("filter add dev %s ingress bpf %s %s" %
422 (self['ifname'], bpf, params), fail=fail)
423
424 def set_ethtool_tc_offloads(self, enable, fail=True):
425 args = "hw-tc-offload %s" % ("on" if enable else "off")
426 return ethtool(self, "-K", args, fail=fail)
427
428################################################################################
429def clean_up():
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800430 global files, netns, devs
431
Jakub Kicinski417ec262017-12-01 15:09:00 -0800432 for dev in devs:
433 dev.remove()
434 for f in files:
435 cmd("rm -f %s" % (f))
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800436 for ns in netns:
437 cmd("ip netns delete %s" % (ns))
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800438 files = []
439 netns = []
Jakub Kicinski417ec262017-12-01 15:09:00 -0800440
441def pin_prog(file_name, idx=0):
442 progs = bpftool_prog_list(expected=(idx + 1))
443 prog = progs[idx]
444 bpftool("prog pin id %d %s" % (prog["id"], file_name))
445 files.append(file_name)
446
447 return file_name, bpf_pinned(file_name)
448
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800449def pin_map(file_name, idx=0, expected=1):
450 maps = bpftool_map_list(expected=expected)
451 m = maps[idx]
452 bpftool("map pin id %d %s" % (m["id"], file_name))
453 files.append(file_name)
454
455 return file_name, bpf_pinned(file_name)
456
457def check_dev_info_removed(prog_file=None, map_file=None):
458 bpftool_prog_list(expected=0)
459 ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
460 fail(ret == 0, "Showing prog with removed device did not fail")
461 fail(err["error"].find("No such device") == -1,
462 "Showing prog with removed device expected ENODEV, error is %s" %
463 (err["error"]))
464
465 bpftool_map_list(expected=0)
466 ret, err = bpftool("map show pin %s" % (map_file), fail=False)
467 fail(ret == 0, "Showing map with removed device did not fail")
468 fail(err["error"].find("No such device") == -1,
469 "Showing map with removed device expected ENODEV, error is %s" %
470 (err["error"]))
471
472def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
473 progs = bpftool_prog_list(expected=1, ns=ns)
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800474 prog = progs[0]
475
476 fail("dev" not in prog.keys(), "Device parameters not reported")
477 dev = prog["dev"]
478 fail("ifindex" not in dev.keys(), "Device parameters not reported")
479 fail("ns_dev" not in dev.keys(), "Device parameters not reported")
480 fail("ns_inode" not in dev.keys(), "Device parameters not reported")
481
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800482 if not other_ns:
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800483 fail("ifname" not in dev.keys(), "Ifname not reported")
484 fail(dev["ifname"] != sim["ifname"],
485 "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
486 else:
487 fail("ifname" in dev.keys(), "Ifname is reported for other ns")
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800488
489 maps = bpftool_map_list(expected=2, ns=ns)
490 for m in maps:
491 fail("dev" not in m.keys(), "Device parameters not reported")
492 fail(dev != m["dev"], "Map's device different than program's")
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800493
Jakub Kicinski417ec262017-12-01 15:09:00 -0800494# Parse command line
495parser = argparse.ArgumentParser()
496parser.add_argument("--log", help="output verbose log to given file")
497args = parser.parse_args()
498if args.log:
499 logfile = open(args.log, 'w+')
500 logfile.write("# -*-Org-*-")
501
502log("Prepare...", "", level=1)
503log_level_inc()
504
505# Check permissions
506skip(os.getuid() != 0, "test must be run as root")
507
508# Check tools
509ret, progs = bpftool("prog", fail=False)
510skip(ret != 0, "bpftool not installed")
511# Check no BPF programs are loaded
512skip(len(progs) != 0, "BPF programs already loaded on the system")
513
514# Check netdevsim
515ret, out = cmd("modprobe netdevsim", fail=False)
516skip(ret != 0, "netdevsim module could not be loaded")
517
518# Check debugfs
519_, out = cmd("mount")
520if out.find("/sys/kernel/debug type debugfs") == -1:
521 cmd("mount -t debugfs none /sys/kernel/debug")
522
523# Check samples are compiled
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800524samples = ["sample_ret0.o", "sample_map_ret0.o"]
Jakub Kicinski417ec262017-12-01 15:09:00 -0800525for s in samples:
526 ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
527 skip(ret != 0, "sample %s/%s not found, please compile it" %
528 (bpf_test_dir, s))
529
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800530# Check if net namespaces seem to work
531ns = mknetns()
532skip(ns is None, "Could not create a net namespace")
533cmd("ip netns delete %s" % (ns))
534netns = []
535
Jakub Kicinski417ec262017-12-01 15:09:00 -0800536try:
537 obj = bpf_obj("sample_ret0.o")
538 bytecode = bpf_bytecode("1,6 0 0 4294967295,")
539
540 start_test("Test destruction of generic XDP...")
541 sim = NetdevSim()
542 sim.set_xdp(obj, "generic")
543 sim.remove()
544 bpftool_prog_list_wait(expected=0)
545
546 sim = NetdevSim()
547 sim.tc_add_ingress()
548
549 start_test("Test TC non-offloaded...")
550 ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
551 fail(ret != 0, "Software TC filter did not load")
552
553 start_test("Test TC non-offloaded isn't getting bound...")
554 ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
555 fail(ret != 0, "Software TC filter did not load")
556 sim.dfs_get_bound_progs(expected=0)
557
558 sim.tc_flush_filters()
559
560 start_test("Test TC offloads are off by default...")
561 ret, _ = sim.cls_bpf_add_filter(obj, skip_sw=True, fail=False)
562 fail(ret == 0, "TC filter loaded without enabling TC offloads")
563 sim.wait_for_flush()
564
565 sim.set_ethtool_tc_offloads(True)
566 sim.dfs["bpf_tc_non_bound_accept"] = "Y"
567
568 start_test("Test TC offload by default...")
569 ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
570 fail(ret != 0, "Software TC filter did not load")
571 sim.dfs_get_bound_progs(expected=0)
572 ingress = sim.tc_show_ingress(expected=1)
573 fltr = ingress[0]
574 fail(not fltr["in_hw"], "Filter not offloaded by default")
575
576 sim.tc_flush_filters()
577
578 start_test("Test TC cBPF bytcode tries offload by default...")
579 ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
580 fail(ret != 0, "Software TC filter did not load")
581 sim.dfs_get_bound_progs(expected=0)
582 ingress = sim.tc_show_ingress(expected=1)
583 fltr = ingress[0]
584 fail(not fltr["in_hw"], "Bytecode not offloaded by default")
585
586 sim.tc_flush_filters()
587 sim.dfs["bpf_tc_non_bound_accept"] = "N"
588
589 start_test("Test TC cBPF unbound bytecode doesn't offload...")
590 ret, _ = sim.cls_bpf_add_filter(bytecode, skip_sw=True, fail=False)
591 fail(ret == 0, "TC bytecode loaded for offload")
592 sim.wait_for_flush()
593
594 start_test("Test TC offloads work...")
595 ret, _ = sim.cls_bpf_add_filter(obj, skip_sw=True, fail=False)
596 fail(ret != 0, "TC filter did not load with TC offloads enabled")
597
598 start_test("Test TC offload basics...")
599 dfs = sim.dfs_get_bound_progs(expected=1)
600 progs = bpftool_prog_list(expected=1)
601 ingress = sim.tc_show_ingress(expected=1)
602
603 dprog = dfs[0]
604 prog = progs[0]
605 fltr = ingress[0]
606 fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
607 fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
608 fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
609
610 start_test("Test TC offload is device-bound...")
611 fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
612 fail(prog["tag"] != fltr["tag"], "Program tags don't match")
613 fail(fltr["id"] != dprog["id"], "Program IDs don't match")
614 fail(dprog["state"] != "xlated", "Offloaded program state not translated")
615 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
616
617 start_test("Test disabling TC offloads is rejected while filters installed...")
618 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
619 fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
620
621 start_test("Test qdisc removal frees things...")
622 sim.tc_flush_filters()
623 sim.tc_show_ingress(expected=0)
624
625 start_test("Test disabling TC offloads is OK without filters...")
626 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
627 fail(ret != 0,
628 "Driver refused to disable TC offloads without filters installed...")
629
630 sim.set_ethtool_tc_offloads(True)
631
632 start_test("Test destroying device gets rid of TC filters...")
633 sim.cls_bpf_add_filter(obj, skip_sw=True)
634 sim.remove()
635 bpftool_prog_list_wait(expected=0)
636
637 sim = NetdevSim()
638 sim.set_ethtool_tc_offloads(True)
639
640 start_test("Test destroying device gets rid of XDP...")
641 sim.set_xdp(obj, "offload")
642 sim.remove()
643 bpftool_prog_list_wait(expected=0)
644
645 sim = NetdevSim()
646 sim.set_ethtool_tc_offloads(True)
647
648 start_test("Test XDP prog reporting...")
649 sim.set_xdp(obj, "drv")
650 ipl = sim.ip_link_show(xdp=True)
651 progs = bpftool_prog_list(expected=1)
652 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
653 "Loaded program has wrong ID")
654
655 start_test("Test XDP prog replace without force...")
656 ret, _ = sim.set_xdp(obj, "drv", fail=False)
657 fail(ret == 0, "Replaced XDP program without -force")
658 sim.wait_for_flush(total=1)
659
660 start_test("Test XDP prog replace with force...")
661 ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
662 fail(ret != 0, "Could not replace XDP program with -force")
663 bpftool_prog_list_wait(expected=1)
664 ipl = sim.ip_link_show(xdp=True)
665 progs = bpftool_prog_list(expected=1)
666 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
667 "Loaded program has wrong ID")
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800668 fail("dev" in progs[0].keys(),
669 "Device parameters reported for non-offloaded program")
Jakub Kicinski417ec262017-12-01 15:09:00 -0800670
671 start_test("Test XDP prog replace with bad flags...")
672 ret, _ = sim.set_xdp(obj, "offload", force=True, fail=False)
673 fail(ret == 0, "Replaced XDP program with a program in different mode")
674 ret, _ = sim.set_xdp(obj, "", force=True, fail=False)
675 fail(ret == 0, "Replaced XDP program with a program in different mode")
676
677 start_test("Test XDP prog remove with bad flags...")
678 ret, _ = sim.unset_xdp("offload", force=True, fail=False)
679 fail(ret == 0, "Removed program with a bad mode mode")
680 ret, _ = sim.unset_xdp("", force=True, fail=False)
681 fail(ret == 0, "Removed program with a bad mode mode")
682
683 start_test("Test MTU restrictions...")
684 ret, _ = sim.set_mtu(9000, fail=False)
685 fail(ret == 0,
686 "Driver should refuse to increase MTU to 9000 with XDP loaded...")
687 sim.unset_xdp("drv")
688 bpftool_prog_list_wait(expected=0)
689 sim.set_mtu(9000)
690 ret, _ = sim.set_xdp(obj, "drv", fail=False)
691 fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
692 sim.set_mtu(1500)
693
694 sim.wait_for_flush()
695 start_test("Test XDP offload...")
696 sim.set_xdp(obj, "offload")
697 ipl = sim.ip_link_show(xdp=True)
698 link_xdp = ipl["xdp"]["prog"]
699 progs = bpftool_prog_list(expected=1)
700 prog = progs[0]
701 fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
702
703 start_test("Test XDP offload is device bound...")
704 dfs = sim.dfs_get_bound_progs(expected=1)
705 dprog = dfs[0]
706
707 fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
708 fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
709 fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
710 fail(dprog["state"] != "xlated", "Offloaded program state not translated")
711 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
712
713 start_test("Test removing XDP program many times...")
714 sim.unset_xdp("offload")
715 sim.unset_xdp("offload")
716 sim.unset_xdp("drv")
717 sim.unset_xdp("drv")
718 sim.unset_xdp("")
719 sim.unset_xdp("")
720 bpftool_prog_list_wait(expected=0)
721
722 start_test("Test attempt to use a program for a wrong device...")
723 sim2 = NetdevSim()
724 sim2.set_xdp(obj, "offload")
725 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
726
727 ret, _ = sim.set_xdp(pinned, "offload", fail=False)
728 fail(ret == 0, "Pinned program loaded for a different device accepted")
729 sim2.remove()
730 ret, _ = sim.set_xdp(pinned, "offload", fail=False)
731 fail(ret == 0, "Pinned program loaded for a removed device accepted")
732 rm(pin_file)
733 bpftool_prog_list_wait(expected=0)
734
735 start_test("Test mixing of TC and XDP...")
736 sim.tc_add_ingress()
737 sim.set_xdp(obj, "offload")
738 ret, _ = sim.cls_bpf_add_filter(obj, skip_sw=True, fail=False)
739 fail(ret == 0, "Loading TC when XDP active should fail")
740 sim.unset_xdp("offload")
741 sim.wait_for_flush()
742
743 sim.cls_bpf_add_filter(obj, skip_sw=True)
744 ret, _ = sim.set_xdp(obj, "offload", fail=False)
745 fail(ret == 0, "Loading XDP when TC active should fail")
746
747 start_test("Test binding TC from pinned...")
748 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
749 sim.tc_flush_filters(bound=1, total=1)
750 sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
751 sim.tc_flush_filters(bound=1, total=1)
752
753 start_test("Test binding XDP from pinned...")
754 sim.set_xdp(obj, "offload")
755 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
756
757 sim.set_xdp(pinned, "offload", force=True)
758 sim.unset_xdp("offload")
759 sim.set_xdp(pinned, "offload", force=True)
760 sim.unset_xdp("offload")
761
762 start_test("Test offload of wrong type fails...")
763 ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
764 fail(ret == 0, "Managed to attach XDP program to TC")
765
766 start_test("Test asking for TC offload of two filters...")
767 sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
David S. Millerfba961a2017-12-22 11:16:31 -0500768 ret, _ = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True, fail=False)
769 fail(ret == 0, "Managed to offload two TC filters at the same time")
Jakub Kicinski417ec262017-12-01 15:09:00 -0800770
771 sim.tc_flush_filters(bound=2, total=2)
772
773 start_test("Test if netdev removal waits for translation...")
774 delay_msec = 500
775 sim.dfs["bpf_bind_verifier_delay"] = delay_msec
776 start = time.time()
777 cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
778 (sim['ifname'], obj)
779 tc_proc = cmd(cmd_line, background=True, fail=False)
780 # Wait for the verifier to start
781 while sim.dfs_num_bound_progs() <= 2:
782 pass
783 sim.remove()
784 end = time.time()
785 ret, _ = cmd_result(tc_proc, fail=False)
786 time_diff = end - start
787 log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
788
789 fail(ret == 0, "Managed to load TC filter on a unregistering device")
790 delay_sec = delay_msec * 0.001
791 fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
792 (time_diff, delay_sec))
793
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800794 # Remove all pinned files and reinstantiate the netdev
795 clean_up()
796 bpftool_prog_list_wait(expected=0)
797
798 sim = NetdevSim()
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800799 map_obj = bpf_obj("sample_map_ret0.o")
800 start_test("Test loading program with maps...")
801 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800802
803 start_test("Test bpftool bound info reporting (own ns)...")
804 check_dev_info(False, "")
805
806 start_test("Test bpftool bound info reporting (other ns)...")
807 ns = mknetns()
808 sim.set_ns(ns)
809 check_dev_info(True, "")
810
811 start_test("Test bpftool bound info reporting (remote ns)...")
812 check_dev_info(False, ns)
813
814 start_test("Test bpftool bound info reporting (back to own ns)...")
815 sim.set_ns("")
816 check_dev_info(False, "")
817
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800818 prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
819 map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800820 sim.remove()
821
822 start_test("Test bpftool bound info reporting (removed dev)...")
Jakub Kicinski7fedbb72018-01-17 19:13:31 -0800823 check_dev_info_removed(prog_file=prog_file, map_file=map_file)
824
825 # Remove all pinned files and reinstantiate the netdev
826 clean_up()
827 bpftool_prog_list_wait(expected=0)
828
829 sim = NetdevSim()
830
831 start_test("Test map update (no flags)...")
832 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
833 maps = bpftool_map_list(expected=2)
834 array = maps[0] if maps[0]["type"] == "array" else maps[1]
835 htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
836 for m in maps:
837 for i in range(2):
838 bpftool("map update id %d key %s value %s" %
839 (m["id"], int2str("I", i), int2str("Q", i * 3)))
840
841 for m in maps:
842 ret, _ = bpftool("map update id %d key %s value %s" %
843 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
844 fail=False)
845 fail(ret == 0, "added too many entries")
846
847 start_test("Test map update (exists)...")
848 for m in maps:
849 for i in range(2):
850 bpftool("map update id %d key %s value %s exist" %
851 (m["id"], int2str("I", i), int2str("Q", i * 3)))
852
853 for m in maps:
854 ret, err = bpftool("map update id %d key %s value %s exist" %
855 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
856 fail=False)
857 fail(ret == 0, "updated non-existing key")
858 fail(err["error"].find("No such file or directory") == -1,
859 "expected ENOENT, error is '%s'" % (err["error"]))
860
861 start_test("Test map update (noexist)...")
862 for m in maps:
863 for i in range(2):
864 ret, err = bpftool("map update id %d key %s value %s noexist" %
865 (m["id"], int2str("I", i), int2str("Q", i * 3)),
866 fail=False)
867 fail(ret == 0, "updated existing key")
868 fail(err["error"].find("File exists") == -1,
869 "expected EEXIST, error is '%s'" % (err["error"]))
870
871 start_test("Test map dump...")
872 for m in maps:
873 _, entries = bpftool("map dump id %d" % (m["id"]))
874 for i in range(2):
875 key = str2int(entries[i]["key"])
876 fail(key != i, "expected key %d, got %d" % (key, i))
877 val = str2int(entries[i]["value"])
878 fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
879
880 start_test("Test map getnext...")
881 for m in maps:
882 _, entry = bpftool("map getnext id %d" % (m["id"]))
883 key = str2int(entry["next_key"])
884 fail(key != 0, "next key %d, expected %d" % (key, 0))
885 _, entry = bpftool("map getnext id %d key %s" %
886 (m["id"], int2str("I", 0)))
887 key = str2int(entry["next_key"])
888 fail(key != 1, "next key %d, expected %d" % (key, 1))
889 ret, err = bpftool("map getnext id %d key %s" %
890 (m["id"], int2str("I", 1)), fail=False)
891 fail(ret == 0, "got next key past the end of map")
892 fail(err["error"].find("No such file or directory") == -1,
893 "expected ENOENT, error is '%s'" % (err["error"]))
894
895 start_test("Test map delete (htab)...")
896 for i in range(2):
897 bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
898
899 start_test("Test map delete (array)...")
900 for i in range(2):
901 ret, err = bpftool("map delete id %d key %s" %
902 (htab["id"], int2str("I", i)), fail=False)
903 fail(ret == 0, "removed entry from an array")
904 fail(err["error"].find("No such file or directory") == -1,
905 "expected ENOENT, error is '%s'" % (err["error"]))
906
907 start_test("Test map remove...")
908 sim.unset_xdp("offload")
909 bpftool_map_list_wait(expected=0)
910 sim.remove()
911
912 sim = NetdevSim()
913 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
914 sim.remove()
915 bpftool_map_list_wait(expected=0)
916
917 start_test("Test map creation fail path...")
918 sim = NetdevSim()
919 sim.dfs["bpf_map_accept"] = "N"
920 ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
921 fail(ret == 0,
922 "netdevsim didn't refuse to create a map with offload disabled")
Jakub Kicinski752d7b42017-12-27 18:39:11 -0800923
Jakub Kicinski417ec262017-12-01 15:09:00 -0800924 print("%s: OK" % (os.path.basename(__file__)))
925
926finally:
927 log("Clean up...", "", level=1)
928 log_level_inc()
929 clean_up()