public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
From: Bobby Eshleman <bobbyeshleman@gmail.com>
To: Wei Wang <weibunny@fb.com>
Cc: netdev@vger.kernel.org, Jakub Kicinski <kuba@kernel.org>,
	Daniel Zahka <daniel.zahka@gmail.com>,
	Willem de Bruijn <willemdebruijn.kernel@gmail.com>,
	David Wei <dw@davidwei.uk>, Andrew Lunn <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Daniel Borkmann <daniel@iogearbox.net>
Subject: Re: [PATCH net-next 3/9] selftests/net: Add env for container based tests
Date: Tue, 24 Feb 2026 10:10:31 -0800	[thread overview]
Message-ID: <aZ3pl177Q6+uJCCN@devvm11784.nha0.facebook.com> (raw)
In-Reply-To: <20260224002410.1553838-4-weibunny@fb.com>

On Mon, Feb 23, 2026 at 04:24:03PM -0800, Wei Wang wrote:
> From: David Wei <dw@davidwei.uk>
> 
> 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>
> ---
>  .../testing/selftests/drivers/net/README.rst  |  19 ++
>  .../drivers/net/hw/lib/py/__init__.py         |   7 +-
>  .../selftests/drivers/net/lib/py/__init__.py  |   7 +-
>  .../selftests/drivers/net/lib/py/env.py       | 163 ++++++++++++++++++
>  4 files changed, 190 insertions(+), 6 deletions(-)
> 
> diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
> index eb838ae94844..39370a83f238 100644
> --- a/tools/testing/selftests/drivers/net/README.rst
> +++ b/tools/testing/selftests/drivers/net/README.rst
> @@ -62,6 +62,25 @@ LOCAL_V4, LOCAL_V6, REMOTE_V4, REMOTE_V6
>  
>  Local and remote endpoint IP addresses.
>  
> +LOCAL_PREFIX_V4, LOCAL_PREFIX_V6
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Still not sure we want LOCAL_PREFIX_V4 here, since it is not supported?

> +
> +Local IP prefix/subnet which can be used to allocate extra IP addresses (for
> +network name spaces behind macvlan, veth, netkit devices). DUT must be
> +reachable using these addresses from the endpoint.
> +
> +  +-------------+        +----------------------------+
> +  | INIT NS     |        | TEST NS                    |
> +  | +---------+ |        | +------------------------+ |
> +  | | NETIF   | |  bpf   | | Netkit                 | |
> +  | |         |-|--------|>| nk_guest               | |
> +  | +---------+ |        | | {LOCAL_PREFIX_V6}::2:2 | |
> +  | +---------+ |        | +------------------------+ |
> +  | | Netkit  | |        +----------------------------+
> +  | | nk_host | |
> +  | +---------+ |
> +  +-------------+
> +

One thing I noticed when I tested this was the flow broke down when
NETIF's address happened to match LOCAL_PREFIX_V6. I didn't fully reason
through why this was the case, but maybe it's worth mentioning in the
docs here?

>  REMOTE_TYPE
>  ~~~~~~~~~~~
>  
> diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
> index d5d247eca6b7..022008249313 100644
> --- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
> +++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
> @@ -3,6 +3,7 @@
>  """
>  Driver test environment (hardware-only tests).
>  NetDrvEnv and NetDrvEpEnv are the main environment classes.
> +NetDrvContEnv extends NetDrvEpEnv with netkit container support.
>  Former is for local host only tests, latter creates / connects
>  to a remote endpoint. See NIPA wiki for more information about
>  running and writing driver tests.
> @@ -29,7 +30,7 @@ try:
>      from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
>          ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
>      from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner
> -    from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv
> +    from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
>  
>      __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev",
>                 "EthtoolFamily", "NetdevFamily", "NetshaperFamily",
> @@ -44,8 +45,8 @@ try:
>                 "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
>                 "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
>                 "ksft_not_none", "ksft_not_none",
> -               "NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote",
> -               "Iperf3Runner"]
> +               "NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic",
> +               "Remote", "Iperf3Runner"]
>  except ModuleNotFoundError as e:
>      print("Failed importing `net` library from kernel sources")
>      print(str(e))
> diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
> index a18e21069f7a..6b55068d5370 100644
> --- a/tools/testing/selftests/drivers/net/lib/py/__init__.py
> +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
> @@ -3,6 +3,7 @@
>  """
>  Driver test environment.
>  NetDrvEnv and NetDrvEpEnv are the main environment classes.
> +NetDrvContEnv extends NetDrvEpEnv with netkit container support.
>  Former is for local host only tests, latter creates / connects
>  to a remote endpoint. See NIPA wiki for more information about
>  running and writing driver tests.
> @@ -43,12 +44,12 @@ try:
>                 "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
>                 "ksft_not_none", "ksft_not_none"]
>  
> -    from .env import NetDrvEnv, NetDrvEpEnv
> +    from .env import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv
>      from .load import GenerateTraffic, Iperf3Runner
>      from .remote import Remote
>  
> -    __all__ += ["NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote",
> -                "Iperf3Runner"]
> +    __all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic",
> +                "Remote", "Iperf3Runner"]
>  except ModuleNotFoundError as e:
>      print("Failed importing `net` library from kernel sources")
>      print(str(e))
> diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
> index 41cc248ac848..857ae0f37516 100644
> --- a/tools/testing/selftests/drivers/net/lib/py/env.py
> +++ b/tools/testing/selftests/drivers/net/lib/py/env.py
> @@ -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,163 @@ 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.
> +    +-------------+        +----------------------------+
> +    | INIT NS     |        | TEST NS                    |
> +    | +---------+ |        | +------------------------+ |
> +    | | NETIF   | |  bpf   | | Netkit                 | |
> +    | |         |-|--------|>| nk_guest               | |
> +    | +---------+ |        | | {LOCAL_PREFIX_V6}::2:2 | |
> +    | +---------+ |        | +------------------------+ |
> +    | | Netkit  | |        +----------------------------+
> +    | | nk_host | |
> +    | +---------+ |
> +    +-------------+
> +    """
> +
> +    def __init__(self, src_path, rxqueues=1, **kwargs):
> +        super().__init__(src_path, **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

One problem with these attrs is that if super().__init__() fails
(NetDrvEpEnv.__init__()) then these will raise attribute errors in
__del__().

This can happen if NetDrvEpEnv.remote_remote_ifc() (for example) throws
an exception because it couldn't find the address the user configured.

One alternative to getattr() is moving these assignments before
super().__init__()?

Best,
Bobby

> +
> +        self.require_ipver("6")
> +        local_prefix = self.env.get("LOCAL_PREFIX_V6")
> +        if not local_prefix:
> +            raise KsftSkipEx("LOCAL_PREFIX_V6 required")
> +
> +        local_prefix = local_prefix.rstrip("/64").rstrip("::").rstrip(":")
> +        self.ipv6_prefix = f"{local_prefix}::"
> +        self.nk_host_ipv6 = f"{local_prefix}::2:1"
> +        self.nk_guest_ipv6 = f"{local_prefix}::2:2"
> +
> +        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
> +
> +        super().__del__()
> +
> +    def _setup_ns(self):
> +        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):
> +        filter = json.loads(cmd(f"tc -j filter show dev {self.ifname} ingress").stdout)
> +        for bpf in filter:
> +            if 'options' not in bpf:
> +                continue
> +            if bpf['options']['bpf_name'].startswith('nk_forward.bpf'):
> +                return (bpf['pref'], bpf['options']['prog']['id'])
> +        if self._bpf_prog_pref is None:
> +            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}")
> -- 
> 2.47.3
> 

  reply	other threads:[~2026-02-24 18:10 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-24  0:24 [PATCH net-next 0/9] psp: Add support for dev-assoc/disassoc Wei Wang
2026-02-24  0:24 ` [PATCH net-next 2/9] selftests/net: Export Netlink class via lib.py Wei Wang
2026-02-24  0:24 ` [PATCH net-next 3/9] selftests/net: Add env for container based tests Wei Wang
2026-02-24 18:10   ` Bobby Eshleman [this message]
2026-02-28  2:30     ` Jakub Kicinski
2026-03-01  4:15       ` David Wei
2026-03-01  4:17       ` David Wei
2026-03-01  4:18     ` David Wei
2026-02-24  0:24 ` [PATCH net-next 4/9] selftests/net: Add netkit container ping test Wei Wang
2026-02-24  0:24 ` [PATCH net-next 5/9] psp: add unprivileged version of psp_device_get_locked Wei Wang
2026-02-24  0:24 ` [PATCH net-next 6/9] psp: Add new netlink cmd for dev-assoc and dev-disassoc Wei Wang
2026-02-24  0:24 ` [PATCH net-next 7/9] psp: add a new netdev event for dev unregister Wei Wang
2026-02-24  0:24 ` [PATCH net-next 8/9] selftests/net: Add bpf skb forwarding program Wei Wang
2026-02-24 18:56   ` Bobby Eshleman
2026-02-24  0:24 ` [PATCH net-next 9/9] selftest/net: psp: Add test for dev-assoc/disassoc Wei Wang
2026-02-28  2:33   ` Jakub Kicinski
     [not found] ` <20260224002410.1553838-2-weibunny@fb.com>
2026-02-28  2:34   ` [PATCH net-next 1/9] selftests/net: Add bpf skb forwarding program Jakub Kicinski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aZ3pl177Q6+uJCCN@devvm11784.nha0.facebook.com \
    --to=bobbyeshleman@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=daniel.zahka@gmail.com \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=dw@davidwei.uk \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=weibunny@fb.com \
    --cc=willemdebruijn.kernel@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox