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_num_bound_progs(self): 343 path = os.path.join(self.dfs_dir, "bpf_bound_progs") 344 _, progs = cmd('ls %s' % (path)) 345 return len(progs.split()) 346 347 def dfs_get_bound_progs(self, expected): 348 progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs")) 349 if expected is not None: 350 if len(progs) != expected: 351 fail(True, "%d BPF programs bound, expected %d" % 352 (len(progs), expected)) 353 return progs 354 355 def wait_for_flush(self, bound=0, total=0, n_retry=20): 356 for i in range(n_retry): 357 nbound = self.dfs_num_bound_progs() 358 nprogs = len(bpftool_prog_list()) 359 if nbound == bound and nprogs == total: 360 return 361 time.sleep(0.05) 362 raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs)) 363 364 def set_ns(self, ns): 365 name = "1" if ns == "" else ns 366 ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns) 367 self.ns = ns 368 369 def set_mtu(self, mtu, fail=True): 370 return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu), 371 fail=fail) 372 373 def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False, 374 fail=True, include_stderr=False): 375 if verbose: 376 bpf += " verbose" 377 return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf), 378 force=force, JSON=JSON, 379 fail=fail, include_stderr=include_stderr) 380 381 def unset_xdp(self, mode, force=False, JSON=True, 382 fail=True, include_stderr=False): 383 return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode), 384 force=force, JSON=JSON, 385 fail=fail, include_stderr=include_stderr) 386 387 def ip_link_show(self, xdp): 388 _, link = ip("link show dev %s" % (self['ifname'])) 389 if len(link) > 1: 390 raise Exception("Multiple objects on ip link show") 391 if len(link) < 1: 392 return {} 393 fail(xdp != "xdp" in link, 394 "XDP program not reporting in iplink (reported %s, expected %s)" % 395 ("xdp" in link, xdp)) 396 return link[0] 397 398 def tc_add_ingress(self): 399 tc("qdisc add dev %s ingress" % (self['ifname'])) 400 401 def tc_del_ingress(self): 402 tc("qdisc del dev %s ingress" % (self['ifname'])) 403 404 def tc_flush_filters(self, bound=0, total=0): 405 self.tc_del_ingress() 406 self.tc_add_ingress() 407 self.wait_for_flush(bound=bound, total=total) 408 409 def tc_show_ingress(self, expected=None): 410 # No JSON support, oh well... 411 flags = ["skip_sw", "skip_hw", "in_hw"] 412 named = ["protocol", "pref", "chain", "handle", "id", "tag"] 413 414 args = "-s filter show dev %s ingress" % (self['ifname']) 415 _, out = tc(args, JSON=False) 416 417 filters = [] 418 lines = out.split('\n') 419 for line in lines: 420 words = line.split() 421 if "handle" not in words: 422 continue 423 fltr = {} 424 for flag in flags: 425 fltr[flag] = flag in words 426 for name in named: 427 try: 428 idx = words.index(name) 429 fltr[name] = words[idx + 1] 430 except ValueError: 431 pass 432 filters.append(fltr) 433 434 if expected is not None: 435 fail(len(filters) != expected, 436 "%d ingress filters loaded, expected %d" % 437 (len(filters), expected)) 438 return filters 439 440 def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None, 441 chain=None, cls="", params="", 442 fail=True, include_stderr=False): 443 spec = "" 444 if prio is not None: 445 spec += " prio %d" % (prio) 446 if handle: 447 spec += " handle %s" % (handle) 448 if chain is not None: 449 spec += " chain %d" % (chain) 450 451 return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\ 452 .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec, 453 cls=cls, params=params), 454 fail=fail, include_stderr=include_stderr) 455 456 def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None, 457 chain=None, da=False, verbose=False, 458 skip_sw=False, skip_hw=False, 459 fail=True, include_stderr=False): 460 cls = "bpf " + bpf 461 462 params = "" 463 if da: 464 params += " da" 465 if verbose: 466 params += " verbose" 467 if skip_sw: 468 params += " skip_sw" 469 if skip_hw: 470 params += " skip_hw" 471 472 return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls, 473 chain=chain, params=params, 474 fail=fail, include_stderr=include_stderr) 475 476 def set_ethtool_tc_offloads(self, enable, fail=True): 477 args = "hw-tc-offload %s" % ("on" if enable else "off") 478 return ethtool(self, "-K", args, fail=fail) 479 480################################################################################ 481def clean_up(): 482 global files, netns, devs 483 484 for dev in devs: 485 dev.remove() 486 for f in files: 487 cmd("rm -f %s" % (f)) 488 for ns in netns: 489 cmd("ip netns delete %s" % (ns)) 490 files = [] 491 netns = [] 492 493def pin_prog(file_name, idx=0): 494 progs = bpftool_prog_list(expected=(idx + 1)) 495 prog = progs[idx] 496 bpftool("prog pin id %d %s" % (prog["id"], file_name)) 497 files.append(file_name) 498 499 return file_name, bpf_pinned(file_name) 500 501def pin_map(file_name, idx=0, expected=1): 502 maps = bpftool_map_list(expected=expected) 503 m = maps[idx] 504 bpftool("map pin id %d %s" % (m["id"], file_name)) 505 files.append(file_name) 506 507 return file_name, bpf_pinned(file_name) 508 509def check_dev_info_removed(prog_file=None, map_file=None): 510 bpftool_prog_list(expected=0) 511 ret, err = bpftool("prog show pin %s" % (prog_file), fail=False) 512 fail(ret == 0, "Showing prog with removed device did not fail") 513 fail(err["error"].find("No such device") == -1, 514 "Showing prog with removed device expected ENODEV, error is %s" % 515 (err["error"])) 516 517 bpftool_map_list(expected=0) 518 ret, err = bpftool("map show pin %s" % (map_file), fail=False) 519 fail(ret == 0, "Showing map with removed device did not fail") 520 fail(err["error"].find("No such device") == -1, 521 "Showing map with removed device expected ENODEV, error is %s" % 522 (err["error"])) 523 524def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False): 525 progs = bpftool_prog_list(expected=1, ns=ns) 526 prog = progs[0] 527 528 fail("dev" not in prog.keys(), "Device parameters not reported") 529 dev = prog["dev"] 530 fail("ifindex" not in dev.keys(), "Device parameters not reported") 531 fail("ns_dev" not in dev.keys(), "Device parameters not reported") 532 fail("ns_inode" not in dev.keys(), "Device parameters not reported") 533 534 if not other_ns: 535 fail("ifname" not in dev.keys(), "Ifname not reported") 536 fail(dev["ifname"] != sim["ifname"], 537 "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"])) 538 else: 539 fail("ifname" in dev.keys(), "Ifname is reported for other ns") 540 541 maps = bpftool_map_list(expected=2, ns=ns) 542 for m in maps: 543 fail("dev" not in m.keys(), "Device parameters not reported") 544 fail(dev != m["dev"], "Map's device different than program's") 545 546def check_extack(output, reference, args): 547 if skip_extack: 548 return 549 lines = output.split("\n") 550 comp = len(lines) >= 2 and lines[1] == reference 551 fail(not comp, "Missing or incorrect netlink extack message") 552 553def check_extack_nsim(output, reference, args): 554 check_extack(output, "Error: netdevsim: " + reference, args) 555 556def check_no_extack(res, needle): 557 fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"), 558 "Found '%s' in command output, leaky extack?" % (needle)) 559 560def check_verifier_log(output, reference): 561 lines = output.split("\n") 562 for l in reversed(lines): 563 if l == reference: 564 return 565 fail(True, "Missing or incorrect message from netdevsim in verifier log") 566 567def test_spurios_extack(sim, obj, skip_hw, needle): 568 res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw, 569 include_stderr=True) 570 check_no_extack(res, needle) 571 res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, 572 skip_hw=skip_hw, include_stderr=True) 573 check_no_extack(res, needle) 574 res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf", 575 include_stderr=True) 576 check_no_extack(res, needle) 577 578 579# Parse command line 580parser = argparse.ArgumentParser() 581parser.add_argument("--log", help="output verbose log to given file") 582args = parser.parse_args() 583if args.log: 584 logfile = open(args.log, 'w+') 585 logfile.write("# -*-Org-*-") 586 587log("Prepare...", "", level=1) 588log_level_inc() 589 590# Check permissions 591skip(os.getuid() != 0, "test must be run as root") 592 593# Check tools 594ret, progs = bpftool("prog", fail=False) 595skip(ret != 0, "bpftool not installed") 596base_progs = progs 597_, base_maps = bpftool("map") 598 599# Check netdevsim 600ret, out = cmd("modprobe netdevsim", fail=False) 601skip(ret != 0, "netdevsim module could not be loaded") 602 603# Check debugfs 604_, out = cmd("mount") 605if out.find("/sys/kernel/debug type debugfs") == -1: 606 cmd("mount -t debugfs none /sys/kernel/debug") 607 608# Check samples are compiled 609samples = ["sample_ret0.o", "sample_map_ret0.o"] 610for s in samples: 611 ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False) 612 skip(ret != 0, "sample %s/%s not found, please compile it" % 613 (bpf_test_dir, s)) 614 615# Check if iproute2 is built with libmnl (needed by extack support) 616_, _, err = cmd("tc qdisc delete dev lo handle 0", 617 fail=False, include_stderr=True) 618if err.find("Error: Failed to find qdisc with specified handle.") == -1: 619 print("Warning: no extack message in iproute2 output, libmnl missing?") 620 log("Warning: no extack message in iproute2 output, libmnl missing?", "") 621 skip_extack = True 622 623# Check if net namespaces seem to work 624ns = mknetns() 625skip(ns is None, "Could not create a net namespace") 626cmd("ip netns delete %s" % (ns)) 627netns = [] 628 629try: 630 obj = bpf_obj("sample_ret0.o") 631 bytecode = bpf_bytecode("1,6 0 0 4294967295,") 632 633 start_test("Test destruction of generic XDP...") 634 sim = NetdevSim() 635 sim.set_xdp(obj, "generic") 636 sim.remove() 637 bpftool_prog_list_wait(expected=0) 638 639 sim = NetdevSim() 640 sim.tc_add_ingress() 641 642 start_test("Test TC non-offloaded...") 643 ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False) 644 fail(ret != 0, "Software TC filter did not load") 645 646 start_test("Test TC non-offloaded isn't getting bound...") 647 ret, _ = sim.cls_bpf_add_filter(obj, fail=False) 648 fail(ret != 0, "Software TC filter did not load") 649 sim.dfs_get_bound_progs(expected=0) 650 651 sim.tc_flush_filters() 652 653 start_test("Test TC offloads are off by default...") 654 ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True, 655 fail=False, include_stderr=True) 656 fail(ret == 0, "TC filter loaded without enabling TC offloads") 657 check_extack(err, "Error: TC offload is disabled on net device.", args) 658 sim.wait_for_flush() 659 660 sim.set_ethtool_tc_offloads(True) 661 sim.dfs["bpf_tc_non_bound_accept"] = "Y" 662 663 start_test("Test TC offload by default...") 664 ret, _ = sim.cls_bpf_add_filter(obj, fail=False) 665 fail(ret != 0, "Software TC filter did not load") 666 sim.dfs_get_bound_progs(expected=0) 667 ingress = sim.tc_show_ingress(expected=1) 668 fltr = ingress[0] 669 fail(not fltr["in_hw"], "Filter not offloaded by default") 670 671 sim.tc_flush_filters() 672 673 start_test("Test TC cBPF bytcode tries offload by default...") 674 ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False) 675 fail(ret != 0, "Software TC filter did not load") 676 sim.dfs_get_bound_progs(expected=0) 677 ingress = sim.tc_show_ingress(expected=1) 678 fltr = ingress[0] 679 fail(not fltr["in_hw"], "Bytecode not offloaded by default") 680 681 sim.tc_flush_filters() 682 sim.dfs["bpf_tc_non_bound_accept"] = "N" 683 684 start_test("Test TC cBPF unbound bytecode doesn't offload...") 685 ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True, 686 fail=False, include_stderr=True) 687 fail(ret == 0, "TC bytecode loaded for offload") 688 check_extack_nsim(err, "netdevsim configured to reject unbound programs.", 689 args) 690 sim.wait_for_flush() 691 692 start_test("Test non-0 chain offload...") 693 ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1, 694 skip_sw=True, 695 fail=False, include_stderr=True) 696 fail(ret == 0, "Offloaded a filter to chain other than 0") 697 check_extack(err, "Error: Driver supports only offload of chain 0.", args) 698 sim.tc_flush_filters() 699 700 start_test("Test TC replace...") 701 sim.cls_bpf_add_filter(obj, prio=1, handle=1) 702 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1) 703 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 704 705 sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True) 706 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True) 707 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 708 709 sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True) 710 sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True) 711 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 712 713 start_test("Test TC replace bad flags...") 714 for i in range(3): 715 for j in range(3): 716 ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, 717 skip_sw=(j == 1), skip_hw=(j == 2), 718 fail=False) 719 fail(bool(ret) != bool(j), 720 "Software TC incorrect load in replace test, iteration %d" % 721 (j)) 722 sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf") 723 724 start_test("Test spurious extack from the driver...") 725 test_spurios_extack(sim, obj, False, "netdevsim") 726 test_spurios_extack(sim, obj, True, "netdevsim") 727 728 sim.set_ethtool_tc_offloads(False) 729 730 test_spurios_extack(sim, obj, False, "TC offload is disabled") 731 test_spurios_extack(sim, obj, True, "TC offload is disabled") 732 733 sim.set_ethtool_tc_offloads(True) 734 735 sim.tc_flush_filters() 736 737 start_test("Test TC offloads work...") 738 ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True, 739 fail=False, include_stderr=True) 740 fail(ret != 0, "TC filter did not load with TC offloads enabled") 741 check_verifier_log(err, "[netdevsim] Hello from netdevsim!") 742 743 start_test("Test TC offload basics...") 744 dfs = sim.dfs_get_bound_progs(expected=1) 745 progs = bpftool_prog_list(expected=1) 746 ingress = sim.tc_show_ingress(expected=1) 747 748 dprog = dfs[0] 749 prog = progs[0] 750 fltr = ingress[0] 751 fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter") 752 fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter") 753 fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back") 754 755 start_test("Test TC offload is device-bound...") 756 fail(str(prog["id"]) != fltr["id"], "Program IDs don't match") 757 fail(prog["tag"] != fltr["tag"], "Program tags don't match") 758 fail(fltr["id"] != dprog["id"], "Program IDs don't match") 759 fail(dprog["state"] != "xlated", "Offloaded program state not translated") 760 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded") 761 762 start_test("Test disabling TC offloads is rejected while filters installed...") 763 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False) 764 fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...") 765 766 start_test("Test qdisc removal frees things...") 767 sim.tc_flush_filters() 768 sim.tc_show_ingress(expected=0) 769 770 start_test("Test disabling TC offloads is OK without filters...") 771 ret, _ = sim.set_ethtool_tc_offloads(False, fail=False) 772 fail(ret != 0, 773 "Driver refused to disable TC offloads without filters installed...") 774 775 sim.set_ethtool_tc_offloads(True) 776 777 start_test("Test destroying device gets rid of TC filters...") 778 sim.cls_bpf_add_filter(obj, skip_sw=True) 779 sim.remove() 780 bpftool_prog_list_wait(expected=0) 781 782 sim = NetdevSim() 783 sim.set_ethtool_tc_offloads(True) 784 785 start_test("Test destroying device gets rid of XDP...") 786 sim.set_xdp(obj, "offload") 787 sim.remove() 788 bpftool_prog_list_wait(expected=0) 789 790 sim = NetdevSim() 791 sim.set_ethtool_tc_offloads(True) 792 793 start_test("Test XDP prog reporting...") 794 sim.set_xdp(obj, "drv") 795 ipl = sim.ip_link_show(xdp=True) 796 progs = bpftool_prog_list(expected=1) 797 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], 798 "Loaded program has wrong ID") 799 800 start_test("Test XDP prog replace without force...") 801 ret, _ = sim.set_xdp(obj, "drv", fail=False) 802 fail(ret == 0, "Replaced XDP program without -force") 803 sim.wait_for_flush(total=1) 804 805 start_test("Test XDP prog replace with force...") 806 ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False) 807 fail(ret != 0, "Could not replace XDP program with -force") 808 bpftool_prog_list_wait(expected=1) 809 ipl = sim.ip_link_show(xdp=True) 810 progs = bpftool_prog_list(expected=1) 811 fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"], 812 "Loaded program has wrong ID") 813 fail("dev" in progs[0].keys(), 814 "Device parameters reported for non-offloaded program") 815 816 start_test("Test XDP prog replace with bad flags...") 817 ret, _, err = sim.set_xdp(obj, "offload", force=True, 818 fail=False, include_stderr=True) 819 fail(ret == 0, "Replaced XDP program with a program in different mode") 820 check_extack_nsim(err, "program loaded with different flags.", args) 821 ret, _, err = sim.set_xdp(obj, "", force=True, 822 fail=False, include_stderr=True) 823 fail(ret == 0, "Replaced XDP program with a program in different mode") 824 check_extack_nsim(err, "program loaded with different flags.", args) 825 826 start_test("Test XDP prog remove with bad flags...") 827 ret, _, err = sim.unset_xdp("offload", force=True, 828 fail=False, include_stderr=True) 829 fail(ret == 0, "Removed program with a bad mode mode") 830 check_extack_nsim(err, "program loaded with different flags.", args) 831 ret, _, err = sim.unset_xdp("", force=True, 832 fail=False, include_stderr=True) 833 fail(ret == 0, "Removed program with a bad mode mode") 834 check_extack_nsim(err, "program loaded with different flags.", args) 835 836 start_test("Test MTU restrictions...") 837 ret, _ = sim.set_mtu(9000, fail=False) 838 fail(ret == 0, 839 "Driver should refuse to increase MTU to 9000 with XDP loaded...") 840 sim.unset_xdp("drv") 841 bpftool_prog_list_wait(expected=0) 842 sim.set_mtu(9000) 843 ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True) 844 fail(ret == 0, "Driver should refuse to load program with MTU of 9000...") 845 check_extack_nsim(err, "MTU too large w/ XDP enabled.", args) 846 sim.set_mtu(1500) 847 848 sim.wait_for_flush() 849 start_test("Test XDP offload...") 850 _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True) 851 ipl = sim.ip_link_show(xdp=True) 852 link_xdp = ipl["xdp"]["prog"] 853 progs = bpftool_prog_list(expected=1) 854 prog = progs[0] 855 fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID") 856 check_verifier_log(err, "[netdevsim] Hello from netdevsim!") 857 858 start_test("Test XDP offload is device bound...") 859 dfs = sim.dfs_get_bound_progs(expected=1) 860 dprog = dfs[0] 861 862 fail(prog["id"] != link_xdp["id"], "Program IDs don't match") 863 fail(prog["tag"] != link_xdp["tag"], "Program tags don't match") 864 fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match") 865 fail(dprog["state"] != "xlated", "Offloaded program state not translated") 866 fail(dprog["loaded"] != "Y", "Offloaded program is not loaded") 867 868 start_test("Test removing XDP program many times...") 869 sim.unset_xdp("offload") 870 sim.unset_xdp("offload") 871 sim.unset_xdp("drv") 872 sim.unset_xdp("drv") 873 sim.unset_xdp("") 874 sim.unset_xdp("") 875 bpftool_prog_list_wait(expected=0) 876 877 start_test("Test attempt to use a program for a wrong device...") 878 sim2 = NetdevSim() 879 sim2.set_xdp(obj, "offload") 880 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp") 881 882 ret, _, err = sim.set_xdp(pinned, "offload", 883 fail=False, include_stderr=True) 884 fail(ret == 0, "Pinned program loaded for a different device accepted") 885 check_extack_nsim(err, "program bound to different dev.", args) 886 sim2.remove() 887 ret, _, err = sim.set_xdp(pinned, "offload", 888 fail=False, include_stderr=True) 889 fail(ret == 0, "Pinned program loaded for a removed device accepted") 890 check_extack_nsim(err, "xdpoffload of non-bound program.", args) 891 rm(pin_file) 892 bpftool_prog_list_wait(expected=0) 893 894 start_test("Test mixing of TC and XDP...") 895 sim.tc_add_ingress() 896 sim.set_xdp(obj, "offload") 897 ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True, 898 fail=False, include_stderr=True) 899 fail(ret == 0, "Loading TC when XDP active should fail") 900 check_extack_nsim(err, "driver and netdev offload states mismatch.", args) 901 sim.unset_xdp("offload") 902 sim.wait_for_flush() 903 904 sim.cls_bpf_add_filter(obj, skip_sw=True) 905 ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True) 906 fail(ret == 0, "Loading XDP when TC active should fail") 907 check_extack_nsim(err, "TC program is already loaded.", args) 908 909 start_test("Test binding TC from pinned...") 910 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp") 911 sim.tc_flush_filters(bound=1, total=1) 912 sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True) 913 sim.tc_flush_filters(bound=1, total=1) 914 915 start_test("Test binding XDP from pinned...") 916 sim.set_xdp(obj, "offload") 917 pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1) 918 919 sim.set_xdp(pinned, "offload", force=True) 920 sim.unset_xdp("offload") 921 sim.set_xdp(pinned, "offload", force=True) 922 sim.unset_xdp("offload") 923 924 start_test("Test offload of wrong type fails...") 925 ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False) 926 fail(ret == 0, "Managed to attach XDP program to TC") 927 928 start_test("Test asking for TC offload of two filters...") 929 sim.cls_bpf_add_filter(obj, da=True, skip_sw=True) 930 ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True, 931 fail=False, include_stderr=True) 932 fail(ret == 0, "Managed to offload two TC filters at the same time") 933 check_extack_nsim(err, "driver and netdev offload states mismatch.", args) 934 935 sim.tc_flush_filters(bound=2, total=2) 936 937 start_test("Test if netdev removal waits for translation...") 938 delay_msec = 500 939 sim.dfs["bpf_bind_verifier_delay"] = delay_msec 940 start = time.time() 941 cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \ 942 (sim['ifname'], obj) 943 tc_proc = cmd(cmd_line, background=True, fail=False) 944 # Wait for the verifier to start 945 while sim.dfs_num_bound_progs() <= 2: 946 pass 947 sim.remove() 948 end = time.time() 949 ret, _ = cmd_result(tc_proc, fail=False) 950 time_diff = end - start 951 log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff)) 952 953 fail(ret == 0, "Managed to load TC filter on a unregistering device") 954 delay_sec = delay_msec * 0.001 955 fail(time_diff < delay_sec, "Removal process took %s, expected %s" % 956 (time_diff, delay_sec)) 957 958 # Remove all pinned files and reinstantiate the netdev 959 clean_up() 960 bpftool_prog_list_wait(expected=0) 961 962 sim = NetdevSim() 963 map_obj = bpf_obj("sample_map_ret0.o") 964 start_test("Test loading program with maps...") 965 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON 966 967 start_test("Test bpftool bound info reporting (own ns)...") 968 check_dev_info(False, "") 969 970 start_test("Test bpftool bound info reporting (other ns)...") 971 ns = mknetns() 972 sim.set_ns(ns) 973 check_dev_info(True, "") 974 975 start_test("Test bpftool bound info reporting (remote ns)...") 976 check_dev_info(False, ns) 977 978 start_test("Test bpftool bound info reporting (back to own ns)...") 979 sim.set_ns("") 980 check_dev_info(False, "") 981 982 prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog") 983 map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2) 984 sim.remove() 985 986 start_test("Test bpftool bound info reporting (removed dev)...") 987 check_dev_info_removed(prog_file=prog_file, map_file=map_file) 988 989 # Remove all pinned files and reinstantiate the netdev 990 clean_up() 991 bpftool_prog_list_wait(expected=0) 992 993 sim = NetdevSim() 994 995 start_test("Test map update (no flags)...") 996 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON 997 maps = bpftool_map_list(expected=2) 998 array = maps[0] if maps[0]["type"] == "array" else maps[1] 999 htab = maps[0] if maps[0]["type"] == "hash" else maps[1] 1000 for m in maps: 1001 for i in range(2): 1002 bpftool("map update id %d key %s value %s" % 1003 (m["id"], int2str("I", i), int2str("Q", i * 3))) 1004 1005 for m in maps: 1006 ret, _ = bpftool("map update id %d key %s value %s" % 1007 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)), 1008 fail=False) 1009 fail(ret == 0, "added too many entries") 1010 1011 start_test("Test map update (exists)...") 1012 for m in maps: 1013 for i in range(2): 1014 bpftool("map update id %d key %s value %s exist" % 1015 (m["id"], int2str("I", i), int2str("Q", i * 3))) 1016 1017 for m in maps: 1018 ret, err = bpftool("map update id %d key %s value %s exist" % 1019 (m["id"], int2str("I", 3), int2str("Q", 3 * 3)), 1020 fail=False) 1021 fail(ret == 0, "updated non-existing key") 1022 fail(err["error"].find("No such file or directory") == -1, 1023 "expected ENOENT, error is '%s'" % (err["error"])) 1024 1025 start_test("Test map update (noexist)...") 1026 for m in maps: 1027 for i in range(2): 1028 ret, err = bpftool("map update id %d key %s value %s noexist" % 1029 (m["id"], int2str("I", i), int2str("Q", i * 3)), 1030 fail=False) 1031 fail(ret == 0, "updated existing key") 1032 fail(err["error"].find("File exists") == -1, 1033 "expected EEXIST, error is '%s'" % (err["error"])) 1034 1035 start_test("Test map dump...") 1036 for m in maps: 1037 _, entries = bpftool("map dump id %d" % (m["id"])) 1038 for i in range(2): 1039 key = str2int(entries[i]["key"]) 1040 fail(key != i, "expected key %d, got %d" % (key, i)) 1041 val = str2int(entries[i]["value"]) 1042 fail(val != i * 3, "expected value %d, got %d" % (val, i * 3)) 1043 1044 start_test("Test map getnext...") 1045 for m in maps: 1046 _, entry = bpftool("map getnext id %d" % (m["id"])) 1047 key = str2int(entry["next_key"]) 1048 fail(key != 0, "next key %d, expected %d" % (key, 0)) 1049 _, entry = bpftool("map getnext id %d key %s" % 1050 (m["id"], int2str("I", 0))) 1051 key = str2int(entry["next_key"]) 1052 fail(key != 1, "next key %d, expected %d" % (key, 1)) 1053 ret, err = bpftool("map getnext id %d key %s" % 1054 (m["id"], int2str("I", 1)), fail=False) 1055 fail(ret == 0, "got next key past the end of map") 1056 fail(err["error"].find("No such file or directory") == -1, 1057 "expected ENOENT, error is '%s'" % (err["error"])) 1058 1059 start_test("Test map delete (htab)...") 1060 for i in range(2): 1061 bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i))) 1062 1063 start_test("Test map delete (array)...") 1064 for i in range(2): 1065 ret, err = bpftool("map delete id %d key %s" % 1066 (htab["id"], int2str("I", i)), fail=False) 1067 fail(ret == 0, "removed entry from an array") 1068 fail(err["error"].find("No such file or directory") == -1, 1069 "expected ENOENT, error is '%s'" % (err["error"])) 1070 1071 start_test("Test map remove...") 1072 sim.unset_xdp("offload") 1073 bpftool_map_list_wait(expected=0) 1074 sim.remove() 1075 1076 sim = NetdevSim() 1077 sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON 1078 sim.remove() 1079 bpftool_map_list_wait(expected=0) 1080 1081 start_test("Test map creation fail path...") 1082 sim = NetdevSim() 1083 sim.dfs["bpf_map_accept"] = "N" 1084 ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False) 1085 fail(ret == 0, 1086 "netdevsim didn't refuse to create a map with offload disabled") 1087 1088 print("%s: OK" % (os.path.basename(__file__))) 1089 1090finally: 1091 log("Clean up...", "", level=1) 1092 log_level_inc() 1093 clean_up() 1094