test_offload.py 38.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#!/usr/bin/python3

# Copyright (C) 2017 Netronome Systems, Inc.
#
# This software is licensed under the GNU General License Version 2,
# June 1991 as shown in the file COPYING in the top-level directory of this
# source tree.
#
# THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
# WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
# THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

from datetime import datetime
import argparse
import json
import os
import pprint
21 22
import random
import string
23
import struct
24 25 26 27 28
import subprocess
import time

logfile = None
log_level = 1
29
skip_extack = False
30 31 32 33
bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
pp = pprint.PrettyPrinter()
devs = [] # devices we created for clean up
files = [] # files to be removed
34
netns = [] # net namespaces to be removed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

def log_get_sec(level=0):
    return "*" * (log_level + level)

def log_level_inc(add=1):
    global log_level
    log_level += add

def log_level_dec(sub=1):
    global log_level
    log_level -= sub

def log_level_set(level):
    global log_level
    log_level = level

def log(header, data, level=None):
    """
    Output to an optional log.
    """
    if logfile is None:
        return
    if level is not None:
        log_level_set(level)

    if not isinstance(data, str):
        data = pp.pformat(data)

    if len(header):
        logfile.write("\n" + log_get_sec() + " ")
        logfile.write(header)
    if len(header) and len(data.strip()):
        logfile.write("\n")
    logfile.write(data)

def skip(cond, msg):
    if not cond:
        return
    print("SKIP: " + msg)
    log("SKIP: " + msg, "", level=1)
    os.sys.exit(0)

def fail(cond, msg):
    if not cond:
        return
    print("FAIL: " + msg)
    log("FAIL: " + msg, "", level=1)
    os.sys.exit(1)

def start_test(msg):
    log(msg, "", level=1)
    log_level_inc()
    print(msg)

def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
    """
    Run a command in subprocess and return tuple of (retval, stdout);
    optionally return stderr as well as third value.
    """
    proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    if background:
        msg = "%s START: %s" % (log_get_sec(1),
                                datetime.now().strftime("%H:%M:%S.%f"))
        log("BKG " + proc.args, msg)
        return proc

    return cmd_result(proc, include_stderr=include_stderr, fail=fail)

def cmd_result(proc, include_stderr=False, fail=False):
    stdout, stderr = proc.communicate()
    stdout = stdout.decode("utf-8")
    stderr = stderr.decode("utf-8")
    proc.stdout.close()
    proc.stderr.close()

    stderr = "\n" + stderr
    if stderr[-1] == "\n":
        stderr = stderr[:-1]

    sec = log_get_sec(1)
    log("CMD " + proc.args,
        "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
        (proc.returncode, sec, stdout, sec, stderr,
         sec, datetime.now().strftime("%H:%M:%S.%f")))

    if proc.returncode != 0 and fail:
        if len(stderr) > 0 and stderr[-1] == "\n":
            stderr = stderr[:-1]
        raise Exception("Command failed: %s\n%s" % (proc.args, stderr))

    if include_stderr:
        return proc.returncode, stdout, stderr
    else:
        return proc.returncode, stdout

def rm(f):
    cmd("rm -f %s" % (f))
    if f in files:
        files.remove(f)

136
def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False):
137 138 139 140
    params = ""
    if JSON:
        params += "%s " % (flags["json"])

141 142 143
    if ns != "":
        ns = "ip netns exec %s " % (ns)

144 145 146 147 148 149 150 151 152 153 154 155 156 157
    if include_stderr:
        ret, stdout, stderr = cmd(ns + name + " " + params + args,
                                  fail=fail, include_stderr=True)
    else:
        ret, stdout = cmd(ns + name + " " + params + args,
                          fail=fail, include_stderr=False)

    if JSON and len(stdout.strip()) != 0:
        out = json.loads(stdout)
    else:
        out = stdout

    if include_stderr:
        return ret, out, stderr
158 159 160
    else:
        return ret, out

161 162
def bpftool(args, JSON=True, ns="", fail=True):
    return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail)
163

164 165
def bpftool_prog_list(expected=None, ns=""):
    _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
166 167 168 169
    # Remove the base progs
    for p in base_progs:
        if p in progs:
            progs.remove(p)
170 171 172 173 174 175
    if expected is not None:
        if len(progs) != expected:
            fail(True, "%d BPF programs loaded, expected %d" %
                 (len(progs), expected))
    return progs

176 177
def bpftool_map_list(expected=None, ns=""):
    _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
178 179 180 181
    # Remove the base maps
    for m in base_maps:
        if m in maps:
            maps.remove(m)
182 183 184 185 186 187
    if expected is not None:
        if len(maps) != expected:
            fail(True, "%d BPF maps loaded, expected %d" %
                 (len(maps), expected))
    return maps

188 189 190 191 192 193 194 195
def bpftool_prog_list_wait(expected=0, n_retry=20):
    for i in range(n_retry):
        nprogs = len(bpftool_prog_list())
        if nprogs == expected:
            return
        time.sleep(0.05)
    raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))

196 197 198 199 200 201 202 203
def bpftool_map_list_wait(expected=0, n_retry=20):
    for i in range(n_retry):
        nmaps = len(bpftool_map_list())
        if nmaps == expected:
            return
        time.sleep(0.05)
    raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))

204
def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False):
205 206
    if force:
        args = "-force " + args
207 208
    return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns,
                fail=fail, include_stderr=include_stderr)
209

210 211 212
def tc(args, JSON=True, ns="", fail=True, include_stderr=False):
    return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns,
                fail=fail, include_stderr=include_stderr)
213 214 215 216 217 218 219 220 221 222 223 224 225

def ethtool(dev, opt, args, fail=True):
    return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)

def bpf_obj(name, sec=".text", path=bpf_test_dir,):
    return "obj %s sec %s" % (os.path.join(path, name), sec)

def bpf_pinned(name):
    return "pinned %s" % (name)

def bpf_bytecode(bytecode):
    return "bytecode \"%s\"" % (bytecode)

226 227 228 229 230 231 232 233 234
def mknetns(n_retry=10):
    for i in range(n_retry):
        name = ''.join([random.choice(string.ascii_letters) for i in range(8)])
        ret, _ = ip("netns add %s" % (name), fail=False)
        if ret == 0:
            netns.append(name)
            return name
    return None

235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
def int2str(fmt, val):
    ret = []
    for b in struct.pack(fmt, val):
        ret.append(int(b))
    return " ".join(map(lambda x: str(x), ret))

def str2int(strtab):
    inttab = []
    for i in strtab:
        inttab.append(int(i, 16))
    ba = bytearray(inttab)
    if len(strtab) == 4:
        fmt = "I"
    elif len(strtab) == 8:
        fmt = "Q"
    else:
        raise Exception("String array of len %d can't be unpacked to an int" %
                        (len(strtab)))
    return struct.unpack(fmt, ba)[0]

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
class DebugfsDir:
    """
    Class for accessing DebugFS directories as a dictionary.
    """

    def __init__(self, path):
        self.path = path
        self._dict = self._debugfs_dir_read(path)

    def __len__(self):
        return len(self._dict.keys())

    def __getitem__(self, key):
        if type(key) is int:
            key = list(self._dict.keys())[key]
        return self._dict[key]

    def __setitem__(self, key, value):
        log("DebugFS set %s = %s" % (key, value), "")
        log_level_inc()

        cmd("echo '%s' > %s/%s" % (value, self.path, key))
        log_level_dec()

        _, out = cmd('cat %s/%s' % (self.path, key))
        self._dict[key] = out.strip()

    def _debugfs_dir_read(self, path):
        dfs = {}

        log("DebugFS state for %s" % (path), "")
        log_level_inc(add=2)

        _, out = cmd('ls ' + path)
        for f in out.split():
            p = os.path.join(path, f)
            if os.path.isfile(p):
                _, out = cmd('cat %s/%s' % (path, f))
                dfs[f] = out.strip()
            elif os.path.isdir(p):
                dfs[f] = DebugfsDir(p)
            else:
                raise Exception("%s is neither file nor directory" % (p))

        log_level_dec()
        log("DebugFS state", dfs)
        log_level_dec()

        return dfs

class NetdevSim:
    """
    Class for netdevsim netdevice and its attributes.
    """

    def __init__(self):
        self.dev = self._netdevsim_create()
        devs.append(self)

314 315
        self.ns = ""

316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
        self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname'])
        self.dfs_refresh()

    def __getitem__(self, key):
        return self.dev[key]

    def _netdevsim_create(self):
        _, old  = ip("link show")
        ip("link add sim%d type netdevsim")
        _, new  = ip("link show")

        for dev in new:
            f = filter(lambda x: x["ifname"] == dev["ifname"], old)
            if len(list(f)) == 0:
                return dev

        raise Exception("failed to create netdevsim device")

    def remove(self):
        devs.remove(self)
336
        ip("link del dev %s" % (self.dev["ifname"]), ns=self.ns)
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363

    def dfs_refresh(self):
        self.dfs = DebugfsDir(self.dfs_dir)
        return self.dfs

    def dfs_num_bound_progs(self):
        path = os.path.join(self.dfs_dir, "bpf_bound_progs")
        _, progs = cmd('ls %s' % (path))
        return len(progs.split())

    def dfs_get_bound_progs(self, expected):
        progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
        if expected is not None:
            if len(progs) != expected:
                fail(True, "%d BPF programs bound, expected %d" %
                     (len(progs), expected))
        return progs

    def wait_for_flush(self, bound=0, total=0, n_retry=20):
        for i in range(n_retry):
            nbound = self.dfs_num_bound_progs()
            nprogs = len(bpftool_prog_list())
            if nbound == bound and nprogs == total:
                return
            time.sleep(0.05)
        raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))

364 365 366 367 368
    def set_ns(self, ns):
        name = "1" if ns == "" else ns
        ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
        self.ns = ns

369 370 371 372
    def set_mtu(self, mtu, fail=True):
        return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
                  fail=fail)

373
    def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
374
                fail=True, include_stderr=False):
375 376
        if verbose:
            bpf += " verbose"
377
        return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
378 379
                  force=force, JSON=JSON,
                  fail=fail, include_stderr=include_stderr)
380

381 382
    def unset_xdp(self, mode, force=False, JSON=True,
                  fail=True, include_stderr=False):
383
        return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
384 385
                  force=force, JSON=JSON,
                  fail=fail, include_stderr=include_stderr)
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439

    def ip_link_show(self, xdp):
        _, link = ip("link show dev %s" % (self['ifname']))
        if len(link) > 1:
            raise Exception("Multiple objects on ip link show")
        if len(link) < 1:
            return {}
        fail(xdp != "xdp" in link,
             "XDP program not reporting in iplink (reported %s, expected %s)" %
             ("xdp" in link, xdp))
        return link[0]

    def tc_add_ingress(self):
        tc("qdisc add dev %s ingress" % (self['ifname']))

    def tc_del_ingress(self):
        tc("qdisc del dev %s ingress" % (self['ifname']))

    def tc_flush_filters(self, bound=0, total=0):
        self.tc_del_ingress()
        self.tc_add_ingress()
        self.wait_for_flush(bound=bound, total=total)

    def tc_show_ingress(self, expected=None):
        # No JSON support, oh well...
        flags = ["skip_sw", "skip_hw", "in_hw"]
        named = ["protocol", "pref", "chain", "handle", "id", "tag"]

        args = "-s filter show dev %s ingress" % (self['ifname'])
        _, out = tc(args, JSON=False)

        filters = []
        lines = out.split('\n')
        for line in lines:
            words = line.split()
            if "handle" not in words:
                continue
            fltr = {}
            for flag in flags:
                fltr[flag] = flag in words
            for name in named:
                try:
                    idx = words.index(name)
                    fltr[name] = words[idx + 1]
                except ValueError:
                    pass
            filters.append(fltr)

        if expected is not None:
            fail(len(filters) != expected,
                 "%d ingress filters loaded, expected %d" %
                 (len(filters), expected))
        return filters

440
    def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None,
441
                      chain=None, cls="", params="",
442 443 444 445 446 447
                      fail=True, include_stderr=False):
        spec = ""
        if prio is not None:
            spec += " prio %d" % (prio)
        if handle:
            spec += " handle %s" % (handle)
448 449
        if chain is not None:
            spec += " chain %d" % (chain)
450 451 452 453 454 455 456

        return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\
                  .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec,
                          cls=cls, params=params),
                  fail=fail, include_stderr=include_stderr)

    def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None,
457
                           chain=None, da=False, verbose=False,
458 459 460 461
                           skip_sw=False, skip_hw=False,
                           fail=True, include_stderr=False):
        cls = "bpf " + bpf

462 463 464
        params = ""
        if da:
            params += " da"
465 466
        if verbose:
            params += " verbose"
467 468 469 470
        if skip_sw:
            params += " skip_sw"
        if skip_hw:
            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 477 478 479 480 481

    def set_ethtool_tc_offloads(self, enable, fail=True):
        args = "hw-tc-offload %s" % ("on" if enable else "off")
        return ethtool(self, "-K", args, fail=fail)

################################################################################
def clean_up():
482 483
    global files, netns, devs

484 485 486 487
    for dev in devs:
        dev.remove()
    for f in files:
        cmd("rm -f %s" % (f))
488 489
    for ns in netns:
        cmd("ip netns delete %s" % (ns))
490 491
    files = []
    netns = []
492 493 494 495 496 497 498 499 500

def pin_prog(file_name, idx=0):
    progs = bpftool_prog_list(expected=(idx + 1))
    prog = progs[idx]
    bpftool("prog pin id %d %s" % (prog["id"], file_name))
    files.append(file_name)

    return file_name, bpf_pinned(file_name)

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
def pin_map(file_name, idx=0, expected=1):
    maps = bpftool_map_list(expected=expected)
    m = maps[idx]
    bpftool("map pin id %d %s" % (m["id"], file_name))
    files.append(file_name)

    return file_name, bpf_pinned(file_name)

def check_dev_info_removed(prog_file=None, map_file=None):
    bpftool_prog_list(expected=0)
    ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
    fail(ret == 0, "Showing prog with removed device did not fail")
    fail(err["error"].find("No such device") == -1,
         "Showing prog with removed device expected ENODEV, error is %s" %
         (err["error"]))

    bpftool_map_list(expected=0)
    ret, err = bpftool("map show pin %s" % (map_file), fail=False)
    fail(ret == 0, "Showing map with removed device did not fail")
    fail(err["error"].find("No such device") == -1,
         "Showing map with removed device expected ENODEV, error is %s" %
         (err["error"]))

def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
    progs = bpftool_prog_list(expected=1, ns=ns)
526 527 528 529 530 531 532 533
    prog = progs[0]

    fail("dev" not in prog.keys(), "Device parameters not reported")
    dev = prog["dev"]
    fail("ifindex" not in dev.keys(), "Device parameters not reported")
    fail("ns_dev" not in dev.keys(), "Device parameters not reported")
    fail("ns_inode" not in dev.keys(), "Device parameters not reported")

534
    if not other_ns:
535 536 537 538 539
        fail("ifname" not in dev.keys(), "Ifname not reported")
        fail(dev["ifname"] != sim["ifname"],
             "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
    else:
        fail("ifname" in dev.keys(), "Ifname is reported for other ns")
540 541 542 543 544

    maps = bpftool_map_list(expected=2, ns=ns)
    for m in maps:
        fail("dev" not in m.keys(), "Device parameters not reported")
        fail(dev != m["dev"], "Map's device different than program's")
545

546 547 548 549
def check_extack(output, reference, args):
    if skip_extack:
        return
    lines = output.split("\n")
550
    comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference
551 552 553
    fail(not comp, "Missing or incorrect netlink extack message")

def check_extack_nsim(output, reference, args):
554
    check_extack(output, "netdevsim: " + reference, args)
555

556 557 558 559
def check_no_extack(res, needle):
    fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"),
         "Found '%s' in command output, leaky extack?" % (needle))

560 561 562 563 564 565 566
def check_verifier_log(output, reference):
    lines = output.split("\n")
    for l in reversed(lines):
        if l == reference:
            return
    fail(True, "Missing or incorrect message from netdevsim in verifier log")

567 568 569 570 571 572 573 574 575 576 577 578
def test_spurios_extack(sim, obj, skip_hw, needle):
    res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw,
                                 include_stderr=True)
    check_no_extack(res, needle)
    res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
                                 skip_hw=skip_hw, include_stderr=True)
    check_no_extack(res, needle)
    res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf",
                            include_stderr=True)
    check_no_extack(res, needle)


579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
# Parse command line
parser = argparse.ArgumentParser()
parser.add_argument("--log", help="output verbose log to given file")
args = parser.parse_args()
if args.log:
    logfile = open(args.log, 'w+')
    logfile.write("# -*-Org-*-")

log("Prepare...", "", level=1)
log_level_inc()

# Check permissions
skip(os.getuid() != 0, "test must be run as root")

# Check tools
ret, progs = bpftool("prog", fail=False)
skip(ret != 0, "bpftool not installed")
596 597
base_progs = progs
_, base_maps = bpftool("map")
598 599 600 601 602 603 604 605 606 607 608

# Check netdevsim
ret, out = cmd("modprobe netdevsim", fail=False)
skip(ret != 0, "netdevsim module could not be loaded")

# Check debugfs
_, out = cmd("mount")
if out.find("/sys/kernel/debug type debugfs") == -1:
    cmd("mount -t debugfs none /sys/kernel/debug")

# Check samples are compiled
609
samples = ["sample_ret0.o", "sample_map_ret0.o"]
610 611 612 613 614
for s in samples:
    ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
    skip(ret != 0, "sample %s/%s not found, please compile it" %
         (bpf_test_dir, s))

615 616 617 618 619 620 621 622
# Check if iproute2 is built with libmnl (needed by extack support)
_, _, err = cmd("tc qdisc delete dev lo handle 0",
                fail=False, include_stderr=True)
if err.find("Error: Failed to find qdisc with specified handle.") == -1:
    print("Warning: no extack message in iproute2 output, libmnl missing?")
    log("Warning: no extack message in iproute2 output, libmnl missing?", "")
    skip_extack = True

623 624 625 626 627 628
# Check if net namespaces seem to work
ns = mknetns()
skip(ns is None, "Could not create a net namespace")
cmd("ip netns delete %s" % (ns))
netns = []

629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
try:
    obj = bpf_obj("sample_ret0.o")
    bytecode = bpf_bytecode("1,6 0 0 4294967295,")

    start_test("Test destruction of generic XDP...")
    sim = NetdevSim()
    sim.set_xdp(obj, "generic")
    sim.remove()
    bpftool_prog_list_wait(expected=0)

    sim = NetdevSim()
    sim.tc_add_ingress()

    start_test("Test TC non-offloaded...")
    ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
    fail(ret != 0, "Software TC filter did not load")

    start_test("Test TC non-offloaded isn't getting bound...")
    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
    fail(ret != 0, "Software TC filter did not load")
    sim.dfs_get_bound_progs(expected=0)

    sim.tc_flush_filters()

    start_test("Test TC offloads are off by default...")
654 655
    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
                                         fail=False, include_stderr=True)
656
    fail(ret == 0, "TC filter loaded without enabling TC offloads")
657
    check_extack(err, "TC offload is disabled on net device.", args)
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
    sim.wait_for_flush()

    sim.set_ethtool_tc_offloads(True)
    sim.dfs["bpf_tc_non_bound_accept"] = "Y"

    start_test("Test TC offload by default...")
    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
    fail(ret != 0, "Software TC filter did not load")
    sim.dfs_get_bound_progs(expected=0)
    ingress = sim.tc_show_ingress(expected=1)
    fltr = ingress[0]
    fail(not fltr["in_hw"], "Filter not offloaded by default")

    sim.tc_flush_filters()

    start_test("Test TC cBPF bytcode tries offload by default...")
    ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
    fail(ret != 0, "Software TC filter did not load")
    sim.dfs_get_bound_progs(expected=0)
    ingress = sim.tc_show_ingress(expected=1)
    fltr = ingress[0]
    fail(not fltr["in_hw"], "Bytecode not offloaded by default")

    sim.tc_flush_filters()
    sim.dfs["bpf_tc_non_bound_accept"] = "N"

    start_test("Test TC cBPF unbound bytecode doesn't offload...")
685 686
    ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
                                         fail=False, include_stderr=True)
687
    fail(ret == 0, "TC bytecode loaded for offload")
688 689
    check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
                      args)
690 691
    sim.wait_for_flush()

692 693 694 695 696
    start_test("Test non-0 chain offload...")
    ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1,
                                         skip_sw=True,
                                         fail=False, include_stderr=True)
    fail(ret == 0, "Offloaded a filter to chain other than 0")
697
    check_extack(err, "Driver supports only offload of chain 0.", args)
698 699
    sim.tc_flush_filters()

700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
    start_test("Test TC replace...")
    sim.cls_bpf_add_filter(obj, prio=1, handle=1)
    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1)
    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")

    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True)
    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True)
    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")

    sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True)
    sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True)
    sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")

    start_test("Test TC replace bad flags...")
    for i in range(3):
        for j in range(3):
            ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
                                            skip_sw=(j == 1), skip_hw=(j == 2),
                                            fail=False)
            fail(bool(ret) != bool(j),
                 "Software TC incorrect load in replace test, iteration %d" %
                 (j))
        sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")

724 725 726 727 728 729 730 731 732 733 734
    start_test("Test spurious extack from the driver...")
    test_spurios_extack(sim, obj, False, "netdevsim")
    test_spurios_extack(sim, obj, True, "netdevsim")

    sim.set_ethtool_tc_offloads(False)

    test_spurios_extack(sim, obj, False, "TC offload is disabled")
    test_spurios_extack(sim, obj, True, "TC offload is disabled")

    sim.set_ethtool_tc_offloads(True)

735 736
    sim.tc_flush_filters()

737
    start_test("Test TC offloads work...")
738 739
    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
                                         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 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812

    start_test("Test TC offload basics...")
    dfs = sim.dfs_get_bound_progs(expected=1)
    progs = bpftool_prog_list(expected=1)
    ingress = sim.tc_show_ingress(expected=1)

    dprog = dfs[0]
    prog = progs[0]
    fltr = ingress[0]
    fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
    fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
    fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")

    start_test("Test TC offload is device-bound...")
    fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
    fail(prog["tag"] != fltr["tag"], "Program tags don't match")
    fail(fltr["id"] != dprog["id"], "Program IDs don't match")
    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")

    start_test("Test disabling TC offloads is rejected while filters installed...")
    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
    fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")

    start_test("Test qdisc removal frees things...")
    sim.tc_flush_filters()
    sim.tc_show_ingress(expected=0)

    start_test("Test disabling TC offloads is OK without filters...")
    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
    fail(ret != 0,
         "Driver refused to disable TC offloads without filters installed...")

    sim.set_ethtool_tc_offloads(True)

    start_test("Test destroying device gets rid of TC filters...")
    sim.cls_bpf_add_filter(obj, skip_sw=True)
    sim.remove()
    bpftool_prog_list_wait(expected=0)

    sim = NetdevSim()
    sim.set_ethtool_tc_offloads(True)

    start_test("Test destroying device gets rid of XDP...")
    sim.set_xdp(obj, "offload")
    sim.remove()
    bpftool_prog_list_wait(expected=0)

    sim = NetdevSim()
    sim.set_ethtool_tc_offloads(True)

    start_test("Test XDP prog reporting...")
    sim.set_xdp(obj, "drv")
    ipl = sim.ip_link_show(xdp=True)
    progs = bpftool_prog_list(expected=1)
    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
         "Loaded program has wrong ID")

    start_test("Test XDP prog replace without force...")
    ret, _ = sim.set_xdp(obj, "drv", fail=False)
    fail(ret == 0, "Replaced XDP program without -force")
    sim.wait_for_flush(total=1)

    start_test("Test XDP prog replace with force...")
    ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
    fail(ret != 0, "Could not replace XDP program with -force")
    bpftool_prog_list_wait(expected=1)
    ipl = sim.ip_link_show(xdp=True)
    progs = bpftool_prog_list(expected=1)
    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
         "Loaded program has wrong ID")
813 814
    fail("dev" in progs[0].keys(),
         "Device parameters reported for non-offloaded program")
815 816

    start_test("Test XDP prog replace with bad flags...")
817 818
    ret, _, err = sim.set_xdp(obj, "offload", force=True,
                              fail=False, include_stderr=True)
819
    fail(ret == 0, "Replaced XDP program with a program in different mode")
820 821 822
    check_extack_nsim(err, "program loaded with different flags.", args)
    ret, _, err = sim.set_xdp(obj, "", force=True,
                              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 828
    ret, _, err = sim.unset_xdp("offload", force=True,
                                fail=False, include_stderr=True)
829
    fail(ret == 0, "Removed program with a bad mode mode")
830 831 832
    check_extack_nsim(err, "program loaded with different flags.", args)
    ret, _, err = sim.unset_xdp("", force=True,
                                fail=False, include_stderr=True)
833
    fail(ret == 0, "Removed program with a bad mode")
834
    check_extack_nsim(err, "program loaded with different flags.", args)
835 836 837 838 839 840 841 842

    start_test("Test MTU restrictions...")
    ret, _ = sim.set_mtu(9000, fail=False)
    fail(ret == 0,
         "Driver should refuse to increase MTU to 9000 with XDP loaded...")
    sim.unset_xdp("drv")
    bpftool_prog_list_wait(expected=0)
    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 847 848 849
    sim.set_mtu(1500)

    sim.wait_for_flush()
    start_test("Test XDP offload...")
850
    _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
851 852 853 854 855
    ipl = sim.ip_link_show(xdp=True)
    link_xdp = ipl["xdp"]["prog"]
    progs = bpftool_prog_list(expected=1)
    prog = progs[0]
    fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
856
    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881

    start_test("Test XDP offload is device bound...")
    dfs = sim.dfs_get_bound_progs(expected=1)
    dprog = dfs[0]

    fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
    fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
    fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")

    start_test("Test removing XDP program many times...")
    sim.unset_xdp("offload")
    sim.unset_xdp("offload")
    sim.unset_xdp("drv")
    sim.unset_xdp("drv")
    sim.unset_xdp("")
    sim.unset_xdp("")
    bpftool_prog_list_wait(expected=0)

    start_test("Test attempt to use a program for a wrong device...")
    sim2 = NetdevSim()
    sim2.set_xdp(obj, "offload")
    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")

882 883
    ret, _, err = sim.set_xdp(pinned, "offload",
                              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 888
    ret, _, err = sim.set_xdp(pinned, "offload",
                              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 892 893 894 895 896
    rm(pin_file)
    bpftool_prog_list_wait(expected=0)

    start_test("Test mixing of TC and XDP...")
    sim.tc_add_ingress()
    sim.set_xdp(obj, "offload")
897 898
    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
                                         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 902 903 904
    sim.unset_xdp("offload")
    sim.wait_for_flush()

    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 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929

    start_test("Test binding TC from pinned...")
    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
    sim.tc_flush_filters(bound=1, total=1)
    sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
    sim.tc_flush_filters(bound=1, total=1)

    start_test("Test binding XDP from pinned...")
    sim.set_xdp(obj, "offload")
    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)

    sim.set_xdp(pinned, "offload", force=True)
    sim.unset_xdp("offload")
    sim.set_xdp(pinned, "offload", force=True)
    sim.unset_xdp("offload")

    start_test("Test offload of wrong type fails...")
    ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
    fail(ret == 0, "Managed to attach XDP program to TC")

    start_test("Test asking for TC offload of two filters...")
    sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
930 931
    ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
                                         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 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957

    sim.tc_flush_filters(bound=2, total=2)

    start_test("Test if netdev removal waits for translation...")
    delay_msec = 500
    sim.dfs["bpf_bind_verifier_delay"] = delay_msec
    start = time.time()
    cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
               (sim['ifname'], obj)
    tc_proc = cmd(cmd_line, background=True, fail=False)
    # Wait for the verifier to start
    while sim.dfs_num_bound_progs() <= 2:
        pass
    sim.remove()
    end = time.time()
    ret, _ = cmd_result(tc_proc, fail=False)
    time_diff = end - start
    log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))

    fail(ret == 0, "Managed to load TC filter on a unregistering device")
    delay_sec = delay_msec * 0.001
    fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
         (time_diff, delay_sec))

958 959 960 961 962
    # Remove all pinned files and reinstantiate the netdev
    clean_up()
    bpftool_prog_list_wait(expected=0)

    sim = NetdevSim()
963 964 965
    map_obj = bpf_obj("sample_map_ret0.o")
    start_test("Test loading program with maps...")
    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981

    start_test("Test bpftool bound info reporting (own ns)...")
    check_dev_info(False, "")

    start_test("Test bpftool bound info reporting (other ns)...")
    ns = mknetns()
    sim.set_ns(ns)
    check_dev_info(True, "")

    start_test("Test bpftool bound info reporting (remote ns)...")
    check_dev_info(False, ns)

    start_test("Test bpftool bound info reporting (back to own ns)...")
    sim.set_ns("")
    check_dev_info(False, "")

982 983
    prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
    map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
984 985 986
    sim.remove()

    start_test("Test bpftool bound info reporting (removed dev)...")
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
    check_dev_info_removed(prog_file=prog_file, map_file=map_file)

    # Remove all pinned files and reinstantiate the netdev
    clean_up()
    bpftool_prog_list_wait(expected=0)

    sim = NetdevSim()

    start_test("Test map update (no flags)...")
    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
    maps = bpftool_map_list(expected=2)
    array = maps[0] if maps[0]["type"] == "array" else maps[1]
    htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
    for m in maps:
        for i in range(2):
            bpftool("map update id %d key %s value %s" %
                    (m["id"], int2str("I", i), int2str("Q", i * 3)))

    for m in maps:
        ret, _ = bpftool("map update id %d key %s value %s" %
                         (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
                         fail=False)
        fail(ret == 0, "added too many entries")

    start_test("Test map update (exists)...")
    for m in maps:
        for i in range(2):
            bpftool("map update id %d key %s value %s exist" %
                    (m["id"], int2str("I", i), int2str("Q", i * 3)))

    for m in maps:
        ret, err = bpftool("map update id %d key %s value %s exist" %
                           (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
                           fail=False)
        fail(ret == 0, "updated non-existing key")
        fail(err["error"].find("No such file or directory") == -1,
             "expected ENOENT, error is '%s'" % (err["error"]))

    start_test("Test map update (noexist)...")
    for m in maps:
        for i in range(2):
            ret, err = bpftool("map update id %d key %s value %s noexist" %
                               (m["id"], int2str("I", i), int2str("Q", i * 3)),
                               fail=False)
        fail(ret == 0, "updated existing key")
        fail(err["error"].find("File exists") == -1,
             "expected EEXIST, error is '%s'" % (err["error"]))

    start_test("Test map dump...")
    for m in maps:
        _, entries = bpftool("map dump id %d" % (m["id"]))
        for i in range(2):
            key = str2int(entries[i]["key"])
            fail(key != i, "expected key %d, got %d" % (key, i))
            val = str2int(entries[i]["value"])
            fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))

    start_test("Test map getnext...")
    for m in maps:
        _, entry = bpftool("map getnext id %d" % (m["id"]))
        key = str2int(entry["next_key"])
        fail(key != 0, "next key %d, expected %d" % (key, 0))
        _, entry = bpftool("map getnext id %d key %s" %
                           (m["id"], int2str("I", 0)))
        key = str2int(entry["next_key"])
        fail(key != 1, "next key %d, expected %d" % (key, 1))
        ret, err = bpftool("map getnext id %d key %s" %
                           (m["id"], int2str("I", 1)), fail=False)
        fail(ret == 0, "got next key past the end of map")
        fail(err["error"].find("No such file or directory") == -1,
             "expected ENOENT, error is '%s'" % (err["error"]))

    start_test("Test map delete (htab)...")
    for i in range(2):
        bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))

    start_test("Test map delete (array)...")
    for i in range(2):
        ret, err = bpftool("map delete id %d key %s" %
                           (htab["id"], int2str("I", i)), fail=False)
        fail(ret == 0, "removed entry from an array")
        fail(err["error"].find("No such file or directory") == -1,
             "expected ENOENT, error is '%s'" % (err["error"]))

    start_test("Test map remove...")
    sim.unset_xdp("offload")
    bpftool_map_list_wait(expected=0)
    sim.remove()

    sim = NetdevSim()
    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
    sim.remove()
    bpftool_map_list_wait(expected=0)

    start_test("Test map creation fail path...")
    sim = NetdevSim()
    sim.dfs["bpf_map_accept"] = "N"
    ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
    fail(ret == 0,
         "netdevsim didn't refuse to create a map with offload disabled")
1087

1088 1089 1090 1091 1092 1093
    print("%s: OK" % (os.path.basename(__file__)))

finally:
    log("Clean up...", "", level=1)
    log_level_inc()
    clean_up()