“f7bf35862477d6d4f8a9746c645a4380de984700”上不存在“git@gitcode.net:openeuler/kernel.git”
test_offload.py 50.2 KB
Newer Older
1 2 3
#!/usr/bin/python3

# Copyright (C) 2017 Netronome Systems, Inc.
4
# Copyright (c) 2019 Mellanox Technologies. All rights reserved
5 6 7 8 9 10 11 12 13 14 15 16 17 18
#
# 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
19
import errno
20 21 22
import json
import os
import pprint
23
import random
24
import re
25
import stat
26
import string
27
import struct
28 29
import subprocess
import time
30
import traceback
31 32 33

logfile = None
log_level = 1
34
skip_extack = False
35 36 37 38
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
39
netns = [] # net namespaces to be removed
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

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)
86 87 88
    tb = "".join(traceback.extract_stack().format())
    print(tb)
    log("FAIL: " + msg, tb, level=1)
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 136 137 138 139 140 141 142
    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)

143
def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False):
144 145 146 147
    params = ""
    if JSON:
        params += "%s " % (flags["json"])

148 149 150
    if ns != "":
        ns = "ip netns exec %s " % (ns)

151 152 153 154 155 156 157 158 159 160 161 162 163 164
    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
165 166 167
    else:
        return ret, out

168 169 170
def bpftool(args, JSON=True, ns="", fail=True, include_stderr=False):
    return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns,
                fail=fail, include_stderr=include_stderr)
171

172 173
def bpftool_prog_list(expected=None, ns=""):
    _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
174 175 176 177
    # Remove the base progs
    for p in base_progs:
        if p in progs:
            progs.remove(p)
178 179 180 181 182 183
    if expected is not None:
        if len(progs) != expected:
            fail(True, "%d BPF programs loaded, expected %d" %
                 (len(progs), expected))
    return progs

184 185
def bpftool_map_list(expected=None, ns=""):
    _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
186 187 188 189
    # Remove the base maps
    for m in base_maps:
        if m in maps:
            maps.remove(m)
190 191 192 193 194 195
    if expected is not None:
        if len(maps) != expected:
            fail(True, "%d BPF maps loaded, expected %d" %
                 (len(maps), expected))
    return maps

196 197 198 199 200 201 202 203
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))

204 205 206 207 208 209 210 211
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))

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
def bpftool_prog_load(sample, file_name, maps=[], prog_type="xdp", dev=None,
                      fail=True, include_stderr=False):
    args = "prog load %s %s" % (os.path.join(bpf_test_dir, sample), file_name)
    if prog_type is not None:
        args += " type " + prog_type
    if dev is not None:
        args += " dev " + dev
    if len(maps):
        args += " map " + " map ".join(maps)

    res = bpftool(args, fail=fail, include_stderr=include_stderr)
    if res[0] == 0:
        files.append(file_name)
    return res

227
def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False):
228 229
    if force:
        args = "-force " + args
230 231
    return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns,
                fail=fail, include_stderr=include_stderr)
232

233 234 235
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)
236 237 238 239 240 241 242 243 244 245 246 247 248

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)

249 250 251 252 253 254 255 256 257
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

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
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]

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
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():
313 314
            if f == "ports":
                continue
315

316
            p = os.path.join(path, f)
317 318 319 320
            if not os.stat(p).st_mode & stat.S_IRUSR:
                continue

            if os.path.isfile(p):
321 322 323 324 325 326 327 328 329 330 331 332 333
                _, 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

334
class NetdevSimDev:
335
    """
336
    Class for netdevsim bus device and its attributes.
337
    """
338 339 340 341 342 343 344 345 346 347
    @staticmethod
    def ctrl_write(path, val):
        fullpath = os.path.join("/sys/bus/netdevsim/", path)
        try:
            with open(fullpath, "w") as f:
                f.write(val)
        except OSError as e:
            log("WRITE %s: %r" % (fullpath, val), -e.errno)
            raise e
        log("WRITE %s: %r" % (fullpath, val), 0)
348

349 350 351 352
    def __init__(self, port_count=1):
        addr = 0
        while True:
            try:
353
                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
            except OSError as e:
                if e.errno == errno.ENOSPC:
                    addr += 1
                    continue
                raise e
            break
        self.addr = addr

        # As probe of netdevsim device might happen from a workqueue,
        # so wait here until all netdevs appear.
        self.wait_for_netdevs(port_count)

        ret, out = cmd("udevadm settle", fail=False)
        if ret:
            raise Exception("udevadm settle failed")
        ifnames = self.get_ifnames()
370

371
        devs.append(self)
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr

        self.nsims = []
        for port_index in range(port_count):
            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index]))

    def get_ifnames(self):
        ifnames = []
        listdir = os.listdir("/sys/bus/netdevsim/devices/netdevsim%u/net/" % self.addr)
        for ifname in listdir:
            ifnames.append(ifname)
        ifnames.sort()
        return ifnames

    def wait_for_netdevs(self, port_count):
        timeout = 5
        timeout_start = time.time()

        while True:
            try:
                ifnames = self.get_ifnames()
            except FileNotFoundError as e:
                ifnames = []
            if len(ifnames) == port_count:
                break
            if time.time() < timeout_start + timeout:
                continue
            raise Exception("netdevices did not appear within timeout")
400

401 402 403 404
    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())
405

406 407 408 409 410 411 412
    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
413

414
    def remove(self):
415
        self.ctrl_write("del_device", "%u" % (self.addr, ))
416 417 418 419
        devs.remove(self)

    def remove_nsim(self, nsim):
        self.nsims.remove(nsim)
420 421
        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
                        "%u" % (nsim.port_index, ))
422 423 424 425 426

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

428 429 430 431 432 433 434
    def __init__(self, nsimdev, port_index, ifname):
        # In case udev renamed the netdev to according to new schema,
        # check if the name matches the port_index.
        nsimnamere = re.compile("eni\d+np(\d+)")
        match = nsimnamere.match(ifname)
        if match and int(match.groups()[0]) != port_index + 1:
            raise Exception("netdevice name mismatches the expected one")
435

436 437 438 439 440 441
        self.nsimdev = nsimdev
        self.port_index = port_index
        self.ns = ""
        self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
        self.dfs_refresh()
        _, [self.dev] = ip("link show dev %s" % ifname)
442

443 444
    def __getitem__(self, key):
        return self.dev[key]
445 446

    def remove(self):
447
        self.nsimdev.remove_nsim(self)
448 449 450 451 452

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

453 454 455 456 457
    def dfs_read(self, f):
        path = os.path.join(self.dfs_dir, f)
        _, data = cmd('cat %s' % (path))
        return data.strip()

458 459
    def wait_for_flush(self, bound=0, total=0, n_retry=20):
        for i in range(n_retry):
460
            nbound = self.nsimdev.dfs_num_bound_progs()
461 462 463 464 465 466
            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))

467 468 469 470 471
    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

472 473 474 475
    def set_mtu(self, mtu, fail=True):
        return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
                  fail=fail)

476
    def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
477
                fail=True, include_stderr=False):
478 479
        if verbose:
            bpf += " verbose"
480
        return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
481 482
                  force=force, JSON=JSON,
                  fail=fail, include_stderr=include_stderr)
483

484 485
    def unset_xdp(self, mode, force=False, JSON=True,
                  fail=True, include_stderr=False):
486
        return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
487 488
                  force=force, JSON=JSON,
                  fail=fail, include_stderr=include_stderr)
489 490 491 492 493 494 495 496 497 498 499 500 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 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542

    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

543
    def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None,
544
                      chain=None, cls="", params="",
545 546 547 548 549 550
                      fail=True, include_stderr=False):
        spec = ""
        if prio is not None:
            spec += " prio %d" % (prio)
        if handle:
            spec += " handle %s" % (handle)
551 552
        if chain is not None:
            spec += " chain %d" % (chain)
553 554 555 556 557 558 559

        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,
560
                           chain=None, da=False, verbose=False,
561 562 563 564
                           skip_sw=False, skip_hw=False,
                           fail=True, include_stderr=False):
        cls = "bpf " + bpf

565 566 567
        params = ""
        if da:
            params += " da"
568 569
        if verbose:
            params += " verbose"
570 571 572 573
        if skip_sw:
            params += " skip_sw"
        if skip_hw:
            params += " skip_hw"
574 575

        return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls,
576
                                  chain=chain, params=params,
577
                                  fail=fail, include_stderr=include_stderr)
578 579 580 581 582 583 584

    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():
585 586
    global files, netns, devs

587 588 589 590
    for dev in devs:
        dev.remove()
    for f in files:
        cmd("rm -f %s" % (f))
591 592
    for ns in netns:
        cmd("ip netns delete %s" % (ns))
593 594
    files = []
    netns = []
595 596 597 598 599 600 601 602 603

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)

604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
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)
629 630 631 632 633 634 635 636
    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")

637
    if not other_ns:
638 639 640 641 642
        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")
643 644 645 646 647

    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")
648

649 650 651 652
def check_extack(output, reference, args):
    if skip_extack:
        return
    lines = output.split("\n")
653
    comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference
654 655 656
    fail(not comp, "Missing or incorrect netlink extack message")

def check_extack_nsim(output, reference, args):
657
    check_extack(output, "netdevsim: " + reference, args)
658

659 660 661 662
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))

663 664 665 666 667 668 669
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")

670 671 672 673 674 675 676 677 678
def check_multi_basic(two_xdps):
    fail(two_xdps["mode"] != 4, "Bad mode reported with multiple programs")
    fail("prog" in two_xdps, "Base program reported in multi program mode")
    fail(len(two_xdps["attached"]) != 2,
         "Wrong attached program count with two programs")
    fail(two_xdps["attached"][0]["prog"]["id"] ==
         two_xdps["attached"][1]["prog"]["id"],
         "Offloaded and other programs have the same id")

679 680 681 682 683 684 685 686 687 688 689
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)

690
def test_multi_prog(simdev, sim, obj, modename, modeid):
691 692 693 694 695 696 697 698 699 700 701 702 703 704
    start_test("Test multi-attachment XDP - %s + offload..." %
               (modename or "default", ))
    sim.set_xdp(obj, "offload")
    xdp = sim.ip_link_show(xdp=True)["xdp"]
    offloaded = sim.dfs_read("bpf_offloaded_id")
    fail("prog" not in xdp, "Base program not reported in single program mode")
    fail(len(xdp["attached"]) != 1,
         "Wrong attached program count with one program")

    sim.set_xdp(obj, modename)
    two_xdps = sim.ip_link_show(xdp=True)["xdp"]

    fail(xdp["attached"][0] not in two_xdps["attached"],
         "Offload program not reported after other activated")
705 706 707
    check_multi_basic(two_xdps)

    offloaded2 = sim.dfs_read("bpf_offloaded_id")
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    fail(offloaded != offloaded2,
         "Offload ID changed after loading other program")

    start_test("Test multi-attachment XDP - replace...")
    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
    fail(ret == 0, "Replaced one of programs without -force")
    check_extack(err, "XDP program already attached.", args)

    if modename == "" or modename == "drv":
        othermode = "" if modename == "drv" else "drv"
        start_test("Test multi-attachment XDP - detach...")
        ret, _, err = sim.unset_xdp(othermode, force=True,
                                    fail=False, include_stderr=True)
        fail(ret == 0, "Removed program with a bad mode")
        check_extack(err, "program loaded with different flags.", args)

    sim.unset_xdp("offload")
    xdp = sim.ip_link_show(xdp=True)["xdp"]
    offloaded = sim.dfs_read("bpf_offloaded_id")

    fail(xdp["mode"] != modeid, "Bad mode reported after multiple programs")
    fail("prog" not in xdp,
         "Base program not reported after multi program mode")
    fail(xdp["attached"][0] not in two_xdps["attached"],
         "Offload program not reported after other activated")
    fail(len(xdp["attached"]) != 1,
         "Wrong attached program count with remaining programs")
    fail(offloaded != "0", "Offload ID reported with only other program left")

737
    start_test("Test multi-attachment XDP - reattach...")
738
    sim.set_xdp(obj, "offload")
739 740 741 742 743 744 745
    two_xdps = sim.ip_link_show(xdp=True)["xdp"]

    fail(xdp["attached"][0] not in two_xdps["attached"],
         "Other program not reported after offload activated")
    check_multi_basic(two_xdps)

    start_test("Test multi-attachment XDP - device remove...")
746
    simdev.remove()
747

748 749
    simdev = NetdevSimDev()
    sim, = simdev.nsims
750
    sim.set_ethtool_tc_offloads(True)
751
    return [simdev, sim]
752

753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
# 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")
770 771
base_progs = progs
_, base_maps = bpftool("map")
772 773 774 775 776 777 778 779 780 781 782

# 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
783
samples = ["sample_ret0.o", "sample_map_ret0.o"]
784 785 786 787 788
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))

789 790 791 792 793 794 795 796
# 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

797 798 799 800 801 802
# 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 = []

803 804 805 806 807
try:
    obj = bpf_obj("sample_ret0.o")
    bytecode = bpf_bytecode("1,6 0 0 4294967295,")

    start_test("Test destruction of generic XDP...")
808 809
    simdev = NetdevSimDev()
    sim, = simdev.nsims
810
    sim.set_xdp(obj, "generic")
811
    simdev.remove()
812 813
    bpftool_prog_list_wait(expected=0)

814 815
    simdev = NetdevSimDev()
    sim, = simdev.nsims
816 817 818 819 820 821 822 823 824
    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")
825
    simdev.dfs_get_bound_progs(expected=0)
826 827 828 829

    sim.tc_flush_filters()

    start_test("Test TC offloads are off by default...")
830 831
    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
                                         fail=False, include_stderr=True)
832
    fail(ret == 0, "TC filter loaded without enabling TC offloads")
833
    check_extack(err, "TC offload is disabled on net device.", args)
834 835 836 837 838 839 840 841
    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")
842
    simdev.dfs_get_bound_progs(expected=0)
843 844 845 846 847 848 849 850 851
    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")
852
    simdev.dfs_get_bound_progs(expected=0)
853 854 855 856 857 858 859 860
    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...")
861 862
    ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
                                         fail=False, include_stderr=True)
863
    fail(ret == 0, "TC bytecode loaded for offload")
864 865
    check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
                      args)
866 867
    sim.wait_for_flush()

868 869 870 871 872
    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")
873
    check_extack(err, "Driver supports only offload of chain 0.", args)
874 875
    sim.tc_flush_filters()

876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
    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")

900 901 902 903 904 905 906 907 908 909 910
    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)

911 912
    sim.tc_flush_filters()

913
    start_test("Test TC offloads work...")
914 915
    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
                                         fail=False, include_stderr=True)
916
    fail(ret != 0, "TC filter did not load with TC offloads enabled")
917
    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
918 919

    start_test("Test TC offload basics...")
920
    dfs = simdev.dfs_get_bound_progs(expected=1)
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
    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)
955
    simdev.remove()
956 957
    bpftool_prog_list_wait(expected=0)

958 959
    simdev = NetdevSimDev()
    sim, = simdev.nsims
960 961 962 963
    sim.set_ethtool_tc_offloads(True)

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

967 968
    simdev = NetdevSimDev()
    sim, = simdev.nsims
969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
    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")
991 992
    fail("dev" in progs[0].keys(),
         "Device parameters reported for non-offloaded program")
993 994

    start_test("Test XDP prog replace with bad flags...")
995 996 997
    ret, _, err = sim.set_xdp(obj, "generic", force=True,
                              fail=False, include_stderr=True)
    fail(ret == 0, "Replaced XDP program with a program in different mode")
998 999 1000
    check_extack(err,
                 "native and generic XDP can't be active at the same time.",
                 args)
1001 1002
    ret, _, err = sim.set_xdp(obj, "", force=True,
                              fail=False, include_stderr=True)
1003
    fail(ret == 0, "Replaced XDP program with a program in different mode")
1004
    check_extack(err, "program loaded with different flags.", args)
1005 1006

    start_test("Test XDP prog remove with bad flags...")
1007 1008
    ret, _, err = sim.unset_xdp("", force=True,
                                fail=False, include_stderr=True)
1009
    fail(ret == 0, "Removed program with a bad mode")
1010
    check_extack(err, "program loaded with different flags.", args)
1011 1012 1013 1014 1015 1016 1017 1018

    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)
1019
    ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True)
1020
    fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
1021
    check_extack_nsim(err, "MTU too large w/ XDP enabled.", args)
1022 1023 1024
    sim.set_mtu(1500)

    sim.wait_for_flush()
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043
    start_test("Test non-offload XDP attaching to HW...")
    bpftool_prog_load("sample_ret0.o", "/sys/fs/bpf/nooffload")
    nooffload = bpf_pinned("/sys/fs/bpf/nooffload")
    ret, _, err = sim.set_xdp(nooffload, "offload",
                              fail=False, include_stderr=True)
    fail(ret == 0, "attached non-offloaded XDP program to HW")
    check_extack_nsim(err, "xdpoffload of non-bound program.", args)
    rm("/sys/fs/bpf/nooffload")

    start_test("Test offload XDP attaching to drv...")
    bpftool_prog_load("sample_ret0.o", "/sys/fs/bpf/offload",
                      dev=sim['ifname'])
    offload = bpf_pinned("/sys/fs/bpf/offload")
    ret, _, err = sim.set_xdp(offload, "drv", fail=False, include_stderr=True)
    fail(ret == 0, "attached offloaded XDP program to drv")
    check_extack(err, "using device-bound program without HW_MODE flag is not supported.", args)
    rm("/sys/fs/bpf/offload")
    sim.wait_for_flush()

1044
    start_test("Test XDP offload...")
1045
    _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
1046 1047 1048 1049 1050
    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")
1051
    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
1052 1053

    start_test("Test XDP offload is device bound...")
1054
    dfs = simdev.dfs_get_bound_progs(expected=1)
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
    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...")
1073 1074
    simdev2 = NetdevSimDev()
    sim2, = simdev2.nsims
1075 1076 1077
    sim2.set_xdp(obj, "offload")
    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")

1078 1079
    ret, _, err = sim.set_xdp(pinned, "offload",
                              fail=False, include_stderr=True)
1080
    fail(ret == 0, "Pinned program loaded for a different device accepted")
1081
    check_extack_nsim(err, "program bound to different dev.", args)
1082
    simdev2.remove()
1083 1084
    ret, _, err = sim.set_xdp(pinned, "offload",
                              fail=False, include_stderr=True)
1085
    fail(ret == 0, "Pinned program loaded for a removed device accepted")
1086
    check_extack_nsim(err, "xdpoffload of non-bound program.", args)
1087 1088 1089
    rm(pin_file)
    bpftool_prog_list_wait(expected=0)

1090 1091 1092
    simdev, sim = test_multi_prog(simdev, sim, obj, "", 1)
    simdev, sim = test_multi_prog(simdev, sim, obj, "drv", 1)
    simdev, sim = test_multi_prog(simdev, sim, obj, "generic", 2)
1093

1094 1095 1096
    start_test("Test mixing of TC and XDP...")
    sim.tc_add_ingress()
    sim.set_xdp(obj, "offload")
1097 1098
    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
                                         fail=False, include_stderr=True)
1099
    fail(ret == 0, "Loading TC when XDP active should fail")
1100
    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
1101 1102 1103 1104
    sim.unset_xdp("offload")
    sim.wait_for_flush()

    sim.cls_bpf_add_filter(obj, skip_sw=True)
1105
    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
1106
    fail(ret == 0, "Loading XDP when TC active should fail")
1107
    check_extack_nsim(err, "TC program is already loaded.", args)
1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129

    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)
1130 1131
    ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
                                         fail=False, include_stderr=True)
1132
    fail(ret == 0, "Managed to offload two TC filters at the same time")
1133
    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
1134 1135 1136 1137 1138

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

    start_test("Test if netdev removal waits for translation...")
    delay_msec = 500
J
Jiri Pirko 已提交
1139
    sim.dfs["dev/bpf_bind_verifier_delay"] = delay_msec
1140 1141 1142 1143 1144
    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
1145
    while simdev.dfs_num_bound_progs() <= 2:
1146
        pass
1147
    simdev.remove()
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
    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))

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

1162 1163
    simdev = NetdevSimDev()
    sim, = simdev.nsims
1164 1165 1166
    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
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182

    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, "")

1183 1184
    prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
    map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
1185
    simdev.remove()
1186 1187

    start_test("Test bpftool bound info reporting (removed dev)...")
1188 1189 1190 1191 1192 1193
    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)

1194 1195
    simdev = NetdevSimDev()
    sim, = simdev.nsims
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275

    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)
1276
    simdev.remove()
1277

1278 1279
    simdev = NetdevSimDev()
    sim, = simdev.nsims
1280
    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1281
    simdev.remove()
1282 1283 1284
    bpftool_map_list_wait(expected=0)

    start_test("Test map creation fail path...")
1285 1286
    simdev = NetdevSimDev()
    sim, = simdev.nsims
1287 1288 1289 1290
    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")
1291

1292
    simdev.remove()
1293 1294

    start_test("Test multi-dev ASIC program reuse...")
1295 1296 1297 1298
    simdevA = NetdevSimDev()
    simA, = simdevA.nsims
    simdevB = NetdevSimDev(3)
    simB1, simB2, simB3 = simdevB.nsims
1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309
    sims = (simA, simB1, simB2, simB3)
    simB = (simB1, simB2, simB3)

    bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimA",
                      dev=simA['ifname'])
    progA = bpf_pinned("/sys/fs/bpf/nsimA")
    bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimB",
                      dev=simB1['ifname'])
    progB = bpf_pinned("/sys/fs/bpf/nsimB")

    simA.set_xdp(progA, "offload", JSON=False)
1310
    for d in simdevB.nsims:
1311 1312 1313 1314 1315
        d.set_xdp(progB, "offload", JSON=False)

    start_test("Test multi-dev ASIC cross-dev replace...")
    ret, _ = simA.set_xdp(progB, "offload", force=True, JSON=False, fail=False)
    fail(ret == 0, "cross-ASIC program allowed")
1316
    for d in simdevB.nsims:
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327
        ret, _ = d.set_xdp(progA, "offload", force=True, JSON=False, fail=False)
        fail(ret == 0, "cross-ASIC program allowed")

    start_test("Test multi-dev ASIC cross-dev install...")
    for d in sims:
        d.unset_xdp("offload")

    ret, _, err = simA.set_xdp(progB, "offload", force=True, JSON=False,
                               fail=False, include_stderr=True)
    fail(ret == 0, "cross-ASIC program allowed")
    check_extack_nsim(err, "program bound to different dev.", args)
1328
    for d in simdevB.nsims:
1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364
        ret, _, err = d.set_xdp(progA, "offload", force=True, JSON=False,
                                fail=False, include_stderr=True)
        fail(ret == 0, "cross-ASIC program allowed")
        check_extack_nsim(err, "program bound to different dev.", args)

    start_test("Test multi-dev ASIC cross-dev map reuse...")

    mapA = bpftool("prog show %s" % (progA))[1]["map_ids"][0]
    mapB = bpftool("prog show %s" % (progB))[1]["map_ids"][0]

    ret, _ = bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimB_",
                               dev=simB3['ifname'],
                               maps=["idx 0 id %d" % (mapB)],
                               fail=False)
    fail(ret != 0, "couldn't reuse a map on the same ASIC")
    rm("/sys/fs/bpf/nsimB_")

    ret, _, err = bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimA_",
                                    dev=simA['ifname'],
                                    maps=["idx 0 id %d" % (mapB)],
                                    fail=False, include_stderr=True)
    fail(ret == 0, "could reuse a map on a different ASIC")
    fail(err.count("offload device mismatch between prog and map") == 0,
         "error message missing for cross-ASIC map")

    ret, _, err = bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimB_",
                                    dev=simB1['ifname'],
                                    maps=["idx 0 id %d" % (mapA)],
                                    fail=False, include_stderr=True)
    fail(ret == 0, "could reuse a map on a different ASIC")
    fail(err.count("offload device mismatch between prog and map") == 0,
         "error message missing for cross-ASIC map")

    start_test("Test multi-dev ASIC cross-dev destruction...")
    bpftool_prog_list_wait(expected=2)

1365
    simdevA.remove()
1366 1367 1368
    bpftool_prog_list_wait(expected=1)

    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
1369
    fail(ifnameB != simB1['ifname'], "program not bound to original device")
1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382
    simB1.remove()
    bpftool_prog_list_wait(expected=1)

    start_test("Test multi-dev ASIC cross-dev destruction - move...")
    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
    fail(ifnameB not in (simB2['ifname'], simB3['ifname']),
         "program not bound to remaining devices")

    simB2.remove()
    ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
    fail(ifnameB != simB3['ifname'], "program not bound to remaining device")

    simB3.remove()
1383
    simdevB.remove()
1384 1385 1386 1387 1388 1389 1390 1391 1392
    bpftool_prog_list_wait(expected=0)

    start_test("Test multi-dev ASIC cross-dev destruction - orphaned...")
    ret, out = bpftool("prog show %s" % (progB), fail=False)
    fail(ret == 0, "got information about orphaned program")
    fail("error" not in out, "no error reported for get info on orphaned")
    fail(out["error"] != "can't get prog info: No such device",
         "wrong error for get info on orphaned")

1393 1394 1395 1396 1397 1398
    print("%s: OK" % (os.path.basename(__file__)))

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