1417ec264SJakub Kicinski#!/usr/bin/python3
2417ec264SJakub Kicinski
3417ec264SJakub Kicinski# Copyright (C) 2017 Netronome Systems, Inc.
4417ec264SJakub Kicinski#
5417ec264SJakub Kicinski# This software is licensed under the GNU General License Version 2,
6417ec264SJakub Kicinski# June 1991 as shown in the file COPYING in the top-level directory of this
7417ec264SJakub Kicinski# source tree.
8417ec264SJakub Kicinski#
9417ec264SJakub Kicinski# THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
10417ec264SJakub Kicinski# WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
11417ec264SJakub Kicinski# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12417ec264SJakub Kicinski# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
13417ec264SJakub Kicinski# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
14417ec264SJakub Kicinski# THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
15417ec264SJakub Kicinski
16417ec264SJakub Kicinskifrom datetime import datetime
17417ec264SJakub Kicinskiimport argparse
18417ec264SJakub Kicinskiimport json
19417ec264SJakub Kicinskiimport os
20417ec264SJakub Kicinskiimport pprint
21752d7b45SJakub Kicinskiimport random
22752d7b45SJakub Kicinskiimport string
237fedbb7cSJakub Kicinskiimport struct
24417ec264SJakub Kicinskiimport subprocess
25417ec264SJakub Kicinskiimport time
26417ec264SJakub Kicinski
27417ec264SJakub Kicinskilogfile = None
28417ec264SJakub Kicinskilog_level = 1
29caf95228SQuentin Monnetskip_extack = False
30417ec264SJakub Kicinskibpf_test_dir = os.path.dirname(os.path.realpath(__file__))
31417ec264SJakub Kicinskipp = pprint.PrettyPrinter()
32417ec264SJakub Kicinskidevs = [] # devices we created for clean up
33417ec264SJakub Kicinskifiles = [] # files to be removed
34752d7b45SJakub Kicinskinetns = [] # net namespaces to be removed
35417ec264SJakub Kicinski
36417ec264SJakub Kicinskidef log_get_sec(level=0):
37417ec264SJakub Kicinski    return "*" * (log_level + level)
38417ec264SJakub Kicinski
39417ec264SJakub Kicinskidef log_level_inc(add=1):
40417ec264SJakub Kicinski    global log_level
41417ec264SJakub Kicinski    log_level += add
42417ec264SJakub Kicinski
43417ec264SJakub Kicinskidef log_level_dec(sub=1):
44417ec264SJakub Kicinski    global log_level
45417ec264SJakub Kicinski    log_level -= sub
46417ec264SJakub Kicinski
47417ec264SJakub Kicinskidef log_level_set(level):
48417ec264SJakub Kicinski    global log_level
49417ec264SJakub Kicinski    log_level = level
50417ec264SJakub Kicinski
51417ec264SJakub Kicinskidef log(header, data, level=None):
52417ec264SJakub Kicinski    """
53417ec264SJakub Kicinski    Output to an optional log.
54417ec264SJakub Kicinski    """
55417ec264SJakub Kicinski    if logfile is None:
56417ec264SJakub Kicinski        return
57417ec264SJakub Kicinski    if level is not None:
58417ec264SJakub Kicinski        log_level_set(level)
59417ec264SJakub Kicinski
60417ec264SJakub Kicinski    if not isinstance(data, str):
61417ec264SJakub Kicinski        data = pp.pformat(data)
62417ec264SJakub Kicinski
63417ec264SJakub Kicinski    if len(header):
64417ec264SJakub Kicinski        logfile.write("\n" + log_get_sec() + " ")
65417ec264SJakub Kicinski        logfile.write(header)
66417ec264SJakub Kicinski    if len(header) and len(data.strip()):
67417ec264SJakub Kicinski        logfile.write("\n")
68417ec264SJakub Kicinski    logfile.write(data)
69417ec264SJakub Kicinski
70417ec264SJakub Kicinskidef skip(cond, msg):
71417ec264SJakub Kicinski    if not cond:
72417ec264SJakub Kicinski        return
73417ec264SJakub Kicinski    print("SKIP: " + msg)
74417ec264SJakub Kicinski    log("SKIP: " + msg, "", level=1)
75417ec264SJakub Kicinski    os.sys.exit(0)
76417ec264SJakub Kicinski
77417ec264SJakub Kicinskidef fail(cond, msg):
78417ec264SJakub Kicinski    if not cond:
79417ec264SJakub Kicinski        return
80417ec264SJakub Kicinski    print("FAIL: " + msg)
81417ec264SJakub Kicinski    log("FAIL: " + msg, "", level=1)
82417ec264SJakub Kicinski    os.sys.exit(1)
83417ec264SJakub Kicinski
84417ec264SJakub Kicinskidef start_test(msg):
85417ec264SJakub Kicinski    log(msg, "", level=1)
86417ec264SJakub Kicinski    log_level_inc()
87417ec264SJakub Kicinski    print(msg)
88417ec264SJakub Kicinski
89417ec264SJakub Kicinskidef cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
90417ec264SJakub Kicinski    """
91417ec264SJakub Kicinski    Run a command in subprocess and return tuple of (retval, stdout);
92417ec264SJakub Kicinski    optionally return stderr as well as third value.
93417ec264SJakub Kicinski    """
94417ec264SJakub Kicinski    proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
95417ec264SJakub Kicinski                            stderr=subprocess.PIPE)
96417ec264SJakub Kicinski    if background:
97417ec264SJakub Kicinski        msg = "%s START: %s" % (log_get_sec(1),
98417ec264SJakub Kicinski                                datetime.now().strftime("%H:%M:%S.%f"))
99417ec264SJakub Kicinski        log("BKG " + proc.args, msg)
100417ec264SJakub Kicinski        return proc
101417ec264SJakub Kicinski
102417ec264SJakub Kicinski    return cmd_result(proc, include_stderr=include_stderr, fail=fail)
103417ec264SJakub Kicinski
104417ec264SJakub Kicinskidef cmd_result(proc, include_stderr=False, fail=False):
105417ec264SJakub Kicinski    stdout, stderr = proc.communicate()
106417ec264SJakub Kicinski    stdout = stdout.decode("utf-8")
107417ec264SJakub Kicinski    stderr = stderr.decode("utf-8")
108417ec264SJakub Kicinski    proc.stdout.close()
109417ec264SJakub Kicinski    proc.stderr.close()
110417ec264SJakub Kicinski
111417ec264SJakub Kicinski    stderr = "\n" + stderr
112417ec264SJakub Kicinski    if stderr[-1] == "\n":
113417ec264SJakub Kicinski        stderr = stderr[:-1]
114417ec264SJakub Kicinski
115417ec264SJakub Kicinski    sec = log_get_sec(1)
116417ec264SJakub Kicinski    log("CMD " + proc.args,
117417ec264SJakub Kicinski        "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
118417ec264SJakub Kicinski        (proc.returncode, sec, stdout, sec, stderr,
119417ec264SJakub Kicinski         sec, datetime.now().strftime("%H:%M:%S.%f")))
120417ec264SJakub Kicinski
121417ec264SJakub Kicinski    if proc.returncode != 0 and fail:
122417ec264SJakub Kicinski        if len(stderr) > 0 and stderr[-1] == "\n":
123417ec264SJakub Kicinski            stderr = stderr[:-1]
124417ec264SJakub Kicinski        raise Exception("Command failed: %s\n%s" % (proc.args, stderr))
125417ec264SJakub Kicinski
126417ec264SJakub Kicinski    if include_stderr:
127417ec264SJakub Kicinski        return proc.returncode, stdout, stderr
128417ec264SJakub Kicinski    else:
129417ec264SJakub Kicinski        return proc.returncode, stdout
130417ec264SJakub Kicinski
131417ec264SJakub Kicinskidef rm(f):
132417ec264SJakub Kicinski    cmd("rm -f %s" % (f))
133417ec264SJakub Kicinski    if f in files:
134417ec264SJakub Kicinski        files.remove(f)
135417ec264SJakub Kicinski
136caf95228SQuentin Monnetdef tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False):
137417ec264SJakub Kicinski    params = ""
138417ec264SJakub Kicinski    if JSON:
139417ec264SJakub Kicinski        params += "%s " % (flags["json"])
140417ec264SJakub Kicinski
141752d7b45SJakub Kicinski    if ns != "":
142752d7b45SJakub Kicinski        ns = "ip netns exec %s " % (ns)
143752d7b45SJakub Kicinski
144caf95228SQuentin Monnet    if include_stderr:
145caf95228SQuentin Monnet        ret, stdout, stderr = cmd(ns + name + " " + params + args,
146caf95228SQuentin Monnet                                  fail=fail, include_stderr=True)
147caf95228SQuentin Monnet    else:
148caf95228SQuentin Monnet        ret, stdout = cmd(ns + name + " " + params + args,
149caf95228SQuentin Monnet                          fail=fail, include_stderr=False)
150caf95228SQuentin Monnet
151caf95228SQuentin Monnet    if JSON and len(stdout.strip()) != 0:
152caf95228SQuentin Monnet        out = json.loads(stdout)
153caf95228SQuentin Monnet    else:
154caf95228SQuentin Monnet        out = stdout
155caf95228SQuentin Monnet
156caf95228SQuentin Monnet    if include_stderr:
157caf95228SQuentin Monnet        return ret, out, stderr
158417ec264SJakub Kicinski    else:
159417ec264SJakub Kicinski        return ret, out
160417ec264SJakub Kicinski
161752d7b45SJakub Kicinskidef bpftool(args, JSON=True, ns="", fail=True):
162752d7b45SJakub Kicinski    return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns, fail=fail)
163417ec264SJakub Kicinski
164752d7b45SJakub Kicinskidef bpftool_prog_list(expected=None, ns=""):
165752d7b45SJakub Kicinski    _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
166417ec264SJakub Kicinski    if expected is not None:
167417ec264SJakub Kicinski        if len(progs) != expected:
168417ec264SJakub Kicinski            fail(True, "%d BPF programs loaded, expected %d" %
169417ec264SJakub Kicinski                 (len(progs), expected))
170417ec264SJakub Kicinski    return progs
171417ec264SJakub Kicinski
1727fedbb7cSJakub Kicinskidef bpftool_map_list(expected=None, ns=""):
1737fedbb7cSJakub Kicinski    _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
1747fedbb7cSJakub Kicinski    if expected is not None:
1757fedbb7cSJakub Kicinski        if len(maps) != expected:
1767fedbb7cSJakub Kicinski            fail(True, "%d BPF maps loaded, expected %d" %
1777fedbb7cSJakub Kicinski                 (len(maps), expected))
1787fedbb7cSJakub Kicinski    return maps
1797fedbb7cSJakub Kicinski
180417ec264SJakub Kicinskidef bpftool_prog_list_wait(expected=0, n_retry=20):
181417ec264SJakub Kicinski    for i in range(n_retry):
182417ec264SJakub Kicinski        nprogs = len(bpftool_prog_list())
183417ec264SJakub Kicinski        if nprogs == expected:
184417ec264SJakub Kicinski            return
185417ec264SJakub Kicinski        time.sleep(0.05)
186417ec264SJakub Kicinski    raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
187417ec264SJakub Kicinski
1887fedbb7cSJakub Kicinskidef bpftool_map_list_wait(expected=0, n_retry=20):
1897fedbb7cSJakub Kicinski    for i in range(n_retry):
1907fedbb7cSJakub Kicinski        nmaps = len(bpftool_map_list())
1917fedbb7cSJakub Kicinski        if nmaps == expected:
1927fedbb7cSJakub Kicinski            return
1937fedbb7cSJakub Kicinski        time.sleep(0.05)
1947fedbb7cSJakub Kicinski    raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))
1957fedbb7cSJakub Kicinski
196caf95228SQuentin Monnetdef ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False):
197417ec264SJakub Kicinski    if force:
198417ec264SJakub Kicinski        args = "-force " + args
199caf95228SQuentin Monnet    return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns,
200caf95228SQuentin Monnet                fail=fail, include_stderr=include_stderr)
201417ec264SJakub Kicinski
202caf95228SQuentin Monnetdef tc(args, JSON=True, ns="", fail=True, include_stderr=False):
203caf95228SQuentin Monnet    return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns,
204caf95228SQuentin Monnet                fail=fail, include_stderr=include_stderr)
205417ec264SJakub Kicinski
206417ec264SJakub Kicinskidef ethtool(dev, opt, args, fail=True):
207417ec264SJakub Kicinski    return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)
208417ec264SJakub Kicinski
209417ec264SJakub Kicinskidef bpf_obj(name, sec=".text", path=bpf_test_dir,):
210417ec264SJakub Kicinski    return "obj %s sec %s" % (os.path.join(path, name), sec)
211417ec264SJakub Kicinski
212417ec264SJakub Kicinskidef bpf_pinned(name):
213417ec264SJakub Kicinski    return "pinned %s" % (name)
214417ec264SJakub Kicinski
215417ec264SJakub Kicinskidef bpf_bytecode(bytecode):
216417ec264SJakub Kicinski    return "bytecode \"%s\"" % (bytecode)
217417ec264SJakub Kicinski
218752d7b45SJakub Kicinskidef mknetns(n_retry=10):
219752d7b45SJakub Kicinski    for i in range(n_retry):
220752d7b45SJakub Kicinski        name = ''.join([random.choice(string.ascii_letters) for i in range(8)])
221752d7b45SJakub Kicinski        ret, _ = ip("netns add %s" % (name), fail=False)
222752d7b45SJakub Kicinski        if ret == 0:
223752d7b45SJakub Kicinski            netns.append(name)
224752d7b45SJakub Kicinski            return name
225752d7b45SJakub Kicinski    return None
226752d7b45SJakub Kicinski
2277fedbb7cSJakub Kicinskidef int2str(fmt, val):
2287fedbb7cSJakub Kicinski    ret = []
2297fedbb7cSJakub Kicinski    for b in struct.pack(fmt, val):
2307fedbb7cSJakub Kicinski        ret.append(int(b))
2317fedbb7cSJakub Kicinski    return " ".join(map(lambda x: str(x), ret))
2327fedbb7cSJakub Kicinski
2337fedbb7cSJakub Kicinskidef str2int(strtab):
2347fedbb7cSJakub Kicinski    inttab = []
2357fedbb7cSJakub Kicinski    for i in strtab:
2367fedbb7cSJakub Kicinski        inttab.append(int(i, 16))
2377fedbb7cSJakub Kicinski    ba = bytearray(inttab)
2387fedbb7cSJakub Kicinski    if len(strtab) == 4:
2397fedbb7cSJakub Kicinski        fmt = "I"
2407fedbb7cSJakub Kicinski    elif len(strtab) == 8:
2417fedbb7cSJakub Kicinski        fmt = "Q"
2427fedbb7cSJakub Kicinski    else:
2437fedbb7cSJakub Kicinski        raise Exception("String array of len %d can't be unpacked to an int" %
2447fedbb7cSJakub Kicinski                        (len(strtab)))
2457fedbb7cSJakub Kicinski    return struct.unpack(fmt, ba)[0]
2467fedbb7cSJakub Kicinski
247417ec264SJakub Kicinskiclass DebugfsDir:
248417ec264SJakub Kicinski    """
249417ec264SJakub Kicinski    Class for accessing DebugFS directories as a dictionary.
250417ec264SJakub Kicinski    """
251417ec264SJakub Kicinski
252417ec264SJakub Kicinski    def __init__(self, path):
253417ec264SJakub Kicinski        self.path = path
254417ec264SJakub Kicinski        self._dict = self._debugfs_dir_read(path)
255417ec264SJakub Kicinski
256417ec264SJakub Kicinski    def __len__(self):
257417ec264SJakub Kicinski        return len(self._dict.keys())
258417ec264SJakub Kicinski
259417ec264SJakub Kicinski    def __getitem__(self, key):
260417ec264SJakub Kicinski        if type(key) is int:
261417ec264SJakub Kicinski            key = list(self._dict.keys())[key]
262417ec264SJakub Kicinski        return self._dict[key]
263417ec264SJakub Kicinski
264417ec264SJakub Kicinski    def __setitem__(self, key, value):
265417ec264SJakub Kicinski        log("DebugFS set %s = %s" % (key, value), "")
266417ec264SJakub Kicinski        log_level_inc()
267417ec264SJakub Kicinski
268417ec264SJakub Kicinski        cmd("echo '%s' > %s/%s" % (value, self.path, key))
269417ec264SJakub Kicinski        log_level_dec()
270417ec264SJakub Kicinski
271417ec264SJakub Kicinski        _, out = cmd('cat %s/%s' % (self.path, key))
272417ec264SJakub Kicinski        self._dict[key] = out.strip()
273417ec264SJakub Kicinski
274417ec264SJakub Kicinski    def _debugfs_dir_read(self, path):
275417ec264SJakub Kicinski        dfs = {}
276417ec264SJakub Kicinski
277417ec264SJakub Kicinski        log("DebugFS state for %s" % (path), "")
278417ec264SJakub Kicinski        log_level_inc(add=2)
279417ec264SJakub Kicinski
280417ec264SJakub Kicinski        _, out = cmd('ls ' + path)
281417ec264SJakub Kicinski        for f in out.split():
282417ec264SJakub Kicinski            p = os.path.join(path, f)
283417ec264SJakub Kicinski            if os.path.isfile(p):
284417ec264SJakub Kicinski                _, out = cmd('cat %s/%s' % (path, f))
285417ec264SJakub Kicinski                dfs[f] = out.strip()
286417ec264SJakub Kicinski            elif os.path.isdir(p):
287417ec264SJakub Kicinski                dfs[f] = DebugfsDir(p)
288417ec264SJakub Kicinski            else:
289417ec264SJakub Kicinski                raise Exception("%s is neither file nor directory" % (p))
290417ec264SJakub Kicinski
291417ec264SJakub Kicinski        log_level_dec()
292417ec264SJakub Kicinski        log("DebugFS state", dfs)
293417ec264SJakub Kicinski        log_level_dec()
294417ec264SJakub Kicinski
295417ec264SJakub Kicinski        return dfs
296417ec264SJakub Kicinski
297417ec264SJakub Kicinskiclass NetdevSim:
298417ec264SJakub Kicinski    """
299417ec264SJakub Kicinski    Class for netdevsim netdevice and its attributes.
300417ec264SJakub Kicinski    """
301417ec264SJakub Kicinski
302417ec264SJakub Kicinski    def __init__(self):
303417ec264SJakub Kicinski        self.dev = self._netdevsim_create()
304417ec264SJakub Kicinski        devs.append(self)
305417ec264SJakub Kicinski
306752d7b45SJakub Kicinski        self.ns = ""
307752d7b45SJakub Kicinski
308417ec264SJakub Kicinski        self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname'])
309417ec264SJakub Kicinski        self.dfs_refresh()
310417ec264SJakub Kicinski
311417ec264SJakub Kicinski    def __getitem__(self, key):
312417ec264SJakub Kicinski        return self.dev[key]
313417ec264SJakub Kicinski
314417ec264SJakub Kicinski    def _netdevsim_create(self):
315417ec264SJakub Kicinski        _, old  = ip("link show")
316417ec264SJakub Kicinski        ip("link add sim%d type netdevsim")
317417ec264SJakub Kicinski        _, new  = ip("link show")
318417ec264SJakub Kicinski
319417ec264SJakub Kicinski        for dev in new:
320417ec264SJakub Kicinski            f = filter(lambda x: x["ifname"] == dev["ifname"], old)
321417ec264SJakub Kicinski            if len(list(f)) == 0:
322417ec264SJakub Kicinski                return dev
323417ec264SJakub Kicinski
324417ec264SJakub Kicinski        raise Exception("failed to create netdevsim device")
325417ec264SJakub Kicinski
326417ec264SJakub Kicinski    def remove(self):
327417ec264SJakub Kicinski        devs.remove(self)
328752d7b45SJakub Kicinski        ip("link del dev %s" % (self.dev["ifname"]), ns=self.ns)
329417ec264SJakub Kicinski
330417ec264SJakub Kicinski    def dfs_refresh(self):
331417ec264SJakub Kicinski        self.dfs = DebugfsDir(self.dfs_dir)
332417ec264SJakub Kicinski        return self.dfs
333417ec264SJakub Kicinski
334417ec264SJakub Kicinski    def dfs_num_bound_progs(self):
335417ec264SJakub Kicinski        path = os.path.join(self.dfs_dir, "bpf_bound_progs")
336417ec264SJakub Kicinski        _, progs = cmd('ls %s' % (path))
337417ec264SJakub Kicinski        return len(progs.split())
338417ec264SJakub Kicinski
339417ec264SJakub Kicinski    def dfs_get_bound_progs(self, expected):
340417ec264SJakub Kicinski        progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
341417ec264SJakub Kicinski        if expected is not None:
342417ec264SJakub Kicinski            if len(progs) != expected:
343417ec264SJakub Kicinski                fail(True, "%d BPF programs bound, expected %d" %
344417ec264SJakub Kicinski                     (len(progs), expected))
345417ec264SJakub Kicinski        return progs
346417ec264SJakub Kicinski
347417ec264SJakub Kicinski    def wait_for_flush(self, bound=0, total=0, n_retry=20):
348417ec264SJakub Kicinski        for i in range(n_retry):
349417ec264SJakub Kicinski            nbound = self.dfs_num_bound_progs()
350417ec264SJakub Kicinski            nprogs = len(bpftool_prog_list())
351417ec264SJakub Kicinski            if nbound == bound and nprogs == total:
352417ec264SJakub Kicinski                return
353417ec264SJakub Kicinski            time.sleep(0.05)
354417ec264SJakub Kicinski        raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
355417ec264SJakub Kicinski
356752d7b45SJakub Kicinski    def set_ns(self, ns):
357752d7b45SJakub Kicinski        name = "1" if ns == "" else ns
358752d7b45SJakub Kicinski        ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
359752d7b45SJakub Kicinski        self.ns = ns
360752d7b45SJakub Kicinski
361417ec264SJakub Kicinski    def set_mtu(self, mtu, fail=True):
362417ec264SJakub Kicinski        return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
363417ec264SJakub Kicinski                  fail=fail)
364417ec264SJakub Kicinski
3659045bdc8SQuentin Monnet    def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
366caf95228SQuentin Monnet                fail=True, include_stderr=False):
3679045bdc8SQuentin Monnet        if verbose:
3689045bdc8SQuentin Monnet            bpf += " verbose"
369417ec264SJakub Kicinski        return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
370caf95228SQuentin Monnet                  force=force, JSON=JSON,
371caf95228SQuentin Monnet                  fail=fail, include_stderr=include_stderr)
372417ec264SJakub Kicinski
373caf95228SQuentin Monnet    def unset_xdp(self, mode, force=False, JSON=True,
374caf95228SQuentin Monnet                  fail=True, include_stderr=False):
375417ec264SJakub Kicinski        return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
376caf95228SQuentin Monnet                  force=force, JSON=JSON,
377caf95228SQuentin Monnet                  fail=fail, include_stderr=include_stderr)
378417ec264SJakub Kicinski
379417ec264SJakub Kicinski    def ip_link_show(self, xdp):
380417ec264SJakub Kicinski        _, link = ip("link show dev %s" % (self['ifname']))
381417ec264SJakub Kicinski        if len(link) > 1:
382417ec264SJakub Kicinski            raise Exception("Multiple objects on ip link show")
383417ec264SJakub Kicinski        if len(link) < 1:
384417ec264SJakub Kicinski            return {}
385417ec264SJakub Kicinski        fail(xdp != "xdp" in link,
386417ec264SJakub Kicinski             "XDP program not reporting in iplink (reported %s, expected %s)" %
387417ec264SJakub Kicinski             ("xdp" in link, xdp))
388417ec264SJakub Kicinski        return link[0]
389417ec264SJakub Kicinski
390417ec264SJakub Kicinski    def tc_add_ingress(self):
391417ec264SJakub Kicinski        tc("qdisc add dev %s ingress" % (self['ifname']))
392417ec264SJakub Kicinski
393417ec264SJakub Kicinski    def tc_del_ingress(self):
394417ec264SJakub Kicinski        tc("qdisc del dev %s ingress" % (self['ifname']))
395417ec264SJakub Kicinski
396417ec264SJakub Kicinski    def tc_flush_filters(self, bound=0, total=0):
397417ec264SJakub Kicinski        self.tc_del_ingress()
398417ec264SJakub Kicinski        self.tc_add_ingress()
399417ec264SJakub Kicinski        self.wait_for_flush(bound=bound, total=total)
400417ec264SJakub Kicinski
401417ec264SJakub Kicinski    def tc_show_ingress(self, expected=None):
402417ec264SJakub Kicinski        # No JSON support, oh well...
403417ec264SJakub Kicinski        flags = ["skip_sw", "skip_hw", "in_hw"]
404417ec264SJakub Kicinski        named = ["protocol", "pref", "chain", "handle", "id", "tag"]
405417ec264SJakub Kicinski
406417ec264SJakub Kicinski        args = "-s filter show dev %s ingress" % (self['ifname'])
407417ec264SJakub Kicinski        _, out = tc(args, JSON=False)
408417ec264SJakub Kicinski
409417ec264SJakub Kicinski        filters = []
410417ec264SJakub Kicinski        lines = out.split('\n')
411417ec264SJakub Kicinski        for line in lines:
412417ec264SJakub Kicinski            words = line.split()
413417ec264SJakub Kicinski            if "handle" not in words:
414417ec264SJakub Kicinski                continue
415417ec264SJakub Kicinski            fltr = {}
416417ec264SJakub Kicinski            for flag in flags:
417417ec264SJakub Kicinski                fltr[flag] = flag in words
418417ec264SJakub Kicinski            for name in named:
419417ec264SJakub Kicinski                try:
420417ec264SJakub Kicinski                    idx = words.index(name)
421417ec264SJakub Kicinski                    fltr[name] = words[idx + 1]
422417ec264SJakub Kicinski                except ValueError:
423417ec264SJakub Kicinski                    pass
424417ec264SJakub Kicinski            filters.append(fltr)
425417ec264SJakub Kicinski
426417ec264SJakub Kicinski        if expected is not None:
427417ec264SJakub Kicinski            fail(len(filters) != expected,
428417ec264SJakub Kicinski                 "%d ingress filters loaded, expected %d" %
429417ec264SJakub Kicinski                 (len(filters), expected))
430417ec264SJakub Kicinski        return filters
431417ec264SJakub Kicinski
4329045bdc8SQuentin Monnet    def cls_bpf_add_filter(self, bpf, da=False, verbose=False, skip_sw=False,
4339045bdc8SQuentin Monnet                           skip_hw=False, fail=True, include_stderr=False):
434417ec264SJakub Kicinski        params = ""
435417ec264SJakub Kicinski        if da:
436417ec264SJakub Kicinski            params += " da"
4379045bdc8SQuentin Monnet        if verbose:
4389045bdc8SQuentin Monnet            params += " verbose"
439417ec264SJakub Kicinski        if skip_sw:
440417ec264SJakub Kicinski            params += " skip_sw"
441417ec264SJakub Kicinski        if skip_hw:
442417ec264SJakub Kicinski            params += " skip_hw"
443417ec264SJakub Kicinski        return tc("filter add dev %s ingress bpf %s %s" %
444caf95228SQuentin Monnet                  (self['ifname'], bpf, params),
445caf95228SQuentin Monnet                  fail=fail, include_stderr=include_stderr)
446417ec264SJakub Kicinski
447417ec264SJakub Kicinski    def set_ethtool_tc_offloads(self, enable, fail=True):
448417ec264SJakub Kicinski        args = "hw-tc-offload %s" % ("on" if enable else "off")
449417ec264SJakub Kicinski        return ethtool(self, "-K", args, fail=fail)
450417ec264SJakub Kicinski
451417ec264SJakub Kicinski################################################################################
452417ec264SJakub Kicinskidef clean_up():
4537fedbb7cSJakub Kicinski    global files, netns, devs
4547fedbb7cSJakub Kicinski
455417ec264SJakub Kicinski    for dev in devs:
456417ec264SJakub Kicinski        dev.remove()
457417ec264SJakub Kicinski    for f in files:
458417ec264SJakub Kicinski        cmd("rm -f %s" % (f))
459752d7b45SJakub Kicinski    for ns in netns:
460752d7b45SJakub Kicinski        cmd("ip netns delete %s" % (ns))
4617fedbb7cSJakub Kicinski    files = []
4627fedbb7cSJakub Kicinski    netns = []
463417ec264SJakub Kicinski
464417ec264SJakub Kicinskidef pin_prog(file_name, idx=0):
465417ec264SJakub Kicinski    progs = bpftool_prog_list(expected=(idx + 1))
466417ec264SJakub Kicinski    prog = progs[idx]
467417ec264SJakub Kicinski    bpftool("prog pin id %d %s" % (prog["id"], file_name))
468417ec264SJakub Kicinski    files.append(file_name)
469417ec264SJakub Kicinski
470417ec264SJakub Kicinski    return file_name, bpf_pinned(file_name)
471417ec264SJakub Kicinski
4727fedbb7cSJakub Kicinskidef pin_map(file_name, idx=0, expected=1):
4737fedbb7cSJakub Kicinski    maps = bpftool_map_list(expected=expected)
4747fedbb7cSJakub Kicinski    m = maps[idx]
4757fedbb7cSJakub Kicinski    bpftool("map pin id %d %s" % (m["id"], file_name))
4767fedbb7cSJakub Kicinski    files.append(file_name)
4777fedbb7cSJakub Kicinski
4787fedbb7cSJakub Kicinski    return file_name, bpf_pinned(file_name)
4797fedbb7cSJakub Kicinski
4807fedbb7cSJakub Kicinskidef check_dev_info_removed(prog_file=None, map_file=None):
481752d7b45SJakub Kicinski    bpftool_prog_list(expected=0)
4827fedbb7cSJakub Kicinski    ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
483752d7b45SJakub Kicinski    fail(ret == 0, "Showing prog with removed device did not fail")
484752d7b45SJakub Kicinski    fail(err["error"].find("No such device") == -1,
485752d7b45SJakub Kicinski         "Showing prog with removed device expected ENODEV, error is %s" %
486752d7b45SJakub Kicinski         (err["error"]))
4877fedbb7cSJakub Kicinski
4887fedbb7cSJakub Kicinski    bpftool_map_list(expected=0)
4897fedbb7cSJakub Kicinski    ret, err = bpftool("map show pin %s" % (map_file), fail=False)
4907fedbb7cSJakub Kicinski    fail(ret == 0, "Showing map with removed device did not fail")
4917fedbb7cSJakub Kicinski    fail(err["error"].find("No such device") == -1,
4927fedbb7cSJakub Kicinski         "Showing map with removed device expected ENODEV, error is %s" %
4937fedbb7cSJakub Kicinski         (err["error"]))
4947fedbb7cSJakub Kicinski
4957fedbb7cSJakub Kicinskidef check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
4967fedbb7cSJakub Kicinski    progs = bpftool_prog_list(expected=1, ns=ns)
497752d7b45SJakub Kicinski    prog = progs[0]
498752d7b45SJakub Kicinski
499752d7b45SJakub Kicinski    fail("dev" not in prog.keys(), "Device parameters not reported")
500752d7b45SJakub Kicinski    dev = prog["dev"]
501752d7b45SJakub Kicinski    fail("ifindex" not in dev.keys(), "Device parameters not reported")
502752d7b45SJakub Kicinski    fail("ns_dev" not in dev.keys(), "Device parameters not reported")
503752d7b45SJakub Kicinski    fail("ns_inode" not in dev.keys(), "Device parameters not reported")
504752d7b45SJakub Kicinski
5057fedbb7cSJakub Kicinski    if not other_ns:
506752d7b45SJakub Kicinski        fail("ifname" not in dev.keys(), "Ifname not reported")
507752d7b45SJakub Kicinski        fail(dev["ifname"] != sim["ifname"],
508752d7b45SJakub Kicinski             "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
509752d7b45SJakub Kicinski    else:
510752d7b45SJakub Kicinski        fail("ifname" in dev.keys(), "Ifname is reported for other ns")
5117fedbb7cSJakub Kicinski
5127fedbb7cSJakub Kicinski    maps = bpftool_map_list(expected=2, ns=ns)
5137fedbb7cSJakub Kicinski    for m in maps:
5147fedbb7cSJakub Kicinski        fail("dev" not in m.keys(), "Device parameters not reported")
5157fedbb7cSJakub Kicinski        fail(dev != m["dev"], "Map's device different than program's")
516752d7b45SJakub Kicinski
517caf95228SQuentin Monnetdef check_extack(output, reference, args):
518caf95228SQuentin Monnet    if skip_extack:
519caf95228SQuentin Monnet        return
520caf95228SQuentin Monnet    lines = output.split("\n")
521caf95228SQuentin Monnet    comp = len(lines) >= 2 and lines[1] == reference
522caf95228SQuentin Monnet    fail(not comp, "Missing or incorrect netlink extack message")
523caf95228SQuentin Monnet
524caf95228SQuentin Monnetdef check_extack_nsim(output, reference, args):
525caf95228SQuentin Monnet    check_extack(output, "Error: netdevsim: " + reference, args)
526caf95228SQuentin Monnet
5279045bdc8SQuentin Monnetdef check_verifier_log(output, reference):
5289045bdc8SQuentin Monnet    lines = output.split("\n")
5299045bdc8SQuentin Monnet    for l in reversed(lines):
5309045bdc8SQuentin Monnet        if l == reference:
5319045bdc8SQuentin Monnet            return
5329045bdc8SQuentin Monnet    fail(True, "Missing or incorrect message from netdevsim in verifier log")
5339045bdc8SQuentin Monnet
534417ec264SJakub Kicinski# Parse command line
535417ec264SJakub Kicinskiparser = argparse.ArgumentParser()
536417ec264SJakub Kicinskiparser.add_argument("--log", help="output verbose log to given file")
537417ec264SJakub Kicinskiargs = parser.parse_args()
538417ec264SJakub Kicinskiif args.log:
539417ec264SJakub Kicinski    logfile = open(args.log, 'w+')
540417ec264SJakub Kicinski    logfile.write("# -*-Org-*-")
541417ec264SJakub Kicinski
542417ec264SJakub Kicinskilog("Prepare...", "", level=1)
543417ec264SJakub Kicinskilog_level_inc()
544417ec264SJakub Kicinski
545417ec264SJakub Kicinski# Check permissions
546417ec264SJakub Kicinskiskip(os.getuid() != 0, "test must be run as root")
547417ec264SJakub Kicinski
548417ec264SJakub Kicinski# Check tools
549417ec264SJakub Kicinskiret, progs = bpftool("prog", fail=False)
550417ec264SJakub Kicinskiskip(ret != 0, "bpftool not installed")
551417ec264SJakub Kicinski# Check no BPF programs are loaded
552417ec264SJakub Kicinskiskip(len(progs) != 0, "BPF programs already loaded on the system")
553417ec264SJakub Kicinski
554417ec264SJakub Kicinski# Check netdevsim
555417ec264SJakub Kicinskiret, out = cmd("modprobe netdevsim", fail=False)
556417ec264SJakub Kicinskiskip(ret != 0, "netdevsim module could not be loaded")
557417ec264SJakub Kicinski
558417ec264SJakub Kicinski# Check debugfs
559417ec264SJakub Kicinski_, out = cmd("mount")
560417ec264SJakub Kicinskiif out.find("/sys/kernel/debug type debugfs") == -1:
561417ec264SJakub Kicinski    cmd("mount -t debugfs none /sys/kernel/debug")
562417ec264SJakub Kicinski
563417ec264SJakub Kicinski# Check samples are compiled
5647fedbb7cSJakub Kicinskisamples = ["sample_ret0.o", "sample_map_ret0.o"]
565417ec264SJakub Kicinskifor s in samples:
566417ec264SJakub Kicinski    ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
567417ec264SJakub Kicinski    skip(ret != 0, "sample %s/%s not found, please compile it" %
568417ec264SJakub Kicinski         (bpf_test_dir, s))
569417ec264SJakub Kicinski
570caf95228SQuentin Monnet# Check if iproute2 is built with libmnl (needed by extack support)
571caf95228SQuentin Monnet_, _, err = cmd("tc qdisc delete dev lo handle 0",
572caf95228SQuentin Monnet                fail=False, include_stderr=True)
573caf95228SQuentin Monnetif err.find("Error: Failed to find qdisc with specified handle.") == -1:
574caf95228SQuentin Monnet    print("Warning: no extack message in iproute2 output, libmnl missing?")
575caf95228SQuentin Monnet    log("Warning: no extack message in iproute2 output, libmnl missing?", "")
576caf95228SQuentin Monnet    skip_extack = True
577caf95228SQuentin Monnet
578752d7b45SJakub Kicinski# Check if net namespaces seem to work
579752d7b45SJakub Kicinskins = mknetns()
580752d7b45SJakub Kicinskiskip(ns is None, "Could not create a net namespace")
581752d7b45SJakub Kicinskicmd("ip netns delete %s" % (ns))
582752d7b45SJakub Kicinskinetns = []
583752d7b45SJakub Kicinski
584417ec264SJakub Kicinskitry:
585417ec264SJakub Kicinski    obj = bpf_obj("sample_ret0.o")
586417ec264SJakub Kicinski    bytecode = bpf_bytecode("1,6 0 0 4294967295,")
587417ec264SJakub Kicinski
588417ec264SJakub Kicinski    start_test("Test destruction of generic XDP...")
589417ec264SJakub Kicinski    sim = NetdevSim()
590417ec264SJakub Kicinski    sim.set_xdp(obj, "generic")
591417ec264SJakub Kicinski    sim.remove()
592417ec264SJakub Kicinski    bpftool_prog_list_wait(expected=0)
593417ec264SJakub Kicinski
594417ec264SJakub Kicinski    sim = NetdevSim()
595417ec264SJakub Kicinski    sim.tc_add_ingress()
596417ec264SJakub Kicinski
597417ec264SJakub Kicinski    start_test("Test TC non-offloaded...")
598417ec264SJakub Kicinski    ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
599417ec264SJakub Kicinski    fail(ret != 0, "Software TC filter did not load")
600417ec264SJakub Kicinski
601417ec264SJakub Kicinski    start_test("Test TC non-offloaded isn't getting bound...")
602417ec264SJakub Kicinski    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
603417ec264SJakub Kicinski    fail(ret != 0, "Software TC filter did not load")
604417ec264SJakub Kicinski    sim.dfs_get_bound_progs(expected=0)
605417ec264SJakub Kicinski
606417ec264SJakub Kicinski    sim.tc_flush_filters()
607417ec264SJakub Kicinski
608417ec264SJakub Kicinski    start_test("Test TC offloads are off by default...")
609caf95228SQuentin Monnet    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
610caf95228SQuentin Monnet                                         fail=False, include_stderr=True)
611417ec264SJakub Kicinski    fail(ret == 0, "TC filter loaded without enabling TC offloads")
612caf95228SQuentin Monnet    check_extack(err, "Error: TC offload is disabled on net device.", args)
613417ec264SJakub Kicinski    sim.wait_for_flush()
614417ec264SJakub Kicinski
615417ec264SJakub Kicinski    sim.set_ethtool_tc_offloads(True)
616417ec264SJakub Kicinski    sim.dfs["bpf_tc_non_bound_accept"] = "Y"
617417ec264SJakub Kicinski
618417ec264SJakub Kicinski    start_test("Test TC offload by default...")
619417ec264SJakub Kicinski    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
620417ec264SJakub Kicinski    fail(ret != 0, "Software TC filter did not load")
621417ec264SJakub Kicinski    sim.dfs_get_bound_progs(expected=0)
622417ec264SJakub Kicinski    ingress = sim.tc_show_ingress(expected=1)
623417ec264SJakub Kicinski    fltr = ingress[0]
624417ec264SJakub Kicinski    fail(not fltr["in_hw"], "Filter not offloaded by default")
625417ec264SJakub Kicinski
626417ec264SJakub Kicinski    sim.tc_flush_filters()
627417ec264SJakub Kicinski
628417ec264SJakub Kicinski    start_test("Test TC cBPF bytcode tries offload by default...")
629417ec264SJakub Kicinski    ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
630417ec264SJakub Kicinski    fail(ret != 0, "Software TC filter did not load")
631417ec264SJakub Kicinski    sim.dfs_get_bound_progs(expected=0)
632417ec264SJakub Kicinski    ingress = sim.tc_show_ingress(expected=1)
633417ec264SJakub Kicinski    fltr = ingress[0]
634417ec264SJakub Kicinski    fail(not fltr["in_hw"], "Bytecode not offloaded by default")
635417ec264SJakub Kicinski
636417ec264SJakub Kicinski    sim.tc_flush_filters()
637417ec264SJakub Kicinski    sim.dfs["bpf_tc_non_bound_accept"] = "N"
638417ec264SJakub Kicinski
639417ec264SJakub Kicinski    start_test("Test TC cBPF unbound bytecode doesn't offload...")
640caf95228SQuentin Monnet    ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
641caf95228SQuentin Monnet                                         fail=False, include_stderr=True)
642417ec264SJakub Kicinski    fail(ret == 0, "TC bytecode loaded for offload")
643caf95228SQuentin Monnet    check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
644caf95228SQuentin Monnet                      args)
645417ec264SJakub Kicinski    sim.wait_for_flush()
646417ec264SJakub Kicinski
647417ec264SJakub Kicinski    start_test("Test TC offloads work...")
6489045bdc8SQuentin Monnet    ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
6499045bdc8SQuentin Monnet                                         fail=False, include_stderr=True)
650417ec264SJakub Kicinski    fail(ret != 0, "TC filter did not load with TC offloads enabled")
6519045bdc8SQuentin Monnet    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
652417ec264SJakub Kicinski
653417ec264SJakub Kicinski    start_test("Test TC offload basics...")
654417ec264SJakub Kicinski    dfs = sim.dfs_get_bound_progs(expected=1)
655417ec264SJakub Kicinski    progs = bpftool_prog_list(expected=1)
656417ec264SJakub Kicinski    ingress = sim.tc_show_ingress(expected=1)
657417ec264SJakub Kicinski
658417ec264SJakub Kicinski    dprog = dfs[0]
659417ec264SJakub Kicinski    prog = progs[0]
660417ec264SJakub Kicinski    fltr = ingress[0]
661417ec264SJakub Kicinski    fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
662417ec264SJakub Kicinski    fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
663417ec264SJakub Kicinski    fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
664417ec264SJakub Kicinski
665417ec264SJakub Kicinski    start_test("Test TC offload is device-bound...")
666417ec264SJakub Kicinski    fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
667417ec264SJakub Kicinski    fail(prog["tag"] != fltr["tag"], "Program tags don't match")
668417ec264SJakub Kicinski    fail(fltr["id"] != dprog["id"], "Program IDs don't match")
669417ec264SJakub Kicinski    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
670417ec264SJakub Kicinski    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
671417ec264SJakub Kicinski
672417ec264SJakub Kicinski    start_test("Test disabling TC offloads is rejected while filters installed...")
673417ec264SJakub Kicinski    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
674417ec264SJakub Kicinski    fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
675417ec264SJakub Kicinski
676417ec264SJakub Kicinski    start_test("Test qdisc removal frees things...")
677417ec264SJakub Kicinski    sim.tc_flush_filters()
678417ec264SJakub Kicinski    sim.tc_show_ingress(expected=0)
679417ec264SJakub Kicinski
680417ec264SJakub Kicinski    start_test("Test disabling TC offloads is OK without filters...")
681417ec264SJakub Kicinski    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
682417ec264SJakub Kicinski    fail(ret != 0,
683417ec264SJakub Kicinski         "Driver refused to disable TC offloads without filters installed...")
684417ec264SJakub Kicinski
685417ec264SJakub Kicinski    sim.set_ethtool_tc_offloads(True)
686417ec264SJakub Kicinski
687417ec264SJakub Kicinski    start_test("Test destroying device gets rid of TC filters...")
688417ec264SJakub Kicinski    sim.cls_bpf_add_filter(obj, skip_sw=True)
689417ec264SJakub Kicinski    sim.remove()
690417ec264SJakub Kicinski    bpftool_prog_list_wait(expected=0)
691417ec264SJakub Kicinski
692417ec264SJakub Kicinski    sim = NetdevSim()
693417ec264SJakub Kicinski    sim.set_ethtool_tc_offloads(True)
694417ec264SJakub Kicinski
695417ec264SJakub Kicinski    start_test("Test destroying device gets rid of XDP...")
696417ec264SJakub Kicinski    sim.set_xdp(obj, "offload")
697417ec264SJakub Kicinski    sim.remove()
698417ec264SJakub Kicinski    bpftool_prog_list_wait(expected=0)
699417ec264SJakub Kicinski
700417ec264SJakub Kicinski    sim = NetdevSim()
701417ec264SJakub Kicinski    sim.set_ethtool_tc_offloads(True)
702417ec264SJakub Kicinski
703417ec264SJakub Kicinski    start_test("Test XDP prog reporting...")
704417ec264SJakub Kicinski    sim.set_xdp(obj, "drv")
705417ec264SJakub Kicinski    ipl = sim.ip_link_show(xdp=True)
706417ec264SJakub Kicinski    progs = bpftool_prog_list(expected=1)
707417ec264SJakub Kicinski    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
708417ec264SJakub Kicinski         "Loaded program has wrong ID")
709417ec264SJakub Kicinski
710417ec264SJakub Kicinski    start_test("Test XDP prog replace without force...")
711417ec264SJakub Kicinski    ret, _ = sim.set_xdp(obj, "drv", fail=False)
712417ec264SJakub Kicinski    fail(ret == 0, "Replaced XDP program without -force")
713417ec264SJakub Kicinski    sim.wait_for_flush(total=1)
714417ec264SJakub Kicinski
715417ec264SJakub Kicinski    start_test("Test XDP prog replace with force...")
716417ec264SJakub Kicinski    ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
717417ec264SJakub Kicinski    fail(ret != 0, "Could not replace XDP program with -force")
718417ec264SJakub Kicinski    bpftool_prog_list_wait(expected=1)
719417ec264SJakub Kicinski    ipl = sim.ip_link_show(xdp=True)
720417ec264SJakub Kicinski    progs = bpftool_prog_list(expected=1)
721417ec264SJakub Kicinski    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
722417ec264SJakub Kicinski         "Loaded program has wrong ID")
723752d7b45SJakub Kicinski    fail("dev" in progs[0].keys(),
724752d7b45SJakub Kicinski         "Device parameters reported for non-offloaded program")
725417ec264SJakub Kicinski
726417ec264SJakub Kicinski    start_test("Test XDP prog replace with bad flags...")
727caf95228SQuentin Monnet    ret, _, err = sim.set_xdp(obj, "offload", force=True,
728caf95228SQuentin Monnet                              fail=False, include_stderr=True)
729417ec264SJakub Kicinski    fail(ret == 0, "Replaced XDP program with a program in different mode")
730caf95228SQuentin Monnet    check_extack_nsim(err, "program loaded with different flags.", args)
731caf95228SQuentin Monnet    ret, _, err = sim.set_xdp(obj, "", force=True,
732caf95228SQuentin Monnet                              fail=False, include_stderr=True)
733417ec264SJakub Kicinski    fail(ret == 0, "Replaced XDP program with a program in different mode")
734caf95228SQuentin Monnet    check_extack_nsim(err, "program loaded with different flags.", args)
735417ec264SJakub Kicinski
736417ec264SJakub Kicinski    start_test("Test XDP prog remove with bad flags...")
737caf95228SQuentin Monnet    ret, _, err = sim.unset_xdp("offload", force=True,
738caf95228SQuentin Monnet                                fail=False, include_stderr=True)
739417ec264SJakub Kicinski    fail(ret == 0, "Removed program with a bad mode mode")
740caf95228SQuentin Monnet    check_extack_nsim(err, "program loaded with different flags.", args)
741caf95228SQuentin Monnet    ret, _, err = sim.unset_xdp("", force=True,
742caf95228SQuentin Monnet                                fail=False, include_stderr=True)
743417ec264SJakub Kicinski    fail(ret == 0, "Removed program with a bad mode mode")
744caf95228SQuentin Monnet    check_extack_nsim(err, "program loaded with different flags.", args)
745417ec264SJakub Kicinski
746417ec264SJakub Kicinski    start_test("Test MTU restrictions...")
747417ec264SJakub Kicinski    ret, _ = sim.set_mtu(9000, fail=False)
748417ec264SJakub Kicinski    fail(ret == 0,
749417ec264SJakub Kicinski         "Driver should refuse to increase MTU to 9000 with XDP loaded...")
750417ec264SJakub Kicinski    sim.unset_xdp("drv")
751417ec264SJakub Kicinski    bpftool_prog_list_wait(expected=0)
752417ec264SJakub Kicinski    sim.set_mtu(9000)
753caf95228SQuentin Monnet    ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True)
754417ec264SJakub Kicinski    fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
755caf95228SQuentin Monnet    check_extack_nsim(err, "MTU too large w/ XDP enabled.", args)
756417ec264SJakub Kicinski    sim.set_mtu(1500)
757417ec264SJakub Kicinski
758417ec264SJakub Kicinski    sim.wait_for_flush()
759417ec264SJakub Kicinski    start_test("Test XDP offload...")
7609045bdc8SQuentin Monnet    _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
761417ec264SJakub Kicinski    ipl = sim.ip_link_show(xdp=True)
762417ec264SJakub Kicinski    link_xdp = ipl["xdp"]["prog"]
763417ec264SJakub Kicinski    progs = bpftool_prog_list(expected=1)
764417ec264SJakub Kicinski    prog = progs[0]
765417ec264SJakub Kicinski    fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
7669045bdc8SQuentin Monnet    check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
767417ec264SJakub Kicinski
768417ec264SJakub Kicinski    start_test("Test XDP offload is device bound...")
769417ec264SJakub Kicinski    dfs = sim.dfs_get_bound_progs(expected=1)
770417ec264SJakub Kicinski    dprog = dfs[0]
771417ec264SJakub Kicinski
772417ec264SJakub Kicinski    fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
773417ec264SJakub Kicinski    fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
774417ec264SJakub Kicinski    fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
775417ec264SJakub Kicinski    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
776417ec264SJakub Kicinski    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
777417ec264SJakub Kicinski
778417ec264SJakub Kicinski    start_test("Test removing XDP program many times...")
779417ec264SJakub Kicinski    sim.unset_xdp("offload")
780417ec264SJakub Kicinski    sim.unset_xdp("offload")
781417ec264SJakub Kicinski    sim.unset_xdp("drv")
782417ec264SJakub Kicinski    sim.unset_xdp("drv")
783417ec264SJakub Kicinski    sim.unset_xdp("")
784417ec264SJakub Kicinski    sim.unset_xdp("")
785417ec264SJakub Kicinski    bpftool_prog_list_wait(expected=0)
786417ec264SJakub Kicinski
787417ec264SJakub Kicinski    start_test("Test attempt to use a program for a wrong device...")
788417ec264SJakub Kicinski    sim2 = NetdevSim()
789417ec264SJakub Kicinski    sim2.set_xdp(obj, "offload")
790417ec264SJakub Kicinski    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
791417ec264SJakub Kicinski
792caf95228SQuentin Monnet    ret, _, err = sim.set_xdp(pinned, "offload",
793caf95228SQuentin Monnet                              fail=False, include_stderr=True)
794417ec264SJakub Kicinski    fail(ret == 0, "Pinned program loaded for a different device accepted")
795caf95228SQuentin Monnet    check_extack_nsim(err, "program bound to different dev.", args)
796417ec264SJakub Kicinski    sim2.remove()
797caf95228SQuentin Monnet    ret, _, err = sim.set_xdp(pinned, "offload",
798caf95228SQuentin Monnet                              fail=False, include_stderr=True)
799417ec264SJakub Kicinski    fail(ret == 0, "Pinned program loaded for a removed device accepted")
800caf95228SQuentin Monnet    check_extack_nsim(err, "xdpoffload of non-bound program.", args)
801417ec264SJakub Kicinski    rm(pin_file)
802417ec264SJakub Kicinski    bpftool_prog_list_wait(expected=0)
803417ec264SJakub Kicinski
804417ec264SJakub Kicinski    start_test("Test mixing of TC and XDP...")
805417ec264SJakub Kicinski    sim.tc_add_ingress()
806417ec264SJakub Kicinski    sim.set_xdp(obj, "offload")
807caf95228SQuentin Monnet    ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
808caf95228SQuentin Monnet                                         fail=False, include_stderr=True)
809417ec264SJakub Kicinski    fail(ret == 0, "Loading TC when XDP active should fail")
810caf95228SQuentin Monnet    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
811417ec264SJakub Kicinski    sim.unset_xdp("offload")
812417ec264SJakub Kicinski    sim.wait_for_flush()
813417ec264SJakub Kicinski
814417ec264SJakub Kicinski    sim.cls_bpf_add_filter(obj, skip_sw=True)
815caf95228SQuentin Monnet    ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
816417ec264SJakub Kicinski    fail(ret == 0, "Loading XDP when TC active should fail")
817caf95228SQuentin Monnet    check_extack_nsim(err, "TC program is already loaded.", args)
818417ec264SJakub Kicinski
819417ec264SJakub Kicinski    start_test("Test binding TC from pinned...")
820417ec264SJakub Kicinski    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
821417ec264SJakub Kicinski    sim.tc_flush_filters(bound=1, total=1)
822417ec264SJakub Kicinski    sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
823417ec264SJakub Kicinski    sim.tc_flush_filters(bound=1, total=1)
824417ec264SJakub Kicinski
825417ec264SJakub Kicinski    start_test("Test binding XDP from pinned...")
826417ec264SJakub Kicinski    sim.set_xdp(obj, "offload")
827417ec264SJakub Kicinski    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
828417ec264SJakub Kicinski
829417ec264SJakub Kicinski    sim.set_xdp(pinned, "offload", force=True)
830417ec264SJakub Kicinski    sim.unset_xdp("offload")
831417ec264SJakub Kicinski    sim.set_xdp(pinned, "offload", force=True)
832417ec264SJakub Kicinski    sim.unset_xdp("offload")
833417ec264SJakub Kicinski
834417ec264SJakub Kicinski    start_test("Test offload of wrong type fails...")
835417ec264SJakub Kicinski    ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
836417ec264SJakub Kicinski    fail(ret == 0, "Managed to attach XDP program to TC")
837417ec264SJakub Kicinski
838417ec264SJakub Kicinski    start_test("Test asking for TC offload of two filters...")
839417ec264SJakub Kicinski    sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
840caf95228SQuentin Monnet    ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
841caf95228SQuentin Monnet                                         fail=False, include_stderr=True)
842fba961abSDavid S. Miller    fail(ret == 0, "Managed to offload two TC filters at the same time")
843caf95228SQuentin Monnet    check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
844417ec264SJakub Kicinski
845417ec264SJakub Kicinski    sim.tc_flush_filters(bound=2, total=2)
846417ec264SJakub Kicinski
847417ec264SJakub Kicinski    start_test("Test if netdev removal waits for translation...")
848417ec264SJakub Kicinski    delay_msec = 500
849417ec264SJakub Kicinski    sim.dfs["bpf_bind_verifier_delay"] = delay_msec
850417ec264SJakub Kicinski    start = time.time()
851417ec264SJakub Kicinski    cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
852417ec264SJakub Kicinski               (sim['ifname'], obj)
853417ec264SJakub Kicinski    tc_proc = cmd(cmd_line, background=True, fail=False)
854417ec264SJakub Kicinski    # Wait for the verifier to start
855417ec264SJakub Kicinski    while sim.dfs_num_bound_progs() <= 2:
856417ec264SJakub Kicinski        pass
857417ec264SJakub Kicinski    sim.remove()
858417ec264SJakub Kicinski    end = time.time()
859417ec264SJakub Kicinski    ret, _ = cmd_result(tc_proc, fail=False)
860417ec264SJakub Kicinski    time_diff = end - start
861417ec264SJakub Kicinski    log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
862417ec264SJakub Kicinski
863417ec264SJakub Kicinski    fail(ret == 0, "Managed to load TC filter on a unregistering device")
864417ec264SJakub Kicinski    delay_sec = delay_msec * 0.001
865417ec264SJakub Kicinski    fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
866417ec264SJakub Kicinski         (time_diff, delay_sec))
867417ec264SJakub Kicinski
868752d7b45SJakub Kicinski    # Remove all pinned files and reinstantiate the netdev
869752d7b45SJakub Kicinski    clean_up()
870752d7b45SJakub Kicinski    bpftool_prog_list_wait(expected=0)
871752d7b45SJakub Kicinski
872752d7b45SJakub Kicinski    sim = NetdevSim()
8737fedbb7cSJakub Kicinski    map_obj = bpf_obj("sample_map_ret0.o")
8747fedbb7cSJakub Kicinski    start_test("Test loading program with maps...")
8757fedbb7cSJakub Kicinski    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
876752d7b45SJakub Kicinski
877752d7b45SJakub Kicinski    start_test("Test bpftool bound info reporting (own ns)...")
878752d7b45SJakub Kicinski    check_dev_info(False, "")
879752d7b45SJakub Kicinski
880752d7b45SJakub Kicinski    start_test("Test bpftool bound info reporting (other ns)...")
881752d7b45SJakub Kicinski    ns = mknetns()
882752d7b45SJakub Kicinski    sim.set_ns(ns)
883752d7b45SJakub Kicinski    check_dev_info(True, "")
884752d7b45SJakub Kicinski
885752d7b45SJakub Kicinski    start_test("Test bpftool bound info reporting (remote ns)...")
886752d7b45SJakub Kicinski    check_dev_info(False, ns)
887752d7b45SJakub Kicinski
888752d7b45SJakub Kicinski    start_test("Test bpftool bound info reporting (back to own ns)...")
889752d7b45SJakub Kicinski    sim.set_ns("")
890752d7b45SJakub Kicinski    check_dev_info(False, "")
891752d7b45SJakub Kicinski
8927fedbb7cSJakub Kicinski    prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
8937fedbb7cSJakub Kicinski    map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
894752d7b45SJakub Kicinski    sim.remove()
895752d7b45SJakub Kicinski
896752d7b45SJakub Kicinski    start_test("Test bpftool bound info reporting (removed dev)...")
8977fedbb7cSJakub Kicinski    check_dev_info_removed(prog_file=prog_file, map_file=map_file)
8987fedbb7cSJakub Kicinski
8997fedbb7cSJakub Kicinski    # Remove all pinned files and reinstantiate the netdev
9007fedbb7cSJakub Kicinski    clean_up()
9017fedbb7cSJakub Kicinski    bpftool_prog_list_wait(expected=0)
9027fedbb7cSJakub Kicinski
9037fedbb7cSJakub Kicinski    sim = NetdevSim()
9047fedbb7cSJakub Kicinski
9057fedbb7cSJakub Kicinski    start_test("Test map update (no flags)...")
9067fedbb7cSJakub Kicinski    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
9077fedbb7cSJakub Kicinski    maps = bpftool_map_list(expected=2)
9087fedbb7cSJakub Kicinski    array = maps[0] if maps[0]["type"] == "array" else maps[1]
9097fedbb7cSJakub Kicinski    htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
9107fedbb7cSJakub Kicinski    for m in maps:
9117fedbb7cSJakub Kicinski        for i in range(2):
9127fedbb7cSJakub Kicinski            bpftool("map update id %d key %s value %s" %
9137fedbb7cSJakub Kicinski                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
9147fedbb7cSJakub Kicinski
9157fedbb7cSJakub Kicinski    for m in maps:
9167fedbb7cSJakub Kicinski        ret, _ = bpftool("map update id %d key %s value %s" %
9177fedbb7cSJakub Kicinski                         (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
9187fedbb7cSJakub Kicinski                         fail=False)
9197fedbb7cSJakub Kicinski        fail(ret == 0, "added too many entries")
9207fedbb7cSJakub Kicinski
9217fedbb7cSJakub Kicinski    start_test("Test map update (exists)...")
9227fedbb7cSJakub Kicinski    for m in maps:
9237fedbb7cSJakub Kicinski        for i in range(2):
9247fedbb7cSJakub Kicinski            bpftool("map update id %d key %s value %s exist" %
9257fedbb7cSJakub Kicinski                    (m["id"], int2str("I", i), int2str("Q", i * 3)))
9267fedbb7cSJakub Kicinski
9277fedbb7cSJakub Kicinski    for m in maps:
9287fedbb7cSJakub Kicinski        ret, err = bpftool("map update id %d key %s value %s exist" %
9297fedbb7cSJakub Kicinski                           (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
9307fedbb7cSJakub Kicinski                           fail=False)
9317fedbb7cSJakub Kicinski        fail(ret == 0, "updated non-existing key")
9327fedbb7cSJakub Kicinski        fail(err["error"].find("No such file or directory") == -1,
9337fedbb7cSJakub Kicinski             "expected ENOENT, error is '%s'" % (err["error"]))
9347fedbb7cSJakub Kicinski
9357fedbb7cSJakub Kicinski    start_test("Test map update (noexist)...")
9367fedbb7cSJakub Kicinski    for m in maps:
9377fedbb7cSJakub Kicinski        for i in range(2):
9387fedbb7cSJakub Kicinski            ret, err = bpftool("map update id %d key %s value %s noexist" %
9397fedbb7cSJakub Kicinski                               (m["id"], int2str("I", i), int2str("Q", i * 3)),
9407fedbb7cSJakub Kicinski                               fail=False)
9417fedbb7cSJakub Kicinski        fail(ret == 0, "updated existing key")
9427fedbb7cSJakub Kicinski        fail(err["error"].find("File exists") == -1,
9437fedbb7cSJakub Kicinski             "expected EEXIST, error is '%s'" % (err["error"]))
9447fedbb7cSJakub Kicinski
9457fedbb7cSJakub Kicinski    start_test("Test map dump...")
9467fedbb7cSJakub Kicinski    for m in maps:
9477fedbb7cSJakub Kicinski        _, entries = bpftool("map dump id %d" % (m["id"]))
9487fedbb7cSJakub Kicinski        for i in range(2):
9497fedbb7cSJakub Kicinski            key = str2int(entries[i]["key"])
9507fedbb7cSJakub Kicinski            fail(key != i, "expected key %d, got %d" % (key, i))
9517fedbb7cSJakub Kicinski            val = str2int(entries[i]["value"])
9527fedbb7cSJakub Kicinski            fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
9537fedbb7cSJakub Kicinski
9547fedbb7cSJakub Kicinski    start_test("Test map getnext...")
9557fedbb7cSJakub Kicinski    for m in maps:
9567fedbb7cSJakub Kicinski        _, entry = bpftool("map getnext id %d" % (m["id"]))
9577fedbb7cSJakub Kicinski        key = str2int(entry["next_key"])
9587fedbb7cSJakub Kicinski        fail(key != 0, "next key %d, expected %d" % (key, 0))
9597fedbb7cSJakub Kicinski        _, entry = bpftool("map getnext id %d key %s" %
9607fedbb7cSJakub Kicinski                           (m["id"], int2str("I", 0)))
9617fedbb7cSJakub Kicinski        key = str2int(entry["next_key"])
9627fedbb7cSJakub Kicinski        fail(key != 1, "next key %d, expected %d" % (key, 1))
9637fedbb7cSJakub Kicinski        ret, err = bpftool("map getnext id %d key %s" %
9647fedbb7cSJakub Kicinski                           (m["id"], int2str("I", 1)), fail=False)
9657fedbb7cSJakub Kicinski        fail(ret == 0, "got next key past the end of map")
9667fedbb7cSJakub Kicinski        fail(err["error"].find("No such file or directory") == -1,
9677fedbb7cSJakub Kicinski             "expected ENOENT, error is '%s'" % (err["error"]))
9687fedbb7cSJakub Kicinski
9697fedbb7cSJakub Kicinski    start_test("Test map delete (htab)...")
9707fedbb7cSJakub Kicinski    for i in range(2):
9717fedbb7cSJakub Kicinski        bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
9727fedbb7cSJakub Kicinski
9737fedbb7cSJakub Kicinski    start_test("Test map delete (array)...")
9747fedbb7cSJakub Kicinski    for i in range(2):
9757fedbb7cSJakub Kicinski        ret, err = bpftool("map delete id %d key %s" %
9767fedbb7cSJakub Kicinski                           (htab["id"], int2str("I", i)), fail=False)
9777fedbb7cSJakub Kicinski        fail(ret == 0, "removed entry from an array")
9787fedbb7cSJakub Kicinski        fail(err["error"].find("No such file or directory") == -1,
9797fedbb7cSJakub Kicinski             "expected ENOENT, error is '%s'" % (err["error"]))
9807fedbb7cSJakub Kicinski
9817fedbb7cSJakub Kicinski    start_test("Test map remove...")
9827fedbb7cSJakub Kicinski    sim.unset_xdp("offload")
9837fedbb7cSJakub Kicinski    bpftool_map_list_wait(expected=0)
9847fedbb7cSJakub Kicinski    sim.remove()
9857fedbb7cSJakub Kicinski
9867fedbb7cSJakub Kicinski    sim = NetdevSim()
9877fedbb7cSJakub Kicinski    sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
9887fedbb7cSJakub Kicinski    sim.remove()
9897fedbb7cSJakub Kicinski    bpftool_map_list_wait(expected=0)
9907fedbb7cSJakub Kicinski
9917fedbb7cSJakub Kicinski    start_test("Test map creation fail path...")
9927fedbb7cSJakub Kicinski    sim = NetdevSim()
9937fedbb7cSJakub Kicinski    sim.dfs["bpf_map_accept"] = "N"
9947fedbb7cSJakub Kicinski    ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
9957fedbb7cSJakub Kicinski    fail(ret == 0,
9967fedbb7cSJakub Kicinski         "netdevsim didn't refuse to create a map with offload disabled")
997752d7b45SJakub Kicinski
998417ec264SJakub Kicinski    print("%s: OK" % (os.path.basename(__file__)))
999417ec264SJakub Kicinski
1000417ec264SJakub Kicinskifinally:
1001417ec264SJakub Kicinski    log("Clean up...", "", level=1)
1002417ec264SJakub Kicinski    log_level_inc()
1003417ec264SJakub Kicinski    clean_up()
1004