mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 14:53:58 -04:00
selftests/net: Add env for container based tests
Add an env NetDrvContEnv for container based selftests. This automates the setup of a netns, netkit pair with one inside the netns, and a BPF program that forwards skbs from the NETIF host inside the container. Currently only netkit is used, but other virtual netdevs e.g. veth can be used too. Expect netkit container datapath selftests to have a publicly routable IP prefix to assign to netkit in a container, such that packets will land on eth0. The BPF skb forward program will then forward such packets from the host netns to the container netns. Signed-off-by: David Wei <dw@davidwei.uk> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Link: https://patch.msgid.link/20260305181803.2912736-4-dw@davidwei.uk Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
68ab8ba927
commit
3f74d5bb80
@@ -1,13 +1,16 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import ipaddress
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
from pathlib import Path
|
||||
from lib.py import KsftSkipEx, KsftXfailEx
|
||||
from lib.py import ksft_setup, wait_file
|
||||
from lib.py import cmd, ethtool, ip, CmdExitFailure
|
||||
from lib.py import NetNS, NetdevSimDev
|
||||
from .remote import Remote
|
||||
from . import bpftool, RtnlFamily, Netlink
|
||||
|
||||
|
||||
class NetDrvEnvBase:
|
||||
@@ -289,3 +292,207 @@ class NetDrvEpEnv(NetDrvEnvBase):
|
||||
data.get('stats-block-usecs', 0) / 1000 / 1000
|
||||
|
||||
time.sleep(self._stats_settle_time)
|
||||
|
||||
|
||||
class NetDrvContEnv(NetDrvEpEnv):
|
||||
"""
|
||||
Class for an environment with a netkit pair setup for forwarding traffic
|
||||
between the physical interface and a network namespace.
|
||||
NETIF = "eth0"
|
||||
LOCAL_V6 = "2001:db8:1::1"
|
||||
REMOTE_V6 = "2001:db8:1::2"
|
||||
LOCAL_PREFIX_V6 = "2001:db8:2::0/64"
|
||||
|
||||
+-----------------------------+ +------------------------------+
|
||||
dst | INIT NS | | TEST NS |
|
||||
2001: | +---------------+ | | |
|
||||
db8:2::2| | NETIF | | bpf | |
|
||||
+---|>| 2001:db8:1::1 | |redirect| +-------------------------+ |
|
||||
| | | |-----------|--------|>| Netkit | |
|
||||
| | +---------------+ | _peer | | nk_guest | |
|
||||
| | +-------------+ Netkit pair | | | fe80::2/64 | |
|
||||
| | | Netkit |.............|........|>| 2001:db8:2::2/64 | |
|
||||
| | | nk_host | | | +-------------------------+ |
|
||||
| | | fe80::1/64 | | | |
|
||||
| | +-------------+ | | route: |
|
||||
| | | | default |
|
||||
| | route: | | via fe80::1 dev nk_guest |
|
||||
| | 2001:db8:2::2/128 | +------------------------------+
|
||||
| | via fe80::2 dev nk_host |
|
||||
| +-----------------------------+
|
||||
|
|
||||
| +---------------+
|
||||
| | REMOTE |
|
||||
+---| 2001:db8:1::2 |
|
||||
+---------------+
|
||||
"""
|
||||
|
||||
def __init__(self, src_path, rxqueues=1, **kwargs):
|
||||
self.netns = None
|
||||
self._nk_host_ifname = None
|
||||
self._nk_guest_ifname = None
|
||||
self._tc_clsact_added = False
|
||||
self._tc_attached = False
|
||||
self._bpf_prog_pref = None
|
||||
self._bpf_prog_id = None
|
||||
self._init_ns_attached = False
|
||||
self._old_fwd = None
|
||||
self._old_accept_ra = None
|
||||
|
||||
super().__init__(src_path, **kwargs)
|
||||
|
||||
self.require_ipver("6")
|
||||
local_prefix = self.env.get("LOCAL_PREFIX_V6")
|
||||
if not local_prefix:
|
||||
raise KsftSkipEx("LOCAL_PREFIX_V6 required")
|
||||
|
||||
net = ipaddress.IPv6Network(local_prefix, strict=False)
|
||||
self.ipv6_prefix = str(net.network_address)
|
||||
self.nk_host_ipv6 = f"{self.ipv6_prefix}2:1"
|
||||
self.nk_guest_ipv6 = f"{self.ipv6_prefix}2:2"
|
||||
|
||||
local_v6 = ipaddress.IPv6Address(self.addr_v["6"])
|
||||
if local_v6 in net:
|
||||
raise KsftSkipEx("LOCAL_V6 must not fall within LOCAL_PREFIX_V6")
|
||||
|
||||
rtnl = RtnlFamily()
|
||||
rtnl.newlink(
|
||||
{
|
||||
"linkinfo": {
|
||||
"kind": "netkit",
|
||||
"data": {
|
||||
"mode": "l2",
|
||||
"policy": "forward",
|
||||
"peer-policy": "forward",
|
||||
},
|
||||
},
|
||||
"num-rx-queues": rxqueues,
|
||||
},
|
||||
flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
|
||||
)
|
||||
|
||||
all_links = ip("-d link show", json=True)
|
||||
netkit_links = [link for link in all_links
|
||||
if link.get('linkinfo', {}).get('info_kind') == 'netkit'
|
||||
and 'UP' not in link.get('flags', [])]
|
||||
|
||||
if len(netkit_links) != 2:
|
||||
raise KsftSkipEx("Failed to create netkit pair")
|
||||
|
||||
netkit_links.sort(key=lambda x: x['ifindex'])
|
||||
self._nk_host_ifname = netkit_links[1]['ifname']
|
||||
self._nk_guest_ifname = netkit_links[0]['ifname']
|
||||
self.nk_host_ifindex = netkit_links[1]['ifindex']
|
||||
self.nk_guest_ifindex = netkit_links[0]['ifindex']
|
||||
|
||||
self._setup_ns()
|
||||
self._attach_bpf()
|
||||
|
||||
def __del__(self):
|
||||
if self._tc_attached:
|
||||
cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
|
||||
self._tc_attached = False
|
||||
|
||||
if self._tc_clsact_added:
|
||||
cmd(f"tc qdisc del dev {self.ifname} clsact")
|
||||
self._tc_clsact_added = False
|
||||
|
||||
if self._nk_host_ifname:
|
||||
cmd(f"ip link del dev {self._nk_host_ifname}")
|
||||
self._nk_host_ifname = None
|
||||
self._nk_guest_ifname = None
|
||||
|
||||
if self._init_ns_attached:
|
||||
cmd("ip netns del init", fail=False)
|
||||
self._init_ns_attached = False
|
||||
|
||||
if self.netns:
|
||||
del self.netns
|
||||
self.netns = None
|
||||
|
||||
if self._old_fwd is not None:
|
||||
with open("/proc/sys/net/ipv6/conf/all/forwarding", "w",
|
||||
encoding="utf-8") as f:
|
||||
f.write(self._old_fwd)
|
||||
self._old_fwd = None
|
||||
if self._old_accept_ra is not None:
|
||||
with open("/proc/sys/net/ipv6/conf/all/accept_ra", "w",
|
||||
encoding="utf-8") as f:
|
||||
f.write(self._old_accept_ra)
|
||||
self._old_accept_ra = None
|
||||
|
||||
super().__del__()
|
||||
|
||||
def _setup_ns(self):
|
||||
fwd_path = "/proc/sys/net/ipv6/conf/all/forwarding"
|
||||
ra_path = "/proc/sys/net/ipv6/conf/all/accept_ra"
|
||||
with open(fwd_path, encoding="utf-8") as f:
|
||||
self._old_fwd = f.read().strip()
|
||||
with open(ra_path, encoding="utf-8") as f:
|
||||
self._old_accept_ra = f.read().strip()
|
||||
with open(fwd_path, "w", encoding="utf-8") as f:
|
||||
f.write("1")
|
||||
with open(ra_path, "w", encoding="utf-8") as f:
|
||||
f.write("2")
|
||||
|
||||
self.netns = NetNS()
|
||||
cmd("ip netns attach init 1")
|
||||
self._init_ns_attached = True
|
||||
ip("netns set init 0", ns=self.netns)
|
||||
ip(f"link set dev {self._nk_guest_ifname} netns {self.netns.name}")
|
||||
ip(f"link set dev {self._nk_host_ifname} up")
|
||||
ip(f"-6 addr add fe80::1/64 dev {self._nk_host_ifname} nodad")
|
||||
ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self._nk_host_ifname}")
|
||||
|
||||
ip("link set lo up", ns=self.netns)
|
||||
ip(f"link set dev {self._nk_guest_ifname} up", ns=self.netns)
|
||||
ip(f"-6 addr add fe80::2/64 dev {self._nk_guest_ifname}", ns=self.netns)
|
||||
ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns)
|
||||
ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns)
|
||||
|
||||
def _tc_ensure_clsact(self):
|
||||
qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)
|
||||
for q in qdisc:
|
||||
if q['kind'] == 'clsact':
|
||||
return
|
||||
cmd(f"tc qdisc add dev {self.ifname} clsact")
|
||||
self._tc_clsact_added = True
|
||||
|
||||
def _get_bpf_prog_ids(self):
|
||||
filters = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout)
|
||||
for bpf in filters:
|
||||
if 'options' not in bpf:
|
||||
continue
|
||||
if bpf['options']['bpf_name'].startswith('nk_forward.bpf'):
|
||||
return (bpf['pref'], bpf['options']['prog']['id'])
|
||||
raise Exception("Failed to get BPF prog ID")
|
||||
|
||||
def _attach_bpf(self):
|
||||
bpf_obj = self.test_dir / "nk_forward.bpf.o"
|
||||
if not bpf_obj.exists():
|
||||
raise KsftSkipEx("BPF prog not found")
|
||||
|
||||
self._tc_ensure_clsact()
|
||||
cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj}"
|
||||
" sec tc/ingress direct-action")
|
||||
self._tc_attached = True
|
||||
|
||||
(self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids()
|
||||
prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True)
|
||||
map_ids = prog_info.get("map_ids", [])
|
||||
|
||||
bss_map_id = None
|
||||
for map_id in map_ids:
|
||||
map_info = bpftool(f"map show id {map_id}", json=True)
|
||||
if map_info.get("name").endswith("bss"):
|
||||
bss_map_id = map_id
|
||||
|
||||
if bss_map_id is None:
|
||||
raise Exception("Failed to find .bss map")
|
||||
|
||||
ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix)
|
||||
ipv6_bytes = ipv6_addr.packed
|
||||
ifindex_bytes = self.nk_host_ifindex.to_bytes(4, byteorder='little')
|
||||
value = ipv6_bytes + ifindex_bytes
|
||||
value_hex = ' '.join(f'{b:02x}' for b in value)
|
||||
bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
|
||||
|
||||
Reference in New Issue
Block a user