1#!/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 21import random 22import string 23import struct 24import subprocess 25import time 26 27logfile = None 28log_level = 1 29skip_extack = False 30bpf_test_dir = os.path.dirname(os.path.realpath(__file__)) 31pp = pprint.PrettyPrinter() 32devs = [] # devices we created for clean up 33files = [] # files to be removed 34netns = [] # net namespaces to be removed 35 36def log_get_sec(level=0): 37 return "*" * (log_level + level) 38 39def log_level_inc(add=1): 40 global log_level 41 log_level += add 42 43def log_level_dec(sub=1): 44 global log_level 45 log_level -= sub 46 47def log_level_set(level): 48 global log_level 49 log_level = level 50 51def log(header, data, level=None): 52 """ 53 Output to an optional log. 54 """ 55 if logfile is None: 56 return 57 if level is not None: 58 log_level_set(level) 59 60 if not isinstance(data, str): 61 data = pp.pformat(data) 62 63 if len(header): 64 logfile.write("\n" + log_get_sec() + " ") 65 logfile.write(header) 66 if len(header) and len(data.strip()): 67 logfile.write("\n") 68 logfile.write(data) 69 70def skip(cond, msg): 71 if not cond: 72 return 73 print("SKIP: " + msg) 74 log("SKIP: " + msg, "", level=1) 75 os.sys.exit(0) 76 77def fail(cond, msg): 78 if not cond: 79 return 80 print("FAIL: " + msg) 81 log("FAIL: " + msg, "", level=1) 82 os.sys.exit(1) 83 84def start_test(msg): 85 log(msg, "", level=1) 86 log_level_inc() 87 print(msg) 88 89def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True): 90 """ 91 Run a command in subprocess and return tuple of (retval, stdout); 92 optionally return stderr as well as third value. 93 """ 94 proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, 95 stderr=subprocess.PIPE) 96 if background: 97 msg = "%s START: %s" % (log_get_sec(1), 98 datetime.now().strftime("%H:%M:%S.%f")) 99 log("BKG " + proc.args, msg) 100 return proc 101 102 return cmd_result(proc, include_stderr=include_stderr, fail=fail) 103 104def cmd_result(proc, include_stderr=False, fail=False): 105 stdout, stderr = proc.communicate() 106 stdout = stdout.decode("utf-8") 107 stderr = stderr.decode("utf-8") 108 proc.stdout.close() 109 proc.stderr.close() 110 111 stderr = "\n" + stderr 112 if stderr[-1] == "\n": 113 stderr = stderr[:-1] 114 115 sec = log_get_sec(1) 116 log("CMD " + proc.args, 117 "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" % 118 (proc.returncode, sec, stdout, sec, stderr, 119 sec, datetime.now().strftime("%H:%M:%S.%f"))) 120 121 if proc.returncode != 0 and fail: 122 if len(stderr) > 0 and stderr[-1] == "\n": 123 stderr = stderr[:-1] 124 raise Exception("Command failed: %s\n%s" % (proc.args, stderr)) 125 126 if include_stderr: 127 return proc.returncode, stdout, stderr 128 else: 129 return proc.returncode, stdout 130 131def rm(f): 132 cmd("rm -f %s" % (f)) 133 if f in files: 134 files.remove(f) 135 136def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False): 137 params = "" 138 if JSON: 139 params += "%s " % (flags["json"]) 140 141 if ns != "": 142 ns = "ip netns exec %s " % (ns) 143 144 if include_stderr: 145 ret, stdout, stderr = cmd(ns + name + " " + params + args, 146 fail=fail, include_stderr=True) 147 else: 148 ret, stdout = cmd(ns + name + " " + params + args, 149 fail=fail, include_stderr=False) 150 151 if JSON and len(stdout.strip()) != 0: 152 out = json.loads(stdout) 153 else: 154 out = stdout 155 156 if include_stderr: 157 return ret, out, stderr 158 else: 159 return ret, out 160 161def bpftool(args, JSON=True, ns="", fail=True): 162 return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail) 163 164def bpftool_prog_list(expected=None, ns=""): 165 _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True) 166 # Remove the base progs 167 for p in base_progs: 168 if p in progs: 169 progs.remove(p) 170 if expected is not None: 171 if len(progs) != expected: 172 fail(True, "%d BPF programs loaded, expected %d" % 173 (len(progs), expected)) 174 return progs 175 176def bpftool_map_list(expected=None, ns=""): 177 _, maps = bpftool("map show", JSON=True, ns=ns, fail=True) 178 # Remove the base maps 179 for m in base_maps: 180 if m in maps: 181 maps.remove(m) 182 if expected is not None: 183 if len(maps) != expected: 184 fail(True, "%d BPF maps loaded, expected %d" % 185 (len(maps), expected)) 186 return maps 187 188def bpftool_prog_list_wait(expected=0, n_retry=20): 189 for i in range(n_retry): 190 nprogs = len(bpftool_prog_list()) 191 if nprogs == expected: 192 return 193 time.sleep(0.05) 194 raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs)) 195 196def bpftool_map_list_wait(expected=0, n_retry=20): 197 for i in range(n_retry): 198 nmaps = len(bpftool_map_list()) 199 if nmaps == expected: 200 return 201 time.sleep(0.05) 202 raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps)) 203 204def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False): 205 if force: 206 args = "-force " + args 207 return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns, 208 fail=fail, include_stderr=include_stderr) 209 210def tc(args, JSON=True, ns="", fail=True, include_stderr=False): 211 return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns, 212 fail=fail, include_stderr=include_stderr) 213 214def ethtool(dev, opt, args, fail=True): 215 return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail) 216 217def bpf_obj(name, sec=".text", path=bpf_test_dir,): 218 return "obj %s sec %s" % (os.path.join(path, name), sec) 219 220def bpf_pinned(name): 221 return "pinned %s" % (name) 222 223def bpf_bytecode(bytecode): 224 return "bytecode \"%s\"" % (bytecode) 225 226def mknetns(n_retry=10): 227 for i in range(n_retry): 228 name = ''.join([random.choice(string.ascii_letters) for i in range(8)]) 229 ret, _ = ip("netns add %s" % (name), fail=False) 230 if ret == 0: 231 netns.append(name) 232 return name 233 return None 234 235def int2str(fmt, val): 236 ret = [] 237 for b in struct.pack(fmt, val): 238 ret.append(int(b)) 239 return " ".join(map(lambda x: str(x), ret)) 240 241def str2int(strtab): 242 inttab = [] 243 for i in strtab: 244 inttab.append(int(i, 16)) 245 ba = bytearray(inttab) 246 if len(strtab) == 4: 247 fmt = "I" 248 elif len(strtab) == 8: 249 fmt = "Q" 250 else: 251 raise Exception("String array of len %d can't be unpacked to an int" % 252 (len(strtab))) 253 return struct.unpack(fmt, ba)[0] 254 255class DebugfsDir: 256 """ 257 Class for accessing DebugFS directories as a dictionary. 258 """ 259 260 def __init__(self, path): 261 self.path = path 262 self._dict = self._debugfs_dir_read(path) 263 264 def __len__(self): 265 return len(self._dict.keys()) 266 267 def __getitem__(self, key): 268 if type(key) is int: 269 key = list(self._dict.keys())[key] 270 return self._dict[key] 271 272 def __setitem__(self, key, value): 273 log("DebugFS set %s = %s" % (key, value), "") 274 log_level_inc() 275 276 cmd("echo '%s' > %s/%s" % (value, self.path, key)) 277 log_level_dec() 278 279 _, out = cmd('cat %s/%s' % (self.path, key)) 280 self._dict[key] = out.strip() 281 282 def _debugfs_dir_read(self, path): 283 dfs = {} 284 285 log("DebugFS state for %s" % (path), "") 286 log_level_inc(add=2) 287 288 _, out = cmd('ls ' + path) 289 for f in out.split(): 290 p = os.path.join(path, f) 291 if os.path.isfile(p): 292 _, out = cmd('cat %s/%s' % (path, f)) 293 dfs[f] = out.strip() 294 elif os.path.isdir(p): 295 dfs[f] = DebugfsDir(p) 296 else: 297 raise Exception("%s is neither file nor directory" % (p)) 298 299 log_level_dec() 300 log("DebugFS state", dfs) 301 log_level_dec() 302 303 return dfs 304 305class NetdevSim: 306 """ 307 Class for netdevsim netdevice and its attributes. 308 """ 309 310 def __init__(self): 311 self.dev = self._netdevsim_create() 312 devs.append(self) 313 314 self.ns = "" 315 316 self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname']) 317 self.dfs_refresh() 318 319 def __getitem__(self, key): 320 return self.dev[key] 321 322 def _netdevsim_create(self): 323 _, old = ip("link show") 324 ip("link add sim%d type netdevsim") 325 _, new = ip("link show") 326 327 for dev in new: 328 f = filter(lambda x: x["ifname"] == dev["ifname"], old) 329 if len(list(f)) == 0: 330 return dev 331 332 raise Exception("failed to create netdevsim device") 333 334 def remove(self): 335 devs.remove(self) 336 ip("link del dev %s" % (self.dev["ifname"]), ns=self.ns) 337 338 def dfs_refresh(self): 339 self.dfs = DebugfsDir(self.dfs_dir) 340 return self.dfs 341 342 def dfs_read(self, f): 343 path = os.path.join(self.dfs_dir, f) 344 _, data = cmd('cat %s' % (path)) 345 return data.strip() 346 347 def dfs_num_bound_progs(self): 348 path = os.path.join(self.dfs_dir, "bpf_bound_progs") 349 _, progs = cmd('ls %s' % (path)) 350 return len(progs.split()) 351 352 def dfs_get_bound_progs(self, expected): 353 progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs")) 354 if expected is not None: 355 if len(progs) != expected: 356 fail(True, "%d BPF programs bound, expected %d" % 357 (len(progs), expected)) 358 return progs 359 360 def wait_for_flush(self, bound=0, total=0, n_retry=20): 361 for i in range(n_retry): 362 nbound = self.dfs_num_bound_progs() 363 nprogs = len(bpftool_prog_list()) 364 if nbound == bound and nprogs == total: 365 return 366 time.sleep(0.05) 367 raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs)) 368 369 def set_ns(self, ns): 370 name = "1" if ns == "" else ns 371 ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns) 372 self.ns = ns 373 374 def set_mtu(self, mtu, fail=True): 375 return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu), 376 fail=fail) 377 378 def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False, 379 fail=True, include_stderr=False): 380 if verbose: 381 bpf += " verbose" 382 return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf), 383 force=force, JSON=JSON, 384 fail=fail, include_stderr=include_stderr) 385 386 def unset_xdp(self, mode, force=False, JSON=True, 387 fail=True, include_stderr=False): 388 return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode), 389 force=force, JSON=JSON, 390 fail=fail, include_stderr=include_stderr) 391 392 def ip_link_show(self, xdp): 393 _, link = ip("link show dev %s" % (self['ifname'])) 394 if len(link) > 1: 395 raise Exception("Multiple objects on ip link show") 396 if len(link) < 1: 397 return {} 398 fail(xdp != "xdp" in link, 399 "XDP program not reporting in iplink (reported %s, expected %s)" % 400 ("xdp" in link, xdp)) 401 return link[0] 402 403 def tc_add_ingress(self): 404 tc("qdisc add dev %s ingress" % (self['ifname'])) 405 406 def tc_del_ingress(self): 407 tc("qdisc del dev %s ingress" % (self['ifname'])) 408 409 def tc_flush_filters(self, bound=0, total=0): 410 self.tc_del_ingress() 411 self.tc_add_ingress() 412 self.wait_for_flush(bound=bound, total=total) 413 414 def tc_show_ingress(self, expected=None): 415 # No JSON support, oh well... 416 flags = ["skip_sw", "skip_hw", "in_hw"] 417 named = ["protocol", "pref", "chain", "handle", "id", "tag"] 418 419 args = "-s filter show dev %s ingress" % (self['ifname']) 420 _, out = tc(args, JSON=False) 421 422 filters = [] 423 lines = out.split('\n') 424 for line in lines: 425 words = line.split() 426 if "handle" not in words: 427 continue 428 fltr = {} 429 for flag in flags: 430 fltr[flag] = flag in words 431 for name in named: 432 try: 433 idx = words.index(name) 434 fltr[name] = words[idx + 1] 435 except ValueError: 436 pass 437 filters.append(fltr) 438 439 if expected is not None: 440 fail(len(filters) != expected, 441 "%d ingress filters loaded, expected %d" % 442 (len(filters), expected)) 443 return filters 444 445 def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None, 446 chain=None, cls="", params="", 447 fail=True, include_stderr=False): 448 spec = "" 449 if prio is not None: 450 spec += " prio %d" % (prio) 451 if handle: 452 spec += " handle %s" % (handle) 453 if chain is not None: 454 spec += " chain %d" % (chain) 455 456 return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\ 457 .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec, 458 cls=cls, params=params), 459 fail=fail, include_stderr=include_stderr) 460 461 def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None, 462 chain=None, da=False, verbose=False, 463 skip_sw=False, skip_hw=False, 464 fail=True, include_stderr=False): 465 cls = "bpf " + bpf 466 467 params = "" 468 if da: 469 params += " da" 470 if verbose: 471 params += " verbose" 472 if skip_sw: 473 params += " skip_sw" 474 if skip_hw: 475 params += " skip_hw" 476 477 return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls, 478 chain=chain, params=params, 479 fail=fail, include_stderr=include_stderr) 480 481 def set_ethtool_tc_offloads(self, enable, fail=True): 482 args = "hw-tc-offload %s" % ("on" if enable else "off") 483 return ethtool(self, "-K", args, fail=fail) 484 485################################################################################ 486def clean_up(): 487 global files, netns, devs 488 489 for dev in devs: 490 dev.remove() 491 for f in files: 492 cmd("rm -f %s" % (f)) 493 for ns in netns: 494 cmd("ip netns delete %s" % (ns)) 495 files = [] 496 netns = [] 497 498def pin_prog(file_name, idx=0): 499 progs = bpftool_prog_list(expected=(idx + 1)) 500 prog = progs[idx] 501 bpftool("prog pin id %d %s" % (prog["id"], file_name)) 502 files.append(file_name) 503 504 return file_name, bpf_pinned(file_name) 505 506def pin_map(file_name, idx=0, expected=1): 507 maps = bpftool_map_list(expected=expected) 508 m = maps[idx] 509 bpftool("map pin id %d %s" % (m["id"], file_name)) 510 files.append(file_name) 511 512 return file_name, bpf_pinned(file_name) 513 514def check_dev_info_removed(prog_file=None, map_file=None): 515 bpftool_prog_list(expected=0) 516 ret, err = bpftool("prog show pin %s" % (prog_file), fail=False) 517 fail(ret == 0, "Showing prog with removed device did not fail") 518 fail(err["error"].find("No such device") == -1, 519 "Showing prog with removed device expected ENODEV, error is %s" % 520 (err["error"])) 521 522 bpftool_map_list(expected=0) 523 ret, err = bpftool("map show pin %s" % (map_file), fail=False) 524 fail(ret == 0, "Showing map with removed device did not fail") 525 fail(err["error"].find("No such device") == -1, 526 "Showing map with removed device expected ENODEV, error is %s" % 527 (err["error"])) 528 529def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False): 530 progs = bpftool_prog_list(expected=1, ns=ns) 531 prog = progs[0] 532 533 fail("dev" not in prog.keys(), "Device parameters not reported") 534 dev = prog["dev"] 535 fail("ifindex" not in dev.keys(), "Device parameters not reported") 536 fail("ns_dev" not in dev.keys(), "Device parameters not reported") 537 fail("ns_inode" not in dev.keys(), "Device parameters not reported") 538 539 if not other_ns: 540 fail("ifname" not in dev.keys(), "Ifname not reported") 541 fail(dev["ifname"] != sim["ifname"], 542 "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"])) 543 else: 544 fail("ifname" in dev.keys(), "Ifname is reported for other ns") 545 546 maps = bpftool_map_list(expected=2, ns=ns) 547 for m in maps: 548 fail("dev" not in m.keys(), "Device parameters not reported") 549 fail(dev != m["dev"], "Map's device different than program's") 550 551def check_extack(output, reference, args): 552 if skip_extack: 553 return 554 lines = output.split("\n") 555 comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference 556 fail(not comp, "Missing or incorrect netlink extack message") 557 558def check_extack_nsim(output, reference, args): 559 check_extack(output, "netdevsim: " + reference, args) 560 561def check_no_extack(res, needle): 562 fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"), 563 "Found '%s' in command output, leaky extack?" % (needle)) 564 565def check_verifier_log(output, reference): 566 lines = output.split("\n") 567 for l in reversed(lines): 568 if l == reference: 569 return 570 fail(True, "Missing or incorrect message from netdevsim in verifier log") 571 572def test_spurios_extack(sim, obj, skip_hw, needle): 573 res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw, 574 include_stderr=True) 575 check_no_extack(res, needle) 576 res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, 577 skip_hw=skip_hw, include_stderr=True) 578 check_no_extack(res, needle) 579 res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf", 580 include_stderr=True) 581 check_no_extack(res, needle) 582 583 584# Parse command line 585parser = argparse.ArgumentParser() 586parser.add_argument("--log", help="output verbose log to given file") 587args = parser.parse_args() 588if args.log: 589 logfile = open(args.log, 'w+') 590 logfile.write("# -*-Org-*-") 591 592log("Prepare...", "", level=1) 593log_level_inc() 594 595# Check permissions 596skip(os.getuid() != 0, "test must be run as root") 597 598# Check tools 599ret, progs = bpftool("prog", fail=False) 600skip(ret != 0, "bpftool not installed") 601base_progs = progs 602_, base_maps = bpftool("map") 603 604# Check netdevsim 605ret, out = cmd("modprobe netdevsim", fail=False) 606skip(ret != 0, "netdevsim module could not be loaded") 607 608# Check debugfs 609_, out = cmd("mount") 610if out.find("/sys/kernel/debug type debugfs") == -1: 611 cmd("mount -t debugfs none /sys/kernel/debug") 612 613# Check samples are compiled 614samples = ["sample_ret0.o", "sample_map_ret0.o"] 615for s in samples: 616 ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False) 617 skip(ret != 0, "sample %s/%s not found, please compile it" % 618 (bpf_test_dir, s)) 619 620# Check if iproute2 is built with libmnl (needed by extack support) 621_, _, err = cmd("tc qdisc delete dev lo handle 0", 622 fail=False, include_stderr=True) 623if err.find("Error: Failed to find qdisc with specified handle.") == -1: 624 print("Warning: no extack message in iproute2 output, libmnl missing?") 625 log("Warning: no extack message in iproute2 output, libmnl missing?", "") 626 skip_extack = True 627 628# Check if net namespaces seem to work 629ns = mknetns() 630skip(ns is None, "Could not create a net namespace") 631cmd("ip netns delete %s" % (ns)) 632netns = [] 633 634try: 635 obj = bpf_obj("sample_ret0.o") 636 bytecode = bpf_bytecode("1,6 0 0 4294967295,") 637 638 start_test("Test destruction of generic XDP...") 639 sim = NetdevSim() 640 sim.set_xdp(obj, "generic") 641 sim.remove() 642 bpftool_prog_list_wait(expected=0) 643 644 sim = NetdevSim() 645 sim.tc_add_ingress() 646 647 start_test("Test TC non-offloaded...") 648 ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False) 649 fail(ret != 0, "Software TC filter did not load") 650 651 start_test("Test TC non-offloaded isn't getting bound...") 652 ret, _ = sim.cls_bpf_add_filter(obj, fail=False) 653 fail(ret != 0, "Software TC filter did not load") 654 sim.dfs_get_bound_progs(expected=0) 655 656 sim.tc_flush_filters() 657 658 start_test("Test TC offloads are off by default...") 659 ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True, 660 fail=False, include_stderr=True) 661 fail(ret == 0, "TC filter loaded without enabling TC offloads") 662 check_extack(err, "TC offload is disabled on net device.", args) 663 sim.wait_for_flush() 664 665 sim.set_ethtool_tc_offloads(True) 666 sim.dfs["bpf_tc_non_bound_accept"] = "Y" 667 668 start_test("Test TC offload by default...") 669 ret, _ = sim.cls_bpf_add_filter(obj, fail=False) 670 fail(ret != 0, "Software TC filter did not load") 671 sim.dfs_get_bound_progs(expected=0) 672 ingress = sim.tc_show_ingress(expected=1) 673 fltr = ingress[0] 674 fail(not fltr["in_hw"], "Filter not offloaded by default") 675 676 sim.tc_flush_filters() 677 678 start_test("Test TC cBPF bytcode tries offload by default...") 679 ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False) 680 fail(ret != 0, "Software TC filter did not load") 681 sim.dfs_get_bound_progs(expected=0) 682 ingress = sim.tc_show_ingress(expected=1) 683 fltr = ingress[0] 684 fail(not fltr["in_hw"], "Bytecode not offloaded by default") 685 686 sim.tc_flush_filters() 687 sim.dfs["bpf_tc_non_bound_accept"] = "N" 688 689 start_test("Test TC cBPF unbound bytecode doesn't offload...") 690 ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True, 691 fail=False, include_stderr=True) 692 fail(ret == 0, "TC bytecode loaded for offload") 693 check_extack_nsim(err, "netdevsim configured to reject unbound programs.", 694 args) 695 sim.wait_for_flush() 696 697 start_test("Test non-0 chain offload...") 698 ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1, 699 skip_sw=True, 700 fail=False, include_stderr=True) 701 fail(ret == 0, "Offloaded a filter to chain other than 0") 702 check_extack(err, "Driver supports only offload of chain 0.", args) 703 sim.tc_flush_filters() 704 705 start_test("Test TC replace...") 706 sim.cls_bpf_add_filter(obj, prio=1, handle=1) 707 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1) 708 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 709 710 sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True) 711 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True) 712 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 713 714 sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True) 715 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True) 716 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 717 718 start_test("Test TC replace bad flags...") 719 for i in range(3): 720 for j in range(3): 721 ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, 722 skip_sw=(j == 1), skip_hw=(j == 2), 723 fail=False) 724 fail(bool(ret) != bool(j), 725 "Software TC incorrect load in replace test, iteration %d" % 726 (j)) 727 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 728 729 start_test("Test spurious extack from the driver...") 730 test_spurios_extack(sim, obj, False, "netdevsim") 731 test_spurios_extack(sim, obj, True, "netdevsim") 732 733 sim.set_ethtool_tc_offloads(False) 734 735 test_spurios_extack(sim, obj, False, "TC offload is disabled") 736 test_spurios_extack(sim, obj, True, "TC offload is disabled") 737 738 sim.set_ethtool_tc_offloads(True) 739 740 sim.tc_flush_filters() 741 742 start_test("Test TC offloads work...") 743 ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True, 744 fail=False, include_stderr=True) 745 fail(ret != 0, "TC filter did not load with TC offloads enabled") 746 check_verifier_log(err, "[netdevsim] Hello from netdevsim!") 747 748 start_test("Test TC offload basics...") 749 dfs = sim.dfs_get_bound_progs(expected=1) 750 progs = bpftool_prog_list(expected=1) 751 ingress = sim.tc_show_ingress(expected=1) 752 753 dprog = dfs[0] 754 prog = progs[0] 755 fltr = ingress[0] 756 fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter") 757 fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter") 758 fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back") 759 760 start_test("Test TC offload is device-bound...") 761 fail(str(prog["id"]) != fltr["id"], "Program IDs don't match") 762 fail(prog["tag"] != fltr["tag"], "Program tags don't match") 763 fail(fltr["id"] != dprog["id"], "Program IDs don't match") 764 fail(dprog["state"] != "xlated", "Offloaded program state not translated") 765 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded") 766 767 start_test("Test disabling TC offloads is rejected while filters installed...") 768 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False) 769 fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...") 770 771 start_test("Test qdisc removal frees things...") 772 sim.tc_flush_filters() 773 sim.tc_show_ingress(expected=0) 774 775 start_test("Test disabling TC offloads is OK without filters...") 776 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False) 777 fail(ret != 0, 778 "Driver refused to disable TC offloads without filters installed...") 779 780 sim.set_ethtool_tc_offloads(True) 781 782 start_test("Test destroying device gets rid of TC filters...") 783 sim.cls_bpf_add_filter(obj, skip_sw=True) 784 sim.remove() 785 bpftool_prog_list_wait(expected=0) 786 787 sim = NetdevSim() 788 sim.set_ethtool_tc_offloads(True) 789 790 start_test("Test destroying device gets rid of XDP...") 791 sim.set_xdp(obj, "offload") 792 sim.remove() 793 bpftool_prog_list_wait(expected=0) 794 795 sim = NetdevSim() 796 sim.set_ethtool_tc_offloads(True) 797 798 start_test("Test XDP prog reporting...") 799 sim.set_xdp(obj, "drv") 800 ipl = sim.ip_link_show(xdp=True) 801 progs = bpftool_prog_list(expected=1) 802 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], 803 "Loaded program has wrong ID") 804 805 start_test("Test XDP prog replace without force...") 806 ret, _ = sim.set_xdp(obj, "drv", fail=False) 807 fail(ret == 0, "Replaced XDP program without -force") 808 sim.wait_for_flush(total=1) 809 810 start_test("Test XDP prog replace with force...") 811 ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False) 812 fail(ret != 0, "Could not replace XDP program with -force") 813 bpftool_prog_list_wait(expected=1) 814 ipl = sim.ip_link_show(xdp=True) 815 progs = bpftool_prog_list(expected=1) 816 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], 817 "Loaded program has wrong ID") 818 fail("dev" in progs[0].keys(), 819 "Device parameters reported for non-offloaded program") 820 821 start_test("Test XDP prog replace with bad flags...") 822 ret, _, err = sim.set_xdp(obj, "generic", force=True, 823 fail=False, include_stderr=True) 824 fail(ret == 0, "Replaced XDP program with a program in different mode") 825 fail(err.count("File exists") != 1, "Replaced driver XDP with generic") 826 ret, _, err = sim.set_xdp(obj, "", force=True, 827 fail=False, include_stderr=True) 828 fail(ret == 0, "Replaced XDP program with a program in different mode") 829 check_extack(err, "program loaded with different flags.", args) 830 831 start_test("Test XDP prog remove with bad flags...") 832 ret, _, err = sim.unset_xdp("", force=True, 833 fail=False, include_stderr=True) 834 fail(ret == 0, "Removed program with a bad mode") 835 check_extack(err, "program loaded with different flags.", args) 836 837 start_test("Test MTU restrictions...") 838 ret, _ = sim.set_mtu(9000, fail=False) 839 fail(ret == 0, 840 "Driver should refuse to increase MTU to 9000 with XDP loaded...") 841 sim.unset_xdp("drv") 842 bpftool_prog_list_wait(expected=0) 843 sim.set_mtu(9000) 844 ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True) 845 fail(ret == 0, "Driver should refuse to load program with MTU of 9000...") 846 check_extack_nsim(err, "MTU too large w/ XDP enabled.", args) 847 sim.set_mtu(1500) 848 849 sim.wait_for_flush() 850 start_test("Test XDP offload...") 851 _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True) 852 ipl = sim.ip_link_show(xdp=True) 853 link_xdp = ipl["xdp"]["prog"] 854 progs = bpftool_prog_list(expected=1) 855 prog = progs[0] 856 fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID") 857 check_verifier_log(err, "[netdevsim] Hello from netdevsim!") 858 859 start_test("Test XDP offload is device bound...") 860 dfs = sim.dfs_get_bound_progs(expected=1) 861 dprog = dfs[0] 862 863 fail(prog["id"] != link_xdp["id"], "Program IDs don't match") 864 fail(prog["tag"] != link_xdp["tag"], "Program tags don't match") 865 fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match") 866 fail(dprog["state"] != "xlated", "Offloaded program state not translated") 867 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded") 868 869 start_test("Test removing XDP program many times...") 870 sim.unset_xdp("offload") 871 sim.unset_xdp("offload") 872 sim.unset_xdp("drv") 873 sim.unset_xdp("drv") 874 sim.unset_xdp("") 875 sim.unset_xdp("") 876 bpftool_prog_list_wait(expected=0) 877 878 start_test("Test attempt to use a program for a wrong device...") 879 sim2 = NetdevSim() 880 sim2.set_xdp(obj, "offload") 881 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp") 882 883 ret, _, err = sim.set_xdp(pinned, "offload", 884 fail=False, include_stderr=True) 885 fail(ret == 0, "Pinned program loaded for a different device accepted") 886 check_extack_nsim(err, "program bound to different dev.", args) 887 sim2.remove() 888 ret, _, err = sim.set_xdp(pinned, "offload", 889 fail=False, include_stderr=True) 890 fail(ret == 0, "Pinned program loaded for a removed device accepted") 891 check_extack_nsim(err, "xdpoffload of non-bound program.", args) 892 rm(pin_file) 893 bpftool_prog_list_wait(expected=0) 894 895 start_test("Test multi-attachment XDP - attach...") 896 sim.set_xdp(obj, "offload") 897 xdp = sim.ip_link_show(xdp=True)["xdp"] 898 offloaded = sim.dfs_read("bpf_offloaded_id") 899 fail("prog" not in xdp, "Base program not reported in single program mode") 900 fail(len(ipl["xdp"]["attached"]) != 1, 901 "Wrong attached program count with one program") 902 903 sim.set_xdp(obj, "") 904 two_xdps = sim.ip_link_show(xdp=True)["xdp"] 905 offloaded2 = sim.dfs_read("bpf_offloaded_id") 906 907 fail(two_xdps["mode"] != 4, "Bad mode reported with multiple programs") 908 fail("prog" in two_xdps, "Base program reported in multi program mode") 909 fail(xdp["attached"][0] not in two_xdps["attached"], 910 "Offload program not reported after driver activated") 911 fail(len(two_xdps["attached"]) != 2, 912 "Wrong attached program count with two programs") 913 fail(two_xdps["attached"][0]["prog"]["id"] == 914 two_xdps["attached"][1]["prog"]["id"], 915 "offloaded and drv programs have the same id") 916 fail(offloaded != offloaded2, 917 "offload ID changed after loading driver program") 918 919 start_test("Test multi-attachment XDP - replace...") 920 ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True) 921 fail(err.count("busy") != 1, "Replaced one of programs without -force") 922 923 start_test("Test multi-attachment XDP - detach...") 924 ret, _, err = sim.unset_xdp("drv", force=True, 925 fail=False, include_stderr=True) 926 fail(ret == 0, "Removed program with a bad mode") 927 check_extack(err, "program loaded with different flags.", args) 928 929 sim.unset_xdp("offload") 930 xdp = sim.ip_link_show(xdp=True)["xdp"] 931 offloaded = sim.dfs_read("bpf_offloaded_id") 932 933 fail(xdp["mode"] != 1, "Bad mode reported after multiple programs") 934 fail("prog" not in xdp, 935 "Base program not reported after multi program mode") 936 fail(xdp["attached"][0] not in two_xdps["attached"], 937 "Offload program not reported after driver activated") 938 fail(len(ipl["xdp"]["attached"]) != 1, 939 "Wrong attached program count with remaining programs") 940 fail(offloaded != "0", "offload ID reported with only driver program left") 941 942 start_test("Test multi-attachment XDP - device remove...") 943 sim.set_xdp(obj, "offload") 944 sim.remove() 945 946 sim = NetdevSim() 947 sim.set_ethtool_tc_offloads(True) 948 949 start_test("Test mixing of TC and XDP...") 950 sim.tc_add_ingress() 951 sim.set_xdp(obj, "offload") 952 ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True, 953 fail=False, include_stderr=True) 954 fail(ret == 0, "Loading TC when XDP active should fail") 955 check_extack_nsim(err, "driver and netdev offload states mismatch.", args) 956 sim.unset_xdp("offload") 957 sim.wait_for_flush() 958 959 sim.cls_bpf_add_filter(obj, skip_sw=True) 960 ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True) 961 fail(ret == 0, "Loading XDP when TC active should fail") 962 check_extack_nsim(err, "TC program is already loaded.", args) 963 964 start_test("Test binding TC from pinned...") 965 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp") 966 sim.tc_flush_filters(bound=1, total=1) 967 sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True) 968 sim.tc_flush_filters(bound=1, total=1) 969 970 start_test("Test binding XDP from pinned...") 971 sim.set_xdp(obj, "offload") 972 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1) 973 974 sim.set_xdp(pinned, "offload", force=True) 975 sim.unset_xdp("offload") 976 sim.set_xdp(pinned, "offload", force=True) 977 sim.unset_xdp("offload") 978 979 start_test("Test offload of wrong type fails...") 980 ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False) 981 fail(ret == 0, "Managed to attach XDP program to TC") 982 983 start_test("Test asking for TC offload of two filters...") 984 sim.cls_bpf_add_filter(obj, da=True, skip_sw=True) 985 ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True, 986 fail=False, include_stderr=True) 987 fail(ret == 0, "Managed to offload two TC filters at the same time") 988 check_extack_nsim(err, "driver and netdev offload states mismatch.", args) 989 990 sim.tc_flush_filters(bound=2, total=2) 991 992 start_test("Test if netdev removal waits for translation...") 993 delay_msec = 500 994 sim.dfs["bpf_bind_verifier_delay"] = delay_msec 995 start = time.time() 996 cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \ 997 (sim['ifname'], obj) 998 tc_proc = cmd(cmd_line, background=True, fail=False) 999 # Wait for the verifier to start 1000 while sim.dfs_num_bound_progs() <= 2: 1001 pass 1002 sim.remove() 1003 end = time.time() 1004 ret, _ = cmd_result(tc_proc, fail=False) 1005 time_diff = end - start 1006 log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff)) 1007 1008 fail(ret == 0, "Managed to load TC filter on a unregistering device") 1009 delay_sec = delay_msec * 0.001 1010 fail(time_diff < delay_sec, "Removal process took %s, expected %s" % 1011 (time_diff, delay_sec)) 1012 1013 # Remove all pinned files and reinstantiate the netdev 1014 clean_up() 1015 bpftool_prog_list_wait(expected=0) 1016 1017 sim = NetdevSim() 1018 map_obj = bpf_obj("sample_map_ret0.o") 1019 start_test("Test loading program with maps...") 1020 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON 1021 1022 start_test("Test bpftool bound info reporting (own ns)...") 1023 check_dev_info(False, "") 1024 1025 start_test("Test bpftool bound info reporting (other ns)...") 1026 ns = mknetns() 1027 sim.set_ns(ns) 1028 check_dev_info(True, "") 1029 1030 start_test("Test bpftool bound info reporting (remote ns)...") 1031 check_dev_info(False, ns) 1032 1033 start_test("Test bpftool bound info reporting (back to own ns)...") 1034 sim.set_ns("") 1035 check_dev_info(False, "") 1036 1037 prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog") 1038 map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2) 1039 sim.remove() 1040 1041 start_test("Test bpftool bound info reporting (removed dev)...") 1042 check_dev_info_removed(prog_file=prog_file, map_file=map_file) 1043 1044 # Remove all pinned files and reinstantiate the netdev 1045 clean_up() 1046 bpftool_prog_list_wait(expected=0) 1047 1048 sim = NetdevSim() 1049 1050 start_test("Test map update (no flags)...") 1051 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON 1052 maps = bpftool_map_list(expected=2) 1053 array = maps[0] if maps[0]["type"] == "array" else maps[1] 1054 htab = maps[0] if maps[0]["type"] == "hash" else maps[1] 1055 for m in maps: 1056 for i in range(2): 1057 bpftool("map update id %d key %s value %s" % 1058 (m["id"], int2str("I", i), int2str("Q", i * 3))) 1059 1060 for m in maps: 1061 ret, _ = bpftool("map update id %d key %s value %s" % 1062 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)), 1063 fail=False) 1064 fail(ret == 0, "added too many entries") 1065 1066 start_test("Test map update (exists)...") 1067 for m in maps: 1068 for i in range(2): 1069 bpftool("map update id %d key %s value %s exist" % 1070 (m["id"], int2str("I", i), int2str("Q", i * 3))) 1071 1072 for m in maps: 1073 ret, err = bpftool("map update id %d key %s value %s exist" % 1074 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)), 1075 fail=False) 1076 fail(ret == 0, "updated non-existing key") 1077 fail(err["error"].find("No such file or directory") == -1, 1078 "expected ENOENT, error is '%s'" % (err["error"])) 1079 1080 start_test("Test map update (noexist)...") 1081 for m in maps: 1082 for i in range(2): 1083 ret, err = bpftool("map update id %d key %s value %s noexist" % 1084 (m["id"], int2str("I", i), int2str("Q", i * 3)), 1085 fail=False) 1086 fail(ret == 0, "updated existing key") 1087 fail(err["error"].find("File exists") == -1, 1088 "expected EEXIST, error is '%s'" % (err["error"])) 1089 1090 start_test("Test map dump...") 1091 for m in maps: 1092 _, entries = bpftool("map dump id %d" % (m["id"])) 1093 for i in range(2): 1094 key = str2int(entries[i]["key"]) 1095 fail(key != i, "expected key %d, got %d" % (key, i)) 1096 val = str2int(entries[i]["value"]) 1097 fail(val != i * 3, "expected value %d, got %d" % (val, i * 3)) 1098 1099 start_test("Test map getnext...") 1100 for m in maps: 1101 _, entry = bpftool("map getnext id %d" % (m["id"])) 1102 key = str2int(entry["next_key"]) 1103 fail(key != 0, "next key %d, expected %d" % (key, 0)) 1104 _, entry = bpftool("map getnext id %d key %s" % 1105 (m["id"], int2str("I", 0))) 1106 key = str2int(entry["next_key"]) 1107 fail(key != 1, "next key %d, expected %d" % (key, 1)) 1108 ret, err = bpftool("map getnext id %d key %s" % 1109 (m["id"], int2str("I", 1)), fail=False) 1110 fail(ret == 0, "got next key past the end of map") 1111 fail(err["error"].find("No such file or directory") == -1, 1112 "expected ENOENT, error is '%s'" % (err["error"])) 1113 1114 start_test("Test map delete (htab)...") 1115 for i in range(2): 1116 bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i))) 1117 1118 start_test("Test map delete (array)...") 1119 for i in range(2): 1120 ret, err = bpftool("map delete id %d key %s" % 1121 (htab["id"], int2str("I", i)), fail=False) 1122 fail(ret == 0, "removed entry from an array") 1123 fail(err["error"].find("No such file or directory") == -1, 1124 "expected ENOENT, error is '%s'" % (err["error"])) 1125 1126 start_test("Test map remove...") 1127 sim.unset_xdp("offload") 1128 bpftool_map_list_wait(expected=0) 1129 sim.remove() 1130 1131 sim = NetdevSim() 1132 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON 1133 sim.remove() 1134 bpftool_map_list_wait(expected=0) 1135 1136 start_test("Test map creation fail path...") 1137 sim = NetdevSim() 1138 sim.dfs["bpf_map_accept"] = "N" 1139 ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False) 1140 fail(ret == 0, 1141 "netdevsim didn't refuse to create a map with offload disabled") 1142 1143 print("%s: OK" % (os.path.basename(__file__))) 1144 1145finally: 1146 log("Clean up...", "", level=1) 1147 log_level_inc() 1148 clean_up() 1149