Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net
[linux-2.6-microblaze.git] / tools / testing / selftests / bpf / test_offload.py
1 #!/usr/bin/python3
2
3 # Copyright (C) 2017 Netronome Systems, Inc.
4 # Copyright (c) 2019 Mellanox Technologies. All rights reserved
5 #
6 # This software is licensed under the GNU General License Version 2,
7 # June 1991 as shown in the file COPYING in the top-level directory of this
8 # source tree.
9 #
10 # THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
11 # WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
12 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
13 # FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
14 # OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
15 # THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16
17 from datetime import datetime
18 import argparse
19 import errno
20 import json
21 import os
22 import pprint
23 import random
24 import re
25 import stat
26 import string
27 import struct
28 import subprocess
29 import time
30 import traceback
31
32 logfile = None
33 log_level = 1
34 skip_extack = False
35 bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
36 pp = pprint.PrettyPrinter()
37 devs = [] # devices we created for clean up
38 files = [] # files to be removed
39 netns = [] # net namespaces to be removed
40
41 def log_get_sec(level=0):
42     return "*" * (log_level + level)
43
44 def log_level_inc(add=1):
45     global log_level
46     log_level += add
47
48 def log_level_dec(sub=1):
49     global log_level
50     log_level -= sub
51
52 def log_level_set(level):
53     global log_level
54     log_level = level
55
56 def log(header, data, level=None):
57     """
58     Output to an optional log.
59     """
60     if logfile is None:
61         return
62     if level is not None:
63         log_level_set(level)
64
65     if not isinstance(data, str):
66         data = pp.pformat(data)
67
68     if len(header):
69         logfile.write("\n" + log_get_sec() + " ")
70         logfile.write(header)
71     if len(header) and len(data.strip()):
72         logfile.write("\n")
73     logfile.write(data)
74
75 def skip(cond, msg):
76     if not cond:
77         return
78     print("SKIP: " + msg)
79     log("SKIP: " + msg, "", level=1)
80     os.sys.exit(0)
81
82 def fail(cond, msg):
83     if not cond:
84         return
85     print("FAIL: " + msg)
86     tb = "".join(traceback.extract_stack().format())
87     print(tb)
88     log("FAIL: " + msg, tb, level=1)
89     os.sys.exit(1)
90
91 def start_test(msg):
92     log(msg, "", level=1)
93     log_level_inc()
94     print(msg)
95
96 def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
97     """
98     Run a command in subprocess and return tuple of (retval, stdout);
99     optionally return stderr as well as third value.
100     """
101     proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
102                             stderr=subprocess.PIPE)
103     if background:
104         msg = "%s START: %s" % (log_get_sec(1),
105                                 datetime.now().strftime("%H:%M:%S.%f"))
106         log("BKG " + proc.args, msg)
107         return proc
108
109     return cmd_result(proc, include_stderr=include_stderr, fail=fail)
110
111 def cmd_result(proc, include_stderr=False, fail=False):
112     stdout, stderr = proc.communicate()
113     stdout = stdout.decode("utf-8")
114     stderr = stderr.decode("utf-8")
115     proc.stdout.close()
116     proc.stderr.close()
117
118     stderr = "\n" + stderr
119     if stderr[-1] == "\n":
120         stderr = stderr[:-1]
121
122     sec = log_get_sec(1)
123     log("CMD " + proc.args,
124         "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
125         (proc.returncode, sec, stdout, sec, stderr,
126          sec, datetime.now().strftime("%H:%M:%S.%f")))
127
128     if proc.returncode != 0 and fail:
129         if len(stderr) > 0 and stderr[-1] == "\n":
130             stderr = stderr[:-1]
131         raise Exception("Command failed: %s\n%s" % (proc.args, stderr))
132
133     if include_stderr:
134         return proc.returncode, stdout, stderr
135     else:
136         return proc.returncode, stdout
137
138 def rm(f):
139     cmd("rm -f %s" % (f))
140     if f in files:
141         files.remove(f)
142
143 def tool(name, args, flags, JSON=True, ns="", fail=True, include_stderr=False):
144     params = ""
145     if JSON:
146         params += "%s " % (flags["json"])
147
148     if ns != "":
149         ns = "ip netns exec %s " % (ns)
150
151     if include_stderr:
152         ret, stdout, stderr = cmd(ns + name + " " + params + args,
153                                   fail=fail, include_stderr=True)
154     else:
155         ret, stdout = cmd(ns + name + " " + params + args,
156                           fail=fail, include_stderr=False)
157
158     if JSON and len(stdout.strip()) != 0:
159         out = json.loads(stdout)
160     else:
161         out = stdout
162
163     if include_stderr:
164         return ret, out, stderr
165     else:
166         return ret, out
167
168 def bpftool(args, JSON=True, ns="", fail=True, include_stderr=False):
169     return tool("bpftool", args, {"json":"-p"}, JSON=JSON, ns=ns,
170                 fail=fail, include_stderr=include_stderr)
171
172 def bpftool_prog_list(expected=None, ns=""):
173     _, progs = bpftool("prog show", JSON=True, ns=ns, fail=True)
174     # Remove the base progs
175     for p in base_progs:
176         if p in progs:
177             progs.remove(p)
178     if expected is not None:
179         if len(progs) != expected:
180             fail(True, "%d BPF programs loaded, expected %d" %
181                  (len(progs), expected))
182     return progs
183
184 def bpftool_map_list(expected=None, ns=""):
185     _, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
186     # Remove the base maps
187     for m in base_maps:
188         if m in maps:
189             maps.remove(m)
190     if expected is not None:
191         if len(maps) != expected:
192             fail(True, "%d BPF maps loaded, expected %d" %
193                  (len(maps), expected))
194     return maps
195
196 def bpftool_prog_list_wait(expected=0, n_retry=20):
197     for i in range(n_retry):
198         nprogs = len(bpftool_prog_list())
199         if nprogs == expected:
200             return
201         time.sleep(0.05)
202     raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
203
204 def bpftool_map_list_wait(expected=0, n_retry=20):
205     for i in range(n_retry):
206         nmaps = len(bpftool_map_list())
207         if nmaps == expected:
208             return
209         time.sleep(0.05)
210     raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))
211
212 def bpftool_prog_load(sample, file_name, maps=[], prog_type="xdp", dev=None,
213                       fail=True, include_stderr=False):
214     args = "prog load %s %s" % (os.path.join(bpf_test_dir, sample), file_name)
215     if prog_type is not None:
216         args += " type " + prog_type
217     if dev is not None:
218         args += " dev " + dev
219     if len(maps):
220         args += " map " + " map ".join(maps)
221
222     res = bpftool(args, fail=fail, include_stderr=include_stderr)
223     if res[0] == 0:
224         files.append(file_name)
225     return res
226
227 def ip(args, force=False, JSON=True, ns="", fail=True, include_stderr=False):
228     if force:
229         args = "-force " + args
230     return tool("ip", args, {"json":"-j"}, JSON=JSON, ns=ns,
231                 fail=fail, include_stderr=include_stderr)
232
233 def tc(args, JSON=True, ns="", fail=True, include_stderr=False):
234     return tool("tc", args, {"json":"-p"}, JSON=JSON, ns=ns,
235                 fail=fail, include_stderr=include_stderr)
236
237 def ethtool(dev, opt, args, fail=True):
238     return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)
239
240 def bpf_obj(name, sec=".text", path=bpf_test_dir,):
241     return "obj %s sec %s" % (os.path.join(path, name), sec)
242
243 def bpf_pinned(name):
244     return "pinned %s" % (name)
245
246 def bpf_bytecode(bytecode):
247     return "bytecode \"%s\"" % (bytecode)
248
249 def mknetns(n_retry=10):
250     for i in range(n_retry):
251         name = ''.join([random.choice(string.ascii_letters) for i in range(8)])
252         ret, _ = ip("netns add %s" % (name), fail=False)
253         if ret == 0:
254             netns.append(name)
255             return name
256     return None
257
258 def int2str(fmt, val):
259     ret = []
260     for b in struct.pack(fmt, val):
261         ret.append(int(b))
262     return " ".join(map(lambda x: str(x), ret))
263
264 def str2int(strtab):
265     inttab = []
266     for i in strtab:
267         inttab.append(int(i, 16))
268     ba = bytearray(inttab)
269     if len(strtab) == 4:
270         fmt = "I"
271     elif len(strtab) == 8:
272         fmt = "Q"
273     else:
274         raise Exception("String array of len %d can't be unpacked to an int" %
275                         (len(strtab)))
276     return struct.unpack(fmt, ba)[0]
277
278 class DebugfsDir:
279     """
280     Class for accessing DebugFS directories as a dictionary.
281     """
282
283     def __init__(self, path):
284         self.path = path
285         self._dict = self._debugfs_dir_read(path)
286
287     def __len__(self):
288         return len(self._dict.keys())
289
290     def __getitem__(self, key):
291         if type(key) is int:
292             key = list(self._dict.keys())[key]
293         return self._dict[key]
294
295     def __setitem__(self, key, value):
296         log("DebugFS set %s = %s" % (key, value), "")
297         log_level_inc()
298
299         cmd("echo '%s' > %s/%s" % (value, self.path, key))
300         log_level_dec()
301
302         _, out = cmd('cat %s/%s' % (self.path, key))
303         self._dict[key] = out.strip()
304
305     def _debugfs_dir_read(self, path):
306         dfs = {}
307
308         log("DebugFS state for %s" % (path), "")
309         log_level_inc(add=2)
310
311         _, out = cmd('ls ' + path)
312         for f in out.split():
313             if f == "ports":
314                 continue
315
316             p = os.path.join(path, f)
317             if not os.stat(p).st_mode & stat.S_IRUSR:
318                 continue
319
320             if os.path.isfile(p) and os.access(p, os.R_OK):
321                 _, out = cmd('cat %s/%s' % (path, f))
322                 dfs[f] = out.strip()
323             elif os.path.isdir(p):
324                 dfs[f] = DebugfsDir(p)
325             else:
326                 raise Exception("%s is neither file nor directory" % (p))
327
328         log_level_dec()
329         log("DebugFS state", dfs)
330         log_level_dec()
331
332         return dfs
333
334 class NetdevSimDev:
335     """
336     Class for netdevsim bus device and its attributes.
337     """
338
339     def __init__(self, port_count=1):
340         addr = 0
341         while True:
342             try:
343                 with open("/sys/bus/netdevsim/new_device", "w") as f:
344                     f.write("%u %u" % (addr, port_count))
345             except OSError as e:
346                 if e.errno == errno.ENOSPC:
347                     addr += 1
348                     continue
349                 raise e
350             break
351         self.addr = addr
352
353         # As probe of netdevsim device might happen from a workqueue,
354         # so wait here until all netdevs appear.
355         self.wait_for_netdevs(port_count)
356
357         ret, out = cmd("udevadm settle", fail=False)
358         if ret:
359             raise Exception("udevadm settle failed")
360         ifnames = self.get_ifnames()
361
362         devs.append(self)
363         self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
364
365         self.nsims = []
366         for port_index in range(port_count):
367             self.nsims.append(NetdevSim(self, port_index, ifnames[port_index]))
368
369     def get_ifnames(self):
370         ifnames = []
371         listdir = os.listdir("/sys/bus/netdevsim/devices/netdevsim%u/net/" % self.addr)
372         for ifname in listdir:
373             ifnames.append(ifname)
374         ifnames.sort()
375         return ifnames
376
377     def wait_for_netdevs(self, port_count):
378         timeout = 5
379         timeout_start = time.time()
380
381         while True:
382             try:
383                 ifnames = self.get_ifnames()
384             except FileNotFoundError as e:
385                 ifnames = []
386             if len(ifnames) == port_count:
387                 break
388             if time.time() < timeout_start + timeout:
389                 continue
390             raise Exception("netdevices did not appear within timeout")
391
392     def dfs_num_bound_progs(self):
393         path = os.path.join(self.dfs_dir, "bpf_bound_progs")
394         _, progs = cmd('ls %s' % (path))
395         return len(progs.split())
396
397     def dfs_get_bound_progs(self, expected):
398         progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
399         if expected is not None:
400             if len(progs) != expected:
401                 fail(True, "%d BPF programs bound, expected %d" %
402                      (len(progs), expected))
403         return progs
404
405     def remove(self):
406         with open("/sys/bus/netdevsim/del_device", "w") as f:
407             f.write("%u" % self.addr)
408         devs.remove(self)
409
410     def remove_nsim(self, nsim):
411         self.nsims.remove(nsim)
412         with open("/sys/bus/netdevsim/devices/netdevsim%u/del_port" % self.addr ,"w") as f:
413             f.write("%u" % nsim.port_index)
414
415 class NetdevSim:
416     """
417     Class for netdevsim netdevice and its attributes.
418     """
419
420     def __init__(self, nsimdev, port_index, ifname):
421         # In case udev renamed the netdev to according to new schema,
422         # check if the name matches the port_index.
423         nsimnamere = re.compile("eni\d+np(\d+)")
424         match = nsimnamere.match(ifname)
425         if match and int(match.groups()[0]) != port_index + 1:
426             raise Exception("netdevice name mismatches the expected one")
427
428         self.nsimdev = nsimdev
429         self.port_index = port_index
430         self.ns = ""
431         self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
432         self.dfs_refresh()
433         _, [self.dev] = ip("link show dev %s" % ifname)
434
435     def __getitem__(self, key):
436         return self.dev[key]
437
438     def remove(self):
439         self.nsimdev.remove_nsim(self)
440
441     def dfs_refresh(self):
442         self.dfs = DebugfsDir(self.dfs_dir)
443         return self.dfs
444
445     def dfs_read(self, f):
446         path = os.path.join(self.dfs_dir, f)
447         _, data = cmd('cat %s' % (path))
448         return data.strip()
449
450     def wait_for_flush(self, bound=0, total=0, n_retry=20):
451         for i in range(n_retry):
452             nbound = self.nsimdev.dfs_num_bound_progs()
453             nprogs = len(bpftool_prog_list())
454             if nbound == bound and nprogs == total:
455                 return
456             time.sleep(0.05)
457         raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
458
459     def set_ns(self, ns):
460         name = "1" if ns == "" else ns
461         ip("link set dev %s netns %s" % (self.dev["ifname"], name), ns=self.ns)
462         self.ns = ns
463
464     def set_mtu(self, mtu, fail=True):
465         return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
466                   fail=fail)
467
468     def set_xdp(self, bpf, mode, force=False, JSON=True, verbose=False,
469                 fail=True, include_stderr=False):
470         if verbose:
471             bpf += " verbose"
472         return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
473                   force=force, JSON=JSON,
474                   fail=fail, include_stderr=include_stderr)
475
476     def unset_xdp(self, mode, force=False, JSON=True,
477                   fail=True, include_stderr=False):
478         return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
479                   force=force, JSON=JSON,
480                   fail=fail, include_stderr=include_stderr)
481
482     def ip_link_show(self, xdp):
483         _, link = ip("link show dev %s" % (self['ifname']))
484         if len(link) > 1:
485             raise Exception("Multiple objects on ip link show")
486         if len(link) < 1:
487             return {}
488         fail(xdp != "xdp" in link,
489              "XDP program not reporting in iplink (reported %s, expected %s)" %
490              ("xdp" in link, xdp))
491         return link[0]
492
493     def tc_add_ingress(self):
494         tc("qdisc add dev %s ingress" % (self['ifname']))
495
496     def tc_del_ingress(self):
497         tc("qdisc del dev %s ingress" % (self['ifname']))
498
499     def tc_flush_filters(self, bound=0, total=0):
500         self.tc_del_ingress()
501         self.tc_add_ingress()
502         self.wait_for_flush(bound=bound, total=total)
503
504     def tc_show_ingress(self, expected=None):
505         # No JSON support, oh well...
506         flags = ["skip_sw", "skip_hw", "in_hw"]
507         named = ["protocol", "pref", "chain", "handle", "id", "tag"]
508
509         args = "-s filter show dev %s ingress" % (self['ifname'])
510         _, out = tc(args, JSON=False)
511
512         filters = []
513         lines = out.split('\n')
514         for line in lines:
515             words = line.split()
516             if "handle" not in words:
517                 continue
518             fltr = {}
519             for flag in flags:
520                 fltr[flag] = flag in words
521             for name in named:
522                 try:
523                     idx = words.index(name)
524                     fltr[name] = words[idx + 1]
525                 except ValueError:
526                     pass
527             filters.append(fltr)
528
529         if expected is not None:
530             fail(len(filters) != expected,
531                  "%d ingress filters loaded, expected %d" %
532                  (len(filters), expected))
533         return filters
534
535     def cls_filter_op(self, op, qdisc="ingress", prio=None, handle=None,
536                       chain=None, cls="", params="",
537                       fail=True, include_stderr=False):
538         spec = ""
539         if prio is not None:
540             spec += " prio %d" % (prio)
541         if handle:
542             spec += " handle %s" % (handle)
543         if chain is not None:
544             spec += " chain %d" % (chain)
545
546         return tc("filter {op} dev {dev} {qdisc} {spec} {cls} {params}"\
547                   .format(op=op, dev=self['ifname'], qdisc=qdisc, spec=spec,
548                           cls=cls, params=params),
549                   fail=fail, include_stderr=include_stderr)
550
551     def cls_bpf_add_filter(self, bpf, op="add", prio=None, handle=None,
552                            chain=None, da=False, verbose=False,
553                            skip_sw=False, skip_hw=False,
554                            fail=True, include_stderr=False):
555         cls = "bpf " + bpf
556
557         params = ""
558         if da:
559             params += " da"
560         if verbose:
561             params += " verbose"
562         if skip_sw:
563             params += " skip_sw"
564         if skip_hw:
565             params += " skip_hw"
566
567         return self.cls_filter_op(op=op, prio=prio, handle=handle, cls=cls,
568                                   chain=chain, params=params,
569                                   fail=fail, include_stderr=include_stderr)
570
571     def set_ethtool_tc_offloads(self, enable, fail=True):
572         args = "hw-tc-offload %s" % ("on" if enable else "off")
573         return ethtool(self, "-K", args, fail=fail)
574
575 ################################################################################
576 def clean_up():
577     global files, netns, devs
578
579     for dev in devs:
580         dev.remove()
581     for f in files:
582         cmd("rm -f %s" % (f))
583     for ns in netns:
584         cmd("ip netns delete %s" % (ns))
585     files = []
586     netns = []
587
588 def pin_prog(file_name, idx=0):
589     progs = bpftool_prog_list(expected=(idx + 1))
590     prog = progs[idx]
591     bpftool("prog pin id %d %s" % (prog["id"], file_name))
592     files.append(file_name)
593
594     return file_name, bpf_pinned(file_name)
595
596 def pin_map(file_name, idx=0, expected=1):
597     maps = bpftool_map_list(expected=expected)
598     m = maps[idx]
599     bpftool("map pin id %d %s" % (m["id"], file_name))
600     files.append(file_name)
601
602     return file_name, bpf_pinned(file_name)
603
604 def check_dev_info_removed(prog_file=None, map_file=None):
605     bpftool_prog_list(expected=0)
606     ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
607     fail(ret == 0, "Showing prog with removed device did not fail")
608     fail(err["error"].find("No such device") == -1,
609          "Showing prog with removed device expected ENODEV, error is %s" %
610          (err["error"]))
611
612     bpftool_map_list(expected=0)
613     ret, err = bpftool("map show pin %s" % (map_file), fail=False)
614     fail(ret == 0, "Showing map with removed device did not fail")
615     fail(err["error"].find("No such device") == -1,
616          "Showing map with removed device expected ENODEV, error is %s" %
617          (err["error"]))
618
619 def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
620     progs = bpftool_prog_list(expected=1, ns=ns)
621     prog = progs[0]
622
623     fail("dev" not in prog.keys(), "Device parameters not reported")
624     dev = prog["dev"]
625     fail("ifindex" not in dev.keys(), "Device parameters not reported")
626     fail("ns_dev" not in dev.keys(), "Device parameters not reported")
627     fail("ns_inode" not in dev.keys(), "Device parameters not reported")
628
629     if not other_ns:
630         fail("ifname" not in dev.keys(), "Ifname not reported")
631         fail(dev["ifname"] != sim["ifname"],
632              "Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
633     else:
634         fail("ifname" in dev.keys(), "Ifname is reported for other ns")
635
636     maps = bpftool_map_list(expected=2, ns=ns)
637     for m in maps:
638         fail("dev" not in m.keys(), "Device parameters not reported")
639         fail(dev != m["dev"], "Map's device different than program's")
640
641 def check_extack(output, reference, args):
642     if skip_extack:
643         return
644     lines = output.split("\n")
645     comp = len(lines) >= 2 and lines[1] == 'Error: ' + reference
646     fail(not comp, "Missing or incorrect netlink extack message")
647
648 def check_extack_nsim(output, reference, args):
649     check_extack(output, "netdevsim: " + reference, args)
650
651 def check_no_extack(res, needle):
652     fail((res[1] + res[2]).count(needle) or (res[1] + res[2]).count("Warning:"),
653          "Found '%s' in command output, leaky extack?" % (needle))
654
655 def check_verifier_log(output, reference):
656     lines = output.split("\n")
657     for l in reversed(lines):
658         if l == reference:
659             return
660     fail(True, "Missing or incorrect message from netdevsim in verifier log")
661
662 def check_multi_basic(two_xdps):
663     fail(two_xdps["mode"] != 4, "Bad mode reported with multiple programs")
664     fail("prog" in two_xdps, "Base program reported in multi program mode")
665     fail(len(two_xdps["attached"]) != 2,
666          "Wrong attached program count with two programs")
667     fail(two_xdps["attached"][0]["prog"]["id"] ==
668          two_xdps["attached"][1]["prog"]["id"],
669          "Offloaded and other programs have the same id")
670
671 def test_spurios_extack(sim, obj, skip_hw, needle):
672     res = sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=skip_hw,
673                                  include_stderr=True)
674     check_no_extack(res, needle)
675     res = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
676                                  skip_hw=skip_hw, include_stderr=True)
677     check_no_extack(res, needle)
678     res = sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf",
679                             include_stderr=True)
680     check_no_extack(res, needle)
681
682 def test_multi_prog(simdev, sim, obj, modename, modeid):
683     start_test("Test multi-attachment XDP - %s + offload..." %
684                (modename or "default", ))
685     sim.set_xdp(obj, "offload")
686     xdp = sim.ip_link_show(xdp=True)["xdp"]
687     offloaded = sim.dfs_read("bpf_offloaded_id")
688     fail("prog" not in xdp, "Base program not reported in single program mode")
689     fail(len(xdp["attached"]) != 1,
690          "Wrong attached program count with one program")
691
692     sim.set_xdp(obj, modename)
693     two_xdps = sim.ip_link_show(xdp=True)["xdp"]
694
695     fail(xdp["attached"][0] not in two_xdps["attached"],
696          "Offload program not reported after other activated")
697     check_multi_basic(two_xdps)
698
699     offloaded2 = sim.dfs_read("bpf_offloaded_id")
700     fail(offloaded != offloaded2,
701          "Offload ID changed after loading other program")
702
703     start_test("Test multi-attachment XDP - replace...")
704     ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
705     fail(ret == 0, "Replaced one of programs without -force")
706     check_extack(err, "XDP program already attached.", args)
707
708     if modename == "" or modename == "drv":
709         othermode = "" if modename == "drv" else "drv"
710         start_test("Test multi-attachment XDP - detach...")
711         ret, _, err = sim.unset_xdp(othermode, force=True,
712                                     fail=False, include_stderr=True)
713         fail(ret == 0, "Removed program with a bad mode")
714         check_extack(err, "program loaded with different flags.", args)
715
716     sim.unset_xdp("offload")
717     xdp = sim.ip_link_show(xdp=True)["xdp"]
718     offloaded = sim.dfs_read("bpf_offloaded_id")
719
720     fail(xdp["mode"] != modeid, "Bad mode reported after multiple programs")
721     fail("prog" not in xdp,
722          "Base program not reported after multi program mode")
723     fail(xdp["attached"][0] not in two_xdps["attached"],
724          "Offload program not reported after other activated")
725     fail(len(xdp["attached"]) != 1,
726          "Wrong attached program count with remaining programs")
727     fail(offloaded != "0", "Offload ID reported with only other program left")
728
729     start_test("Test multi-attachment XDP - reattach...")
730     sim.set_xdp(obj, "offload")
731     two_xdps = sim.ip_link_show(xdp=True)["xdp"]
732
733     fail(xdp["attached"][0] not in two_xdps["attached"],
734          "Other program not reported after offload activated")
735     check_multi_basic(two_xdps)
736
737     start_test("Test multi-attachment XDP - device remove...")
738     simdev.remove()
739
740     simdev = NetdevSimDev()
741     sim, = simdev.nsims
742     sim.set_ethtool_tc_offloads(True)
743     return [simdev, sim]
744
745 # Parse command line
746 parser = argparse.ArgumentParser()
747 parser.add_argument("--log", help="output verbose log to given file")
748 args = parser.parse_args()
749 if args.log:
750     logfile = open(args.log, 'w+')
751     logfile.write("# -*-Org-*-")
752
753 log("Prepare...", "", level=1)
754 log_level_inc()
755
756 # Check permissions
757 skip(os.getuid() != 0, "test must be run as root")
758
759 # Check tools
760 ret, progs = bpftool("prog", fail=False)
761 skip(ret != 0, "bpftool not installed")
762 base_progs = progs
763 _, base_maps = bpftool("map")
764
765 # Check netdevsim
766 ret, out = cmd("modprobe netdevsim", fail=False)
767 skip(ret != 0, "netdevsim module could not be loaded")
768
769 # Check debugfs
770 _, out = cmd("mount")
771 if out.find("/sys/kernel/debug type debugfs") == -1:
772     cmd("mount -t debugfs none /sys/kernel/debug")
773
774 # Check samples are compiled
775 samples = ["sample_ret0.o", "sample_map_ret0.o"]
776 for s in samples:
777     ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
778     skip(ret != 0, "sample %s/%s not found, please compile it" %
779          (bpf_test_dir, s))
780
781 # Check if iproute2 is built with libmnl (needed by extack support)
782 _, _, err = cmd("tc qdisc delete dev lo handle 0",
783                 fail=False, include_stderr=True)
784 if err.find("Error: Failed to find qdisc with specified handle.") == -1:
785     print("Warning: no extack message in iproute2 output, libmnl missing?")
786     log("Warning: no extack message in iproute2 output, libmnl missing?", "")
787     skip_extack = True
788
789 # Check if net namespaces seem to work
790 ns = mknetns()
791 skip(ns is None, "Could not create a net namespace")
792 cmd("ip netns delete %s" % (ns))
793 netns = []
794
795 try:
796     obj = bpf_obj("sample_ret0.o")
797     bytecode = bpf_bytecode("1,6 0 0 4294967295,")
798
799     start_test("Test destruction of generic XDP...")
800     simdev = NetdevSimDev()
801     sim, = simdev.nsims
802     sim.set_xdp(obj, "generic")
803     simdev.remove()
804     bpftool_prog_list_wait(expected=0)
805
806     simdev = NetdevSimDev()
807     sim, = simdev.nsims
808     sim.tc_add_ingress()
809
810     start_test("Test TC non-offloaded...")
811     ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
812     fail(ret != 0, "Software TC filter did not load")
813
814     start_test("Test TC non-offloaded isn't getting bound...")
815     ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
816     fail(ret != 0, "Software TC filter did not load")
817     simdev.dfs_get_bound_progs(expected=0)
818
819     sim.tc_flush_filters()
820
821     start_test("Test TC offloads are off by default...")
822     ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
823                                          fail=False, include_stderr=True)
824     fail(ret == 0, "TC filter loaded without enabling TC offloads")
825     check_extack(err, "TC offload is disabled on net device.", args)
826     sim.wait_for_flush()
827
828     sim.set_ethtool_tc_offloads(True)
829     sim.dfs["bpf_tc_non_bound_accept"] = "Y"
830
831     start_test("Test TC offload by default...")
832     ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
833     fail(ret != 0, "Software TC filter did not load")
834     simdev.dfs_get_bound_progs(expected=0)
835     ingress = sim.tc_show_ingress(expected=1)
836     fltr = ingress[0]
837     fail(not fltr["in_hw"], "Filter not offloaded by default")
838
839     sim.tc_flush_filters()
840
841     start_test("Test TC cBPF bytcode tries offload by default...")
842     ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
843     fail(ret != 0, "Software TC filter did not load")
844     simdev.dfs_get_bound_progs(expected=0)
845     ingress = sim.tc_show_ingress(expected=1)
846     fltr = ingress[0]
847     fail(not fltr["in_hw"], "Bytecode not offloaded by default")
848
849     sim.tc_flush_filters()
850     sim.dfs["bpf_tc_non_bound_accept"] = "N"
851
852     start_test("Test TC cBPF unbound bytecode doesn't offload...")
853     ret, _, err = sim.cls_bpf_add_filter(bytecode, skip_sw=True,
854                                          fail=False, include_stderr=True)
855     fail(ret == 0, "TC bytecode loaded for offload")
856     check_extack_nsim(err, "netdevsim configured to reject unbound programs.",
857                       args)
858     sim.wait_for_flush()
859
860     start_test("Test non-0 chain offload...")
861     ret, _, err = sim.cls_bpf_add_filter(obj, chain=1, prio=1, handle=1,
862                                          skip_sw=True,
863                                          fail=False, include_stderr=True)
864     fail(ret == 0, "Offloaded a filter to chain other than 0")
865     check_extack(err, "Driver supports only offload of chain 0.", args)
866     sim.tc_flush_filters()
867
868     start_test("Test TC replace...")
869     sim.cls_bpf_add_filter(obj, prio=1, handle=1)
870     sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1)
871     sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
872
873     sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_sw=True)
874     sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_sw=True)
875     sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
876
877     sim.cls_bpf_add_filter(obj, prio=1, handle=1, skip_hw=True)
878     sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1, skip_hw=True)
879     sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
880
881     start_test("Test TC replace bad flags...")
882     for i in range(3):
883         for j in range(3):
884             ret, _ = sim.cls_bpf_add_filter(obj, op="replace", prio=1, handle=1,
885                                             skip_sw=(j == 1), skip_hw=(j == 2),
886                                             fail=False)
887             fail(bool(ret) != bool(j),
888                  "Software TC incorrect load in replace test, iteration %d" %
889                  (j))
890         sim.cls_filter_op(op="delete", prio=1, handle=1, cls="bpf")
891
892     start_test("Test spurious extack from the driver...")
893     test_spurios_extack(sim, obj, False, "netdevsim")
894     test_spurios_extack(sim, obj, True, "netdevsim")
895
896     sim.set_ethtool_tc_offloads(False)
897
898     test_spurios_extack(sim, obj, False, "TC offload is disabled")
899     test_spurios_extack(sim, obj, True, "TC offload is disabled")
900
901     sim.set_ethtool_tc_offloads(True)
902
903     sim.tc_flush_filters()
904
905     start_test("Test TC offloads work...")
906     ret, _, err = sim.cls_bpf_add_filter(obj, verbose=True, skip_sw=True,
907                                          fail=False, include_stderr=True)
908     fail(ret != 0, "TC filter did not load with TC offloads enabled")
909     check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
910
911     start_test("Test TC offload basics...")
912     dfs = simdev.dfs_get_bound_progs(expected=1)
913     progs = bpftool_prog_list(expected=1)
914     ingress = sim.tc_show_ingress(expected=1)
915
916     dprog = dfs[0]
917     prog = progs[0]
918     fltr = ingress[0]
919     fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
920     fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
921     fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
922
923     start_test("Test TC offload is device-bound...")
924     fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
925     fail(prog["tag"] != fltr["tag"], "Program tags don't match")
926     fail(fltr["id"] != dprog["id"], "Program IDs don't match")
927     fail(dprog["state"] != "xlated", "Offloaded program state not translated")
928     fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
929
930     start_test("Test disabling TC offloads is rejected while filters installed...")
931     ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
932     fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
933
934     start_test("Test qdisc removal frees things...")
935     sim.tc_flush_filters()
936     sim.tc_show_ingress(expected=0)
937
938     start_test("Test disabling TC offloads is OK without filters...")
939     ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
940     fail(ret != 0,
941          "Driver refused to disable TC offloads without filters installed...")
942
943     sim.set_ethtool_tc_offloads(True)
944
945     start_test("Test destroying device gets rid of TC filters...")
946     sim.cls_bpf_add_filter(obj, skip_sw=True)
947     simdev.remove()
948     bpftool_prog_list_wait(expected=0)
949
950     simdev = NetdevSimDev()
951     sim, = simdev.nsims
952     sim.set_ethtool_tc_offloads(True)
953
954     start_test("Test destroying device gets rid of XDP...")
955     sim.set_xdp(obj, "offload")
956     simdev.remove()
957     bpftool_prog_list_wait(expected=0)
958
959     simdev = NetdevSimDev()
960     sim, = simdev.nsims
961     sim.set_ethtool_tc_offloads(True)
962
963     start_test("Test XDP prog reporting...")
964     sim.set_xdp(obj, "drv")
965     ipl = sim.ip_link_show(xdp=True)
966     progs = bpftool_prog_list(expected=1)
967     fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
968          "Loaded program has wrong ID")
969
970     start_test("Test XDP prog replace without force...")
971     ret, _ = sim.set_xdp(obj, "drv", fail=False)
972     fail(ret == 0, "Replaced XDP program without -force")
973     sim.wait_for_flush(total=1)
974
975     start_test("Test XDP prog replace with force...")
976     ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
977     fail(ret != 0, "Could not replace XDP program with -force")
978     bpftool_prog_list_wait(expected=1)
979     ipl = sim.ip_link_show(xdp=True)
980     progs = bpftool_prog_list(expected=1)
981     fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
982          "Loaded program has wrong ID")
983     fail("dev" in progs[0].keys(),
984          "Device parameters reported for non-offloaded program")
985
986     start_test("Test XDP prog replace with bad flags...")
987     ret, _, err = sim.set_xdp(obj, "generic", force=True,
988                               fail=False, include_stderr=True)
989     fail(ret == 0, "Replaced XDP program with a program in different mode")
990     check_extack(err,
991                  "native and generic XDP can't be active at the same time.",
992                  args)
993     ret, _, err = sim.set_xdp(obj, "", force=True,
994                               fail=False, include_stderr=True)
995     fail(ret == 0, "Replaced XDP program with a program in different mode")
996     check_extack(err, "program loaded with different flags.", args)
997
998     start_test("Test XDP prog remove with bad flags...")
999     ret, _, err = sim.unset_xdp("", force=True,
1000                                 fail=False, include_stderr=True)
1001     fail(ret == 0, "Removed program with a bad mode")
1002     check_extack(err, "program loaded with different flags.", args)
1003
1004     start_test("Test MTU restrictions...")
1005     ret, _ = sim.set_mtu(9000, fail=False)
1006     fail(ret == 0,
1007          "Driver should refuse to increase MTU to 9000 with XDP loaded...")
1008     sim.unset_xdp("drv")
1009     bpftool_prog_list_wait(expected=0)
1010     sim.set_mtu(9000)
1011     ret, _, err = sim.set_xdp(obj, "drv", fail=False, include_stderr=True)
1012     fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
1013     check_extack_nsim(err, "MTU too large w/ XDP enabled.", args)
1014     sim.set_mtu(1500)
1015
1016     sim.wait_for_flush()
1017     start_test("Test non-offload XDP attaching to HW...")
1018     bpftool_prog_load("sample_ret0.o", "/sys/fs/bpf/nooffload")
1019     nooffload = bpf_pinned("/sys/fs/bpf/nooffload")
1020     ret, _, err = sim.set_xdp(nooffload, "offload",
1021                               fail=False, include_stderr=True)
1022     fail(ret == 0, "attached non-offloaded XDP program to HW")
1023     check_extack_nsim(err, "xdpoffload of non-bound program.", args)
1024     rm("/sys/fs/bpf/nooffload")
1025
1026     start_test("Test offload XDP attaching to drv...")
1027     bpftool_prog_load("sample_ret0.o", "/sys/fs/bpf/offload",
1028                       dev=sim['ifname'])
1029     offload = bpf_pinned("/sys/fs/bpf/offload")
1030     ret, _, err = sim.set_xdp(offload, "drv", fail=False, include_stderr=True)
1031     fail(ret == 0, "attached offloaded XDP program to drv")
1032     check_extack(err, "using device-bound program without HW_MODE flag is not supported.", args)
1033     rm("/sys/fs/bpf/offload")
1034     sim.wait_for_flush()
1035
1036     start_test("Test XDP offload...")
1037     _, _, err = sim.set_xdp(obj, "offload", verbose=True, include_stderr=True)
1038     ipl = sim.ip_link_show(xdp=True)
1039     link_xdp = ipl["xdp"]["prog"]
1040     progs = bpftool_prog_list(expected=1)
1041     prog = progs[0]
1042     fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
1043     check_verifier_log(err, "[netdevsim] Hello from netdevsim!")
1044
1045     start_test("Test XDP offload is device bound...")
1046     dfs = simdev.dfs_get_bound_progs(expected=1)
1047     dprog = dfs[0]
1048
1049     fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
1050     fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
1051     fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
1052     fail(dprog["state"] != "xlated", "Offloaded program state not translated")
1053     fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
1054
1055     start_test("Test removing XDP program many times...")
1056     sim.unset_xdp("offload")
1057     sim.unset_xdp("offload")
1058     sim.unset_xdp("drv")
1059     sim.unset_xdp("drv")
1060     sim.unset_xdp("")
1061     sim.unset_xdp("")
1062     bpftool_prog_list_wait(expected=0)
1063
1064     start_test("Test attempt to use a program for a wrong device...")
1065     simdev2 = NetdevSimDev()
1066     sim2, = simdev2.nsims
1067     sim2.set_xdp(obj, "offload")
1068     pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
1069
1070     ret, _, err = sim.set_xdp(pinned, "offload",
1071                               fail=False, include_stderr=True)
1072     fail(ret == 0, "Pinned program loaded for a different device accepted")
1073     check_extack_nsim(err, "program bound to different dev.", args)
1074     simdev2.remove()
1075     ret, _, err = sim.set_xdp(pinned, "offload",
1076                               fail=False, include_stderr=True)
1077     fail(ret == 0, "Pinned program loaded for a removed device accepted")
1078     check_extack_nsim(err, "xdpoffload of non-bound program.", args)
1079     rm(pin_file)
1080     bpftool_prog_list_wait(expected=0)
1081
1082     simdev, sim = test_multi_prog(simdev, sim, obj, "", 1)
1083     simdev, sim = test_multi_prog(simdev, sim, obj, "drv", 1)
1084     simdev, sim = test_multi_prog(simdev, sim, obj, "generic", 2)
1085
1086     start_test("Test mixing of TC and XDP...")
1087     sim.tc_add_ingress()
1088     sim.set_xdp(obj, "offload")
1089     ret, _, err = sim.cls_bpf_add_filter(obj, skip_sw=True,
1090                                          fail=False, include_stderr=True)
1091     fail(ret == 0, "Loading TC when XDP active should fail")
1092     check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
1093     sim.unset_xdp("offload")
1094     sim.wait_for_flush()
1095
1096     sim.cls_bpf_add_filter(obj, skip_sw=True)
1097     ret, _, err = sim.set_xdp(obj, "offload", fail=False, include_stderr=True)
1098     fail(ret == 0, "Loading XDP when TC active should fail")
1099     check_extack_nsim(err, "TC program is already loaded.", args)
1100
1101     start_test("Test binding TC from pinned...")
1102     pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
1103     sim.tc_flush_filters(bound=1, total=1)
1104     sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
1105     sim.tc_flush_filters(bound=1, total=1)
1106
1107     start_test("Test binding XDP from pinned...")
1108     sim.set_xdp(obj, "offload")
1109     pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
1110
1111     sim.set_xdp(pinned, "offload", force=True)
1112     sim.unset_xdp("offload")
1113     sim.set_xdp(pinned, "offload", force=True)
1114     sim.unset_xdp("offload")
1115
1116     start_test("Test offload of wrong type fails...")
1117     ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
1118     fail(ret == 0, "Managed to attach XDP program to TC")
1119
1120     start_test("Test asking for TC offload of two filters...")
1121     sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
1122     ret, _, err = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True,
1123                                          fail=False, include_stderr=True)
1124     fail(ret == 0, "Managed to offload two TC filters at the same time")
1125     check_extack_nsim(err, "driver and netdev offload states mismatch.", args)
1126
1127     sim.tc_flush_filters(bound=2, total=2)
1128
1129     start_test("Test if netdev removal waits for translation...")
1130     delay_msec = 500
1131     sim.dfs["dev/bpf_bind_verifier_delay"] = delay_msec
1132     start = time.time()
1133     cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
1134                (sim['ifname'], obj)
1135     tc_proc = cmd(cmd_line, background=True, fail=False)
1136     # Wait for the verifier to start
1137     while simdev.dfs_num_bound_progs() <= 2:
1138         pass
1139     simdev.remove()
1140     end = time.time()
1141     ret, _ = cmd_result(tc_proc, fail=False)
1142     time_diff = end - start
1143     log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
1144
1145     fail(ret == 0, "Managed to load TC filter on a unregistering device")
1146     delay_sec = delay_msec * 0.001
1147     fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
1148          (time_diff, delay_sec))
1149
1150     # Remove all pinned files and reinstantiate the netdev
1151     clean_up()
1152     bpftool_prog_list_wait(expected=0)
1153
1154     simdev = NetdevSimDev()
1155     sim, = simdev.nsims
1156     map_obj = bpf_obj("sample_map_ret0.o")
1157     start_test("Test loading program with maps...")
1158     sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1159
1160     start_test("Test bpftool bound info reporting (own ns)...")
1161     check_dev_info(False, "")
1162
1163     start_test("Test bpftool bound info reporting (other ns)...")
1164     ns = mknetns()
1165     sim.set_ns(ns)
1166     check_dev_info(True, "")
1167
1168     start_test("Test bpftool bound info reporting (remote ns)...")
1169     check_dev_info(False, ns)
1170
1171     start_test("Test bpftool bound info reporting (back to own ns)...")
1172     sim.set_ns("")
1173     check_dev_info(False, "")
1174
1175     prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
1176     map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
1177     simdev.remove()
1178
1179     start_test("Test bpftool bound info reporting (removed dev)...")
1180     check_dev_info_removed(prog_file=prog_file, map_file=map_file)
1181
1182     # Remove all pinned files and reinstantiate the netdev
1183     clean_up()
1184     bpftool_prog_list_wait(expected=0)
1185
1186     simdev = NetdevSimDev()
1187     sim, = simdev.nsims
1188
1189     start_test("Test map update (no flags)...")
1190     sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1191     maps = bpftool_map_list(expected=2)
1192     array = maps[0] if maps[0]["type"] == "array" else maps[1]
1193     htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
1194     for m in maps:
1195         for i in range(2):
1196             bpftool("map update id %d key %s value %s" %
1197                     (m["id"], int2str("I", i), int2str("Q", i * 3)))
1198
1199     for m in maps:
1200         ret, _ = bpftool("map update id %d key %s value %s" %
1201                          (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
1202                          fail=False)
1203         fail(ret == 0, "added too many entries")
1204
1205     start_test("Test map update (exists)...")
1206     for m in maps:
1207         for i in range(2):
1208             bpftool("map update id %d key %s value %s exist" %
1209                     (m["id"], int2str("I", i), int2str("Q", i * 3)))
1210
1211     for m in maps:
1212         ret, err = bpftool("map update id %d key %s value %s exist" %
1213                            (m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
1214                            fail=False)
1215         fail(ret == 0, "updated non-existing key")
1216         fail(err["error"].find("No such file or directory") == -1,
1217              "expected ENOENT, error is '%s'" % (err["error"]))
1218
1219     start_test("Test map update (noexist)...")
1220     for m in maps:
1221         for i in range(2):
1222             ret, err = bpftool("map update id %d key %s value %s noexist" %
1223                                (m["id"], int2str("I", i), int2str("Q", i * 3)),
1224                                fail=False)
1225         fail(ret == 0, "updated existing key")
1226         fail(err["error"].find("File exists") == -1,
1227              "expected EEXIST, error is '%s'" % (err["error"]))
1228
1229     start_test("Test map dump...")
1230     for m in maps:
1231         _, entries = bpftool("map dump id %d" % (m["id"]))
1232         for i in range(2):
1233             key = str2int(entries[i]["key"])
1234             fail(key != i, "expected key %d, got %d" % (key, i))
1235             val = str2int(entries[i]["value"])
1236             fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
1237
1238     start_test("Test map getnext...")
1239     for m in maps:
1240         _, entry = bpftool("map getnext id %d" % (m["id"]))
1241         key = str2int(entry["next_key"])
1242         fail(key != 0, "next key %d, expected %d" % (key, 0))
1243         _, entry = bpftool("map getnext id %d key %s" %
1244                            (m["id"], int2str("I", 0)))
1245         key = str2int(entry["next_key"])
1246         fail(key != 1, "next key %d, expected %d" % (key, 1))
1247         ret, err = bpftool("map getnext id %d key %s" %
1248                            (m["id"], int2str("I", 1)), fail=False)
1249         fail(ret == 0, "got next key past the end of map")
1250         fail(err["error"].find("No such file or directory") == -1,
1251              "expected ENOENT, error is '%s'" % (err["error"]))
1252
1253     start_test("Test map delete (htab)...")
1254     for i in range(2):
1255         bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
1256
1257     start_test("Test map delete (array)...")
1258     for i in range(2):
1259         ret, err = bpftool("map delete id %d key %s" %
1260                            (htab["id"], int2str("I", i)), fail=False)
1261         fail(ret == 0, "removed entry from an array")
1262         fail(err["error"].find("No such file or directory") == -1,
1263              "expected ENOENT, error is '%s'" % (err["error"]))
1264
1265     start_test("Test map remove...")
1266     sim.unset_xdp("offload")
1267     bpftool_map_list_wait(expected=0)
1268     simdev.remove()
1269
1270     simdev = NetdevSimDev()
1271     sim, = simdev.nsims
1272     sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
1273     simdev.remove()
1274     bpftool_map_list_wait(expected=0)
1275
1276     start_test("Test map creation fail path...")
1277     simdev = NetdevSimDev()
1278     sim, = simdev.nsims
1279     sim.dfs["bpf_map_accept"] = "N"
1280     ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
1281     fail(ret == 0,
1282          "netdevsim didn't refuse to create a map with offload disabled")
1283
1284     simdev.remove()
1285
1286     start_test("Test multi-dev ASIC program reuse...")
1287     simdevA = NetdevSimDev()
1288     simA, = simdevA.nsims
1289     simdevB = NetdevSimDev(3)
1290     simB1, simB2, simB3 = simdevB.nsims
1291     sims = (simA, simB1, simB2, simB3)
1292     simB = (simB1, simB2, simB3)
1293
1294     bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimA",
1295                       dev=simA['ifname'])
1296     progA = bpf_pinned("/sys/fs/bpf/nsimA")
1297     bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimB",
1298                       dev=simB1['ifname'])
1299     progB = bpf_pinned("/sys/fs/bpf/nsimB")
1300
1301     simA.set_xdp(progA, "offload", JSON=False)
1302     for d in simdevB.nsims:
1303         d.set_xdp(progB, "offload", JSON=False)
1304
1305     start_test("Test multi-dev ASIC cross-dev replace...")
1306     ret, _ = simA.set_xdp(progB, "offload", force=True, JSON=False, fail=False)
1307     fail(ret == 0, "cross-ASIC program allowed")
1308     for d in simdevB.nsims:
1309         ret, _ = d.set_xdp(progA, "offload", force=True, JSON=False, fail=False)
1310         fail(ret == 0, "cross-ASIC program allowed")
1311
1312     start_test("Test multi-dev ASIC cross-dev install...")
1313     for d in sims:
1314         d.unset_xdp("offload")
1315
1316     ret, _, err = simA.set_xdp(progB, "offload", force=True, JSON=False,
1317                                fail=False, include_stderr=True)
1318     fail(ret == 0, "cross-ASIC program allowed")
1319     check_extack_nsim(err, "program bound to different dev.", args)
1320     for d in simdevB.nsims:
1321         ret, _, err = d.set_xdp(progA, "offload", force=True, JSON=False,
1322                                 fail=False, include_stderr=True)
1323         fail(ret == 0, "cross-ASIC program allowed")
1324         check_extack_nsim(err, "program bound to different dev.", args)
1325
1326     start_test("Test multi-dev ASIC cross-dev map reuse...")
1327
1328     mapA = bpftool("prog show %s" % (progA))[1]["map_ids"][0]
1329     mapB = bpftool("prog show %s" % (progB))[1]["map_ids"][0]
1330
1331     ret, _ = bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimB_",
1332                                dev=simB3['ifname'],
1333                                maps=["idx 0 id %d" % (mapB)],
1334                                fail=False)
1335     fail(ret != 0, "couldn't reuse a map on the same ASIC")
1336     rm("/sys/fs/bpf/nsimB_")
1337
1338     ret, _, err = bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimA_",
1339                                     dev=simA['ifname'],
1340                                     maps=["idx 0 id %d" % (mapB)],
1341                                     fail=False, include_stderr=True)
1342     fail(ret == 0, "could reuse a map on a different ASIC")
1343     fail(err.count("offload device mismatch between prog and map") == 0,
1344          "error message missing for cross-ASIC map")
1345
1346     ret, _, err = bpftool_prog_load("sample_map_ret0.o", "/sys/fs/bpf/nsimB_",
1347                                     dev=simB1['ifname'],
1348                                     maps=["idx 0 id %d" % (mapA)],
1349                                     fail=False, include_stderr=True)
1350     fail(ret == 0, "could reuse a map on a different ASIC")
1351     fail(err.count("offload device mismatch between prog and map") == 0,
1352          "error message missing for cross-ASIC map")
1353
1354     start_test("Test multi-dev ASIC cross-dev destruction...")
1355     bpftool_prog_list_wait(expected=2)
1356
1357     simdevA.remove()
1358     bpftool_prog_list_wait(expected=1)
1359
1360     ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
1361     fail(ifnameB != simB1['ifname'], "program not bound to original device")
1362     simB1.remove()
1363     bpftool_prog_list_wait(expected=1)
1364
1365     start_test("Test multi-dev ASIC cross-dev destruction - move...")
1366     ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
1367     fail(ifnameB not in (simB2['ifname'], simB3['ifname']),
1368          "program not bound to remaining devices")
1369
1370     simB2.remove()
1371     ifnameB = bpftool("prog show %s" % (progB))[1]["dev"]["ifname"]
1372     fail(ifnameB != simB3['ifname'], "program not bound to remaining device")
1373
1374     simB3.remove()
1375     simdevB.remove()
1376     bpftool_prog_list_wait(expected=0)
1377
1378     start_test("Test multi-dev ASIC cross-dev destruction - orphaned...")
1379     ret, out = bpftool("prog show %s" % (progB), fail=False)
1380     fail(ret == 0, "got information about orphaned program")
1381     fail("error" not in out, "no error reported for get info on orphaned")
1382     fail(out["error"] != "can't get prog info: No such device",
1383          "wrong error for get info on orphaned")
1384
1385     print("%s: OK" % (os.path.basename(__file__)))
1386
1387 finally:
1388     log("Clean up...", "", level=1)
1389     log_level_inc()
1390     clean_up()