selftest/bpf: extend the offload test with map checks
Check map device information is reported correctly, and perform basic map operations. Check device destruction gets rid of the maps and map allocation failure path by telling netdevsim to reject map offload via DebugFS. Signed-off-by: Jakub Kicinski <jakub.kicinski@netronome.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
This commit is contained in:
parent
395cacb5f1
commit
7fedbb7c5a
|
@ -19,7 +19,8 @@ TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test
|
||||||
TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
|
TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
|
||||||
test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o \
|
test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o \
|
||||||
sockmap_verdict_prog.o dev_cgroup.o sample_ret0.o test_tracepoint.o \
|
sockmap_verdict_prog.o dev_cgroup.o sample_ret0.o test_tracepoint.o \
|
||||||
test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o
|
test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \
|
||||||
|
sample_map_ret0.o
|
||||||
|
|
||||||
TEST_PROGS := test_kmod.sh test_xdp_redirect.sh test_xdp_meta.sh \
|
TEST_PROGS := test_kmod.sh test_xdp_redirect.sh test_xdp_meta.sh \
|
||||||
test_offload.py
|
test_offload.py
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include "bpf_helpers.h"
|
||||||
|
|
||||||
|
struct bpf_map_def SEC("maps") htab = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(__u32),
|
||||||
|
.value_size = sizeof(long),
|
||||||
|
.max_entries = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bpf_map_def SEC("maps") array = {
|
||||||
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
|
.key_size = sizeof(__u32),
|
||||||
|
.value_size = sizeof(long),
|
||||||
|
.max_entries = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Sample program which should always load for testing control paths. */
|
||||||
|
SEC(".text") int func()
|
||||||
|
{
|
||||||
|
__u64 key64 = 0;
|
||||||
|
__u32 key = 0;
|
||||||
|
long *value;
|
||||||
|
|
||||||
|
value = bpf_map_lookup_elem(&htab, &key);
|
||||||
|
if (!value)
|
||||||
|
return 1;
|
||||||
|
value = bpf_map_lookup_elem(&array, &key64);
|
||||||
|
if (!value)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import os
|
||||||
import pprint
|
import pprint
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -156,6 +157,14 @@ def bpftool_prog_list(expected=None, ns=""):
|
||||||
(len(progs), expected))
|
(len(progs), expected))
|
||||||
return progs
|
return progs
|
||||||
|
|
||||||
|
def bpftool_map_list(expected=None, ns=""):
|
||||||
|
_, maps = bpftool("map show", JSON=True, ns=ns, fail=True)
|
||||||
|
if expected is not None:
|
||||||
|
if len(maps) != expected:
|
||||||
|
fail(True, "%d BPF maps loaded, expected %d" %
|
||||||
|
(len(maps), expected))
|
||||||
|
return maps
|
||||||
|
|
||||||
def bpftool_prog_list_wait(expected=0, n_retry=20):
|
def bpftool_prog_list_wait(expected=0, n_retry=20):
|
||||||
for i in range(n_retry):
|
for i in range(n_retry):
|
||||||
nprogs = len(bpftool_prog_list())
|
nprogs = len(bpftool_prog_list())
|
||||||
|
@ -164,6 +173,14 @@ def bpftool_prog_list_wait(expected=0, n_retry=20):
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
|
raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
|
||||||
|
|
||||||
|
def bpftool_map_list_wait(expected=0, n_retry=20):
|
||||||
|
for i in range(n_retry):
|
||||||
|
nmaps = len(bpftool_map_list())
|
||||||
|
if nmaps == expected:
|
||||||
|
return
|
||||||
|
time.sleep(0.05)
|
||||||
|
raise Exception("Time out waiting for map counts to stabilize want %d, have %d" % (expected, nmaps))
|
||||||
|
|
||||||
def ip(args, force=False, JSON=True, ns="", fail=True):
|
def ip(args, force=False, JSON=True, ns="", fail=True):
|
||||||
if force:
|
if force:
|
||||||
args = "-force " + args
|
args = "-force " + args
|
||||||
|
@ -193,6 +210,26 @@ def mknetns(n_retry=10):
|
||||||
return name
|
return name
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def int2str(fmt, val):
|
||||||
|
ret = []
|
||||||
|
for b in struct.pack(fmt, val):
|
||||||
|
ret.append(int(b))
|
||||||
|
return " ".join(map(lambda x: str(x), ret))
|
||||||
|
|
||||||
|
def str2int(strtab):
|
||||||
|
inttab = []
|
||||||
|
for i in strtab:
|
||||||
|
inttab.append(int(i, 16))
|
||||||
|
ba = bytearray(inttab)
|
||||||
|
if len(strtab) == 4:
|
||||||
|
fmt = "I"
|
||||||
|
elif len(strtab) == 8:
|
||||||
|
fmt = "Q"
|
||||||
|
else:
|
||||||
|
raise Exception("String array of len %d can't be unpacked to an int" %
|
||||||
|
(len(strtab)))
|
||||||
|
return struct.unpack(fmt, ba)[0]
|
||||||
|
|
||||||
class DebugfsDir:
|
class DebugfsDir:
|
||||||
"""
|
"""
|
||||||
Class for accessing DebugFS directories as a dictionary.
|
Class for accessing DebugFS directories as a dictionary.
|
||||||
|
@ -311,13 +348,13 @@ class NetdevSim:
|
||||||
return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
|
return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
|
||||||
fail=fail)
|
fail=fail)
|
||||||
|
|
||||||
def set_xdp(self, bpf, mode, force=False, fail=True):
|
def set_xdp(self, bpf, mode, force=False, JSON=True, fail=True):
|
||||||
return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
|
return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
|
||||||
force=force, fail=fail)
|
force=force, JSON=JSON, fail=fail)
|
||||||
|
|
||||||
def unset_xdp(self, mode, force=False, fail=True):
|
def unset_xdp(self, mode, force=False, JSON=True, fail=True):
|
||||||
return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
|
return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
|
||||||
force=force, fail=fail)
|
force=force, JSON=JSON, fail=fail)
|
||||||
|
|
||||||
def ip_link_show(self, xdp):
|
def ip_link_show(self, xdp):
|
||||||
_, link = ip("link show dev %s" % (self['ifname']))
|
_, link = ip("link show dev %s" % (self['ifname']))
|
||||||
|
@ -390,12 +427,16 @@ class NetdevSim:
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
def clean_up():
|
def clean_up():
|
||||||
|
global files, netns, devs
|
||||||
|
|
||||||
for dev in devs:
|
for dev in devs:
|
||||||
dev.remove()
|
dev.remove()
|
||||||
for f in files:
|
for f in files:
|
||||||
cmd("rm -f %s" % (f))
|
cmd("rm -f %s" % (f))
|
||||||
for ns in netns:
|
for ns in netns:
|
||||||
cmd("ip netns delete %s" % (ns))
|
cmd("ip netns delete %s" % (ns))
|
||||||
|
files = []
|
||||||
|
netns = []
|
||||||
|
|
||||||
def pin_prog(file_name, idx=0):
|
def pin_prog(file_name, idx=0):
|
||||||
progs = bpftool_prog_list(expected=(idx + 1))
|
progs = bpftool_prog_list(expected=(idx + 1))
|
||||||
|
@ -405,16 +446,31 @@ def pin_prog(file_name, idx=0):
|
||||||
|
|
||||||
return file_name, bpf_pinned(file_name)
|
return file_name, bpf_pinned(file_name)
|
||||||
|
|
||||||
def check_dev_info(other_ns, ns, pin_file=None, removed=False):
|
def pin_map(file_name, idx=0, expected=1):
|
||||||
if removed:
|
maps = bpftool_map_list(expected=expected)
|
||||||
bpftool_prog_list(expected=0)
|
m = maps[idx]
|
||||||
ret, err = bpftool("prog show pin %s" % (pin_file), fail=False)
|
bpftool("map pin id %d %s" % (m["id"], file_name))
|
||||||
fail(ret == 0, "Showing prog with removed device did not fail")
|
files.append(file_name)
|
||||||
fail(err["error"].find("No such device") == -1,
|
|
||||||
"Showing prog with removed device expected ENODEV, error is %s" %
|
return file_name, bpf_pinned(file_name)
|
||||||
(err["error"]))
|
|
||||||
return
|
def check_dev_info_removed(prog_file=None, map_file=None):
|
||||||
progs = bpftool_prog_list(expected=int(not removed), ns=ns)
|
bpftool_prog_list(expected=0)
|
||||||
|
ret, err = bpftool("prog show pin %s" % (prog_file), fail=False)
|
||||||
|
fail(ret == 0, "Showing prog with removed device did not fail")
|
||||||
|
fail(err["error"].find("No such device") == -1,
|
||||||
|
"Showing prog with removed device expected ENODEV, error is %s" %
|
||||||
|
(err["error"]))
|
||||||
|
|
||||||
|
bpftool_map_list(expected=0)
|
||||||
|
ret, err = bpftool("map show pin %s" % (map_file), fail=False)
|
||||||
|
fail(ret == 0, "Showing map with removed device did not fail")
|
||||||
|
fail(err["error"].find("No such device") == -1,
|
||||||
|
"Showing map with removed device expected ENODEV, error is %s" %
|
||||||
|
(err["error"]))
|
||||||
|
|
||||||
|
def check_dev_info(other_ns, ns, prog_file=None, map_file=None, removed=False):
|
||||||
|
progs = bpftool_prog_list(expected=1, ns=ns)
|
||||||
prog = progs[0]
|
prog = progs[0]
|
||||||
|
|
||||||
fail("dev" not in prog.keys(), "Device parameters not reported")
|
fail("dev" not in prog.keys(), "Device parameters not reported")
|
||||||
|
@ -423,16 +479,17 @@ def check_dev_info(other_ns, ns, pin_file=None, removed=False):
|
||||||
fail("ns_dev" not in dev.keys(), "Device parameters not reported")
|
fail("ns_dev" not in dev.keys(), "Device parameters not reported")
|
||||||
fail("ns_inode" not in dev.keys(), "Device parameters not reported")
|
fail("ns_inode" not in dev.keys(), "Device parameters not reported")
|
||||||
|
|
||||||
if not removed and not other_ns:
|
if not other_ns:
|
||||||
fail("ifname" not in dev.keys(), "Ifname not reported")
|
fail("ifname" not in dev.keys(), "Ifname not reported")
|
||||||
fail(dev["ifname"] != sim["ifname"],
|
fail(dev["ifname"] != sim["ifname"],
|
||||||
"Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
|
"Ifname incorrect %s vs %s" % (dev["ifname"], sim["ifname"]))
|
||||||
else:
|
else:
|
||||||
fail("ifname" in dev.keys(), "Ifname is reported for other ns")
|
fail("ifname" in dev.keys(), "Ifname is reported for other ns")
|
||||||
if removed:
|
|
||||||
fail(dev["ifindex"] != 0, "Device perameters not zero on removed")
|
maps = bpftool_map_list(expected=2, ns=ns)
|
||||||
fail(dev["ns_dev"] != 0, "Device perameters not zero on removed")
|
for m in maps:
|
||||||
fail(dev["ns_inode"] != 0, "Device perameters not zero on removed")
|
fail("dev" not in m.keys(), "Device parameters not reported")
|
||||||
|
fail(dev != m["dev"], "Map's device different than program's")
|
||||||
|
|
||||||
# Parse command line
|
# Parse command line
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
@ -464,7 +521,7 @@ if out.find("/sys/kernel/debug type debugfs") == -1:
|
||||||
cmd("mount -t debugfs none /sys/kernel/debug")
|
cmd("mount -t debugfs none /sys/kernel/debug")
|
||||||
|
|
||||||
# Check samples are compiled
|
# Check samples are compiled
|
||||||
samples = ["sample_ret0.o"]
|
samples = ["sample_ret0.o", "sample_map_ret0.o"]
|
||||||
for s in samples:
|
for s in samples:
|
||||||
ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
|
ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
|
||||||
skip(ret != 0, "sample %s/%s not found, please compile it" %
|
skip(ret != 0, "sample %s/%s not found, please compile it" %
|
||||||
|
@ -739,8 +796,9 @@ try:
|
||||||
bpftool_prog_list_wait(expected=0)
|
bpftool_prog_list_wait(expected=0)
|
||||||
|
|
||||||
sim = NetdevSim()
|
sim = NetdevSim()
|
||||||
sim.set_ethtool_tc_offloads(True)
|
map_obj = bpf_obj("sample_map_ret0.o")
|
||||||
sim.set_xdp(obj, "offload")
|
start_test("Test loading program with maps...")
|
||||||
|
sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
|
||||||
|
|
||||||
start_test("Test bpftool bound info reporting (own ns)...")
|
start_test("Test bpftool bound info reporting (own ns)...")
|
||||||
check_dev_info(False, "")
|
check_dev_info(False, "")
|
||||||
|
@ -757,11 +815,111 @@ try:
|
||||||
sim.set_ns("")
|
sim.set_ns("")
|
||||||
check_dev_info(False, "")
|
check_dev_info(False, "")
|
||||||
|
|
||||||
pin_file, _ = pin_prog("/sys/fs/bpf/tmp")
|
prog_file, _ = pin_prog("/sys/fs/bpf/tmp_prog")
|
||||||
|
map_file, _ = pin_map("/sys/fs/bpf/tmp_map", idx=1, expected=2)
|
||||||
sim.remove()
|
sim.remove()
|
||||||
|
|
||||||
start_test("Test bpftool bound info reporting (removed dev)...")
|
start_test("Test bpftool bound info reporting (removed dev)...")
|
||||||
check_dev_info(True, "", pin_file=pin_file, removed=True)
|
check_dev_info_removed(prog_file=prog_file, map_file=map_file)
|
||||||
|
|
||||||
|
# Remove all pinned files and reinstantiate the netdev
|
||||||
|
clean_up()
|
||||||
|
bpftool_prog_list_wait(expected=0)
|
||||||
|
|
||||||
|
sim = NetdevSim()
|
||||||
|
|
||||||
|
start_test("Test map update (no flags)...")
|
||||||
|
sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
|
||||||
|
maps = bpftool_map_list(expected=2)
|
||||||
|
array = maps[0] if maps[0]["type"] == "array" else maps[1]
|
||||||
|
htab = maps[0] if maps[0]["type"] == "hash" else maps[1]
|
||||||
|
for m in maps:
|
||||||
|
for i in range(2):
|
||||||
|
bpftool("map update id %d key %s value %s" %
|
||||||
|
(m["id"], int2str("I", i), int2str("Q", i * 3)))
|
||||||
|
|
||||||
|
for m in maps:
|
||||||
|
ret, _ = bpftool("map update id %d key %s value %s" %
|
||||||
|
(m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
|
||||||
|
fail=False)
|
||||||
|
fail(ret == 0, "added too many entries")
|
||||||
|
|
||||||
|
start_test("Test map update (exists)...")
|
||||||
|
for m in maps:
|
||||||
|
for i in range(2):
|
||||||
|
bpftool("map update id %d key %s value %s exist" %
|
||||||
|
(m["id"], int2str("I", i), int2str("Q", i * 3)))
|
||||||
|
|
||||||
|
for m in maps:
|
||||||
|
ret, err = bpftool("map update id %d key %s value %s exist" %
|
||||||
|
(m["id"], int2str("I", 3), int2str("Q", 3 * 3)),
|
||||||
|
fail=False)
|
||||||
|
fail(ret == 0, "updated non-existing key")
|
||||||
|
fail(err["error"].find("No such file or directory") == -1,
|
||||||
|
"expected ENOENT, error is '%s'" % (err["error"]))
|
||||||
|
|
||||||
|
start_test("Test map update (noexist)...")
|
||||||
|
for m in maps:
|
||||||
|
for i in range(2):
|
||||||
|
ret, err = bpftool("map update id %d key %s value %s noexist" %
|
||||||
|
(m["id"], int2str("I", i), int2str("Q", i * 3)),
|
||||||
|
fail=False)
|
||||||
|
fail(ret == 0, "updated existing key")
|
||||||
|
fail(err["error"].find("File exists") == -1,
|
||||||
|
"expected EEXIST, error is '%s'" % (err["error"]))
|
||||||
|
|
||||||
|
start_test("Test map dump...")
|
||||||
|
for m in maps:
|
||||||
|
_, entries = bpftool("map dump id %d" % (m["id"]))
|
||||||
|
for i in range(2):
|
||||||
|
key = str2int(entries[i]["key"])
|
||||||
|
fail(key != i, "expected key %d, got %d" % (key, i))
|
||||||
|
val = str2int(entries[i]["value"])
|
||||||
|
fail(val != i * 3, "expected value %d, got %d" % (val, i * 3))
|
||||||
|
|
||||||
|
start_test("Test map getnext...")
|
||||||
|
for m in maps:
|
||||||
|
_, entry = bpftool("map getnext id %d" % (m["id"]))
|
||||||
|
key = str2int(entry["next_key"])
|
||||||
|
fail(key != 0, "next key %d, expected %d" % (key, 0))
|
||||||
|
_, entry = bpftool("map getnext id %d key %s" %
|
||||||
|
(m["id"], int2str("I", 0)))
|
||||||
|
key = str2int(entry["next_key"])
|
||||||
|
fail(key != 1, "next key %d, expected %d" % (key, 1))
|
||||||
|
ret, err = bpftool("map getnext id %d key %s" %
|
||||||
|
(m["id"], int2str("I", 1)), fail=False)
|
||||||
|
fail(ret == 0, "got next key past the end of map")
|
||||||
|
fail(err["error"].find("No such file or directory") == -1,
|
||||||
|
"expected ENOENT, error is '%s'" % (err["error"]))
|
||||||
|
|
||||||
|
start_test("Test map delete (htab)...")
|
||||||
|
for i in range(2):
|
||||||
|
bpftool("map delete id %d key %s" % (htab["id"], int2str("I", i)))
|
||||||
|
|
||||||
|
start_test("Test map delete (array)...")
|
||||||
|
for i in range(2):
|
||||||
|
ret, err = bpftool("map delete id %d key %s" %
|
||||||
|
(htab["id"], int2str("I", i)), fail=False)
|
||||||
|
fail(ret == 0, "removed entry from an array")
|
||||||
|
fail(err["error"].find("No such file or directory") == -1,
|
||||||
|
"expected ENOENT, error is '%s'" % (err["error"]))
|
||||||
|
|
||||||
|
start_test("Test map remove...")
|
||||||
|
sim.unset_xdp("offload")
|
||||||
|
bpftool_map_list_wait(expected=0)
|
||||||
|
sim.remove()
|
||||||
|
|
||||||
|
sim = NetdevSim()
|
||||||
|
sim.set_xdp(map_obj, "offload", JSON=False) # map fixup msg breaks JSON
|
||||||
|
sim.remove()
|
||||||
|
bpftool_map_list_wait(expected=0)
|
||||||
|
|
||||||
|
start_test("Test map creation fail path...")
|
||||||
|
sim = NetdevSim()
|
||||||
|
sim.dfs["bpf_map_accept"] = "N"
|
||||||
|
ret, _ = sim.set_xdp(map_obj, "offload", JSON=False, fail=False)
|
||||||
|
fail(ret == 0,
|
||||||
|
"netdevsim didn't refuse to create a map with offload disabled")
|
||||||
|
|
||||||
print("%s: OK" % (os.path.basename(__file__)))
|
print("%s: OK" % (os.path.basename(__file__)))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue