Skip to contentSkip to conversation

selftests: drv-net: tso: add double tunneling GSO tests

RFC In review Xu Du 2 participants 10 messages Apr 7 – Apr 10

Subsystems (3)
lore
Share & tools
lore

Xu Du

author
#0 lore raw

cover selftests: drv-net: tso: add double tunneling GSO tests

This series extends the TSO selftest (tso.py) to cover double-encapsulated tunnel scenarios, ensuring that hardware offload and the kernel GSO path correctly handle packets with two layers of tunnel headers.

Xu Du (4):
selftests: drv-net: tso: retry connect on EHOSTUNREACH
selftests: drv-net: tso: add helpers for double tunneling GSO
selftests: drv-net: tso: add Geneve double tunneling GSO test
selftests: drv-net: tso: expand double tunnel GSO test coverage

 .../drivers/net/hw/lib/py/__init__.py         |   4 +-
 tools/testing/selftests/drivers/net/hw/tso.py | 267 +++++++++++++++---
 .../selftests/drivers/net/lib/py/__init__.py  |   4 +-
 .../testing/selftests/net/lib/py/__init__.py  |   4 +-
 tools/testing/selftests/net/lib/py/utils.py   |  19 +-
 5 files changed, 248 insertions(+), 50 deletions(-)

base-commit: 2f60df9e61aa48bf40c36254bf2e839f09cffd98

Signature · 1 line
2.53.0
author
#1 lore raw

RFC 1/4 selftests: drv-net: tso: retry connect on EHOSTUNREACH

The TSO test occasionally fails with "No route to host" (EHOSTUNREACH) when connecting to the remote socat listener. This can happen when the neighbor resolution has not yet completed by the time the test attempts to connect, particularly in tunnel setups where neighbor entries may take slightly longer to establish.

Add a retry loop (up to 5 attempts with 0.5s delay) around the sock.connect() call to handle this transient condition gracefully.

---
 tools/testing/selftests/drivers/net/hw/tso.py | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)
tools/testing/selftests/drivers/net/hw/tso.py +13 -2
index bb675e3dac88..f792115adfb3 100755
@@ -3,6 +3,7 @@
 
 """Run the tools/testing/selftests/net/csum testsuite."""
 
+import errno
 import fcntl
 import socket
 import struct
@@ -47,10 +48,20 @@ def run_one_stream(cfg, ipver, remote_v4, remote_v6, should_lso):
 
         if ipver == "4":
             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            sock.connect((remote_v4, port))
+            sockaddr = (remote_v4, port)
         else:
             sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
-            sock.connect((remote_v6, port))
+            sockaddr = (remote_v6, port)
+
+        for attempt in range(5):
+            try:
+                sock.connect(sockaddr)
+                break
+            except OSError as e:
+                if e.errno == errno.EHOSTUNREACH and attempt < 4:
+                    time.sleep(0.5)
+                else:
+                    raise
 
         # Small send to make sure the connection is working.
         sock.send("ping".encode())
Signature · 1 line
2.53.0
author
#2 lore raw

RFC 2/4 selftests: drv-net: tso: add helpers for double tunneling GSO

In reply to Xu Du

Add helper functions to support double tunneling GSO testing. Since certain tunnel-specific parameters (e.g., gro-hint) are not yet supported by the standard ip-link tool, this patch adds a new helper function utilizing the ynl-cli to handle these extended netlink attributes.

As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.

---
 .../drivers/net/hw/lib/py/__init__.py         |  4 ++--
 .../selftests/drivers/net/lib/py/__init__.py  |  4 ++--
 .../testing/selftests/net/lib/py/__init__.py  |  4 ++--
 tools/testing/selftests/net/lib/py/utils.py   | 19 +++++++++++++++++--
 4 files changed, 23 insertions(+), 8 deletions(-)
tools/testing/selftests/drivers/net/hw/lib/py/__init__.py +2 -2
index df4da5078c48..34a06e1afcb5 100644
@@ -24,7 +24,7 @@ try:
     from net.lib.py import CmdExitFailure
     from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
         fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, \
-        wait_file, tool
+        wait_file, tool, ynlcli
     from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
     from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
     from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
@@ -40,7 +40,7 @@ try:
                "CmdExitFailure",
                "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
                "fd_read_timeout", "ip", "rand_port", "rand_ports",
-               "wait_port_listen", "wait_file", "tool",
+               "wait_port_listen", "wait_file", "tool", "ynlcli",
                "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
                "KsftSkipEx", "KsftFailEx", "KsftXfailEx",
                "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
tools/testing/selftests/drivers/net/lib/py/__init__.py +2 -2
index 2b5ec0505672..a9eb1a57fcc4 100644
@@ -22,7 +22,7 @@ try:
     from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \
         NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink
     from net.lib.py import CmdExitFailure
-    from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
+    from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, ynlcli, \
         fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file
     from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
     from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
@@ -36,7 +36,7 @@ try:
                "NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink",
                "CmdExitFailure",
                "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
-               "fd_read_timeout", "ip", "rand_port", "rand_ports",
+               "fd_read_timeout", "ip", "rand_port", "rand_ports", "ynlcli",
                "wait_port_listen", "wait_file",
                "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
                "KsftSkipEx", "KsftFailEx", "KsftXfailEx",
tools/testing/selftests/net/lib/py/__init__.py +2 -2
index 7c81d86a7e97..a3337666a856 100644
@@ -14,7 +14,7 @@ from .netns import NetNS, NetNSEnter
 from .nsim import NetdevSim, NetdevSimDev
 from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
     bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \
-    wait_file, tool
+    wait_file, tool, ynlcli
 from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
 from .ynl import NlError, NlctrlFamily, YnlFamily, \
     EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily
@@ -29,7 +29,7 @@ __all__ = ["KSRC",
            "NetNS", "NetNSEnter",
            "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer",
            "bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports",
-           "wait_port_listen", "wait_file", "tool",
+           "wait_port_listen", "wait_file", "tool", "ynlcli",
            "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
            "NetdevSim", "NetdevSimDev",
            "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
tools/testing/selftests/net/lib/py/utils.py +17 -2
index 6c44a3d2bbf7..a14a4b5dd592 100644
@@ -8,6 +8,8 @@ import socket
 import subprocess
 import time
 
+from .consts import KSRC, KSFT_DIR
+
 
 class CmdInitFailure(Exception):
     """ Command failed to start. Only raised by bkg(). """
@@ -217,12 +219,12 @@ class defer:
         self.exec_only()
 
 
-def tool(name, args, json=None, ns=None, host=None):
+def tool(name, args, json=None, ns=None, host=None, shell=None):
     cmd_str = name + ' '
     if json:
         cmd_str += '--json '
     cmd_str += args
-    cmd_obj = cmd(cmd_str, ns=ns, host=host)
+    cmd_obj = cmd(cmd_str, ns=ns, host=host, shell=shell)
     if json:
         return _json.loads(cmd_obj.stdout)
     return cmd_obj
@@ -242,6 +244,19 @@ def ethtool(args, json=None, ns=None, host=None):
     return tool('ethtool', args, json=json, ns=ns, host=host)
 
 
+def ynlcli(family, args, json=None, ns=None, host=None):
+    if (KSFT_DIR / "kselftest-list.txt").exists():
+        cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py"
+        spec = KSFT_DIR / f"net/lib/specs/{family}.yaml"
+    else:
+        cli = KSRC / "tools/net/ynl/pyynl/cli.py"
+        spec = KSRC / f"Documentation/netlink/specs/{family}.yaml"
+    if not cli.exists():
+        raise FileNotFoundError(f"cli not found at {cli}")
+    args = f"--spec {spec} --no-schema {args}"
+    return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True)
+
+
 def bpftrace(expr, json=None, ns=None, host=None, timeout=None):
     """
     Run bpftrace and return map data (if json=True).
Signature · 1 line
2.53.0
maintainer
#3 lore raw

Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO

Quoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:

As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.

Can you explain more? Why can't you use class RtnlFamily?

author
#4 lore raw

Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO

Quoted 9 lines · Jakub Kicinski
Quoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:

As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.

Can you explain more? Why can't you use class RtnlFamily?

Hi Jakub,
I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.

Signature · 3 lines
Regards,

Xu
maintainer
#5 lore raw

Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO

Quoted 8 lines · On Wed, 8 Apr 2026 10:04:09 +0800 Xu Du wrote:
Quoted 7 lines · Jakub Kicinski
Quoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:

As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.

Quoted 7 lines
Quoted 1 line

Can you explain more? Why can't you use class RtnlFamily?

I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.

Oh. But that's not really what you're doing:

+def ynlcli(family, args, json=None, ns=None, host=None):
+ if (KSFT_DIR / "kselftest-list.txt").exists():
+ cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py"
+ spec = KSFT_DIR / f"net/lib/specs/{family}.yaml"
+ else:
+ cli = KSRC / "tools/net/ynl/pyynl/cli.py"
+ spec = KSRC / f"Documentation/netlink/specs/{family}.yaml"
+ if not cli.exists():
+ raise FileNotFoundError(f"cli not found at {cli}")
+ args = f"--spec {spec} --no-schema {args}"
+ return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True)

You're not deploying anything to the remote system. Are you assuming that the remote system magically has the same filesystem layout?

You can use the ynl CLI but it has to be whatever version is on the remote system. Just call ynl --family rt-link, don't dig around for the spec paths etc.

author
#6 lore raw

Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO

Quoted 32 lines · Jakub Kicinski
Quoted 8 lines · On Wed, 8 Apr 2026 10:04:09 +0800 Xu Du wrote:
Quoted 7 lines · Jakub Kicinski
Quoted 4 lines · On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:

As the YNL Python module cannot be invoked across different devices or environments directly in its current form, the helper abstracts the YNL CLI calls to ensure proper configuration of the tunneling device features.

Quoted 7 lines
Quoted 1 line

Can you explain more? Why can't you use class RtnlFamily?

I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.

Oh. But that's not really what you're doing:

tools/testing/selftests/net/lib/py/utils.py +11 -0
+def ynlcli(family, args, json=None, ns=None, host=None):
+    if (KSFT_DIR / "kselftest-list.txt").exists():
+        cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py"
+        spec = KSFT_DIR / f"net/lib/specs/{family}.yaml"
+    else:
+        cli = KSRC / "tools/net/ynl/pyynl/cli.py"
+        spec = KSRC / f"Documentation/netlink/specs/{family}.yaml"
+    if not cli.exists():
+        raise FileNotFoundError(f"cli not found at {cli}")
+    args = f"--spec {spec} --no-schema {args}"
+    return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True)
Quoted 8 lines

You're not deploying anything to the remote system. Are you assuming that the remote system magically has the same filesystem layout?

You can use the ynl CLI but it has to be whatever version is on the remote system. Just call ynl --family rt-link, don't dig around for the spec paths etc.

In fact, I have tested this from two different locations. The first is in tools/testing/selftests/drivers/net/hw/ using python3 tso.py, which utilizes the specs located in Documentation/netlink/specs/. The second follows the testing methodology described in the README.rst of tools/testing/selftests/drivers/net/, which uses the specs in net/lib/specs/. Based on this, I include that different processes utilize different spec locations.
I also referred to the implementation in net/lib/py/ynl.py, which employs a similar handling logic. Both using the source code repository and installing the package can meet the requirements for remote testing.

Signature · 3 lines
Regards,

Xu
maintainer
#7 lore raw

Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO

Quoted 29 lines · On Thu, 9 Apr 2026 15:35:21 +0800 Xu Du wrote:
Quoted 20 lines
Quoted 5 lines

I want to test the gro-hint parameter functionality of the GENEVE tunnel, so I intend to use YNL for the testing. I am conducting the test between two machines using SSH type. I want to add the gro-hint parameter on both the local and remote nodes; however, I am unable to invoke class RtnlFamily on the remote node via SSH.

Oh. But that's not really what you're doing:

tools/testing/selftests/net/lib/py/utils.py +11 -0
+def ynlcli(family, args, json=None, ns=None, host=None):
+    if (KSFT_DIR / "kselftest-list.txt").exists():
+        cli = KSFT_DIR / "net/lib/ynl/pyynl/cli.py"
+        spec = KSFT_DIR / f"net/lib/specs/{family}.yaml"
+    else:
+        cli = KSRC / "tools/net/ynl/pyynl/cli.py"
+        spec = KSRC / f"Documentation/netlink/specs/{family}.yaml"
+    if not cli.exists():
+        raise FileNotFoundError(f"cli not found at {cli}")
+    args = f"--spec {spec} --no-schema {args}"
+    return tool(cli.as_posix(), args, json=json, ns=ns, host=host, shell=True)
Quoted 8 lines

You're not deploying anything to the remote system. Are you assuming that the remote system magically has the same filesystem layout?

You can use the ynl CLI but it has to be whatever version is on the remote system. Just call ynl --family rt-link, don't dig around for the spec paths etc.

Quoted 10 lines

In fact, I have tested this from two different locations. The first is in tools/testing/selftests/drivers/net/hw/ using python3 tso.py, which utilizes the specs located in Documentation/netlink/specs/. The second follows the testing methodology described in the README.rst of tools/testing/selftests/drivers/net/, which uses the specs in net/lib/specs/. Based on this, I include that different processes utilize different spec locations.
I also referred to the implementation in net/lib/py/ynl.py, which employs a similar handling logic. Both using the source code repository and installing the package can meet the requirements for remote testing.

On the local system you have Python bindings.
On the remote system you can't assume KSFT_DIR or KSRC exist.

You can use ynl CLI on the remote system, like we use ip, tc etc. But then just use --family rt-link, don't try to find the filesystem path to the spec.

Is this clear now? Am I misunderstanding your misunderstanding?

author
#8 lore raw

RFC 3/4 selftests: drv-net: tso: add Geneve double tunneling GSO test

In reply to Xu Du

Add a new test case to verify GSO behavior for double-encapsulated Geneve tunnels. This ensures that hardware drivers and the network stack correctly handle nested tunnel headers during segmentation.

To maintain consistency and avoid complexity in hardware parser logic, the test case ensures that the IP versions for both the inner and outer headers remain the same.

---
 tools/testing/selftests/drivers/net/hw/tso.py | 228 +++++++++++++++---
 1 file changed, 188 insertions(+), 40 deletions(-)
tools/testing/selftests/drivers/net/hw/tso.py +188 -40
index f792115adfb3..6b4639ce6139 100755
@@ -5,6 +5,7 @@
 
 import errno
 import fcntl
+import json
 import socket
 import struct
 import termios
@@ -13,7 +14,29 @@ import time
 from lib.py import ksft_pr, ksft_run, ksft_exit, KsftSkipEx, KsftXfailEx
 from lib.py import ksft_eq, ksft_ge, ksft_lt
 from lib.py import EthtoolFamily, NetdevFamily, NetDrvEpEnv
-from lib.py import bkg, cmd, defer, ethtool, ip, rand_port, wait_port_listen
+from lib.py import bkg, ynlcli, defer, ethtool, ip, rand_port, wait_port_listen
+
+
+def _convert_ip(key):
+    def _conv(val):
+        final_key = f"{key}6" if ":" in str(val) else key
+        return final_key, val
+
+    return _conv
+
+
+YNLLINK_PARAM_MAP = {
+    "geneve": {
+        "id": ("id", int),
+        "dstport": ("port", int),
+        "remote": ("", _convert_ip("remote")),
+        "udpcsum": ("udp-csum",),
+        "udp6zerocsumtx": ("udp-zero-csum6-tx",),
+        "udp6zerocsumrx": ("udp-zero-csum6-rx",),
+        "gro-hint": ("gro-hint",),
+        "innerprotoinherit": ("inner-proto-inherit",),
+    },
+}
 
 
 def sock_wait_drain(sock, max_wait=1000):
@@ -107,29 +130,106 @@ def run_one_stream(cfg, ipver, remote_v4, remote_v6, should_lso):
                         500, comment="Number of LSO wire-packets with LSO disabled")
 
 
-def build_tunnel(cfg, outer_ipver, tun_info):
-    local_v4  = NetDrvEpEnv.nsim_v4_pfx + "1"
-    local_v6  = NetDrvEpEnv.nsim_v6_pfx + "1"
-    remote_v4 = NetDrvEpEnv.nsim_v4_pfx + "2"
-    remote_v6 = NetDrvEpEnv.nsim_v6_pfx + "2"
-
-    local_addr  = cfg.addr_v[outer_ipver]
-    remote_addr = cfg.remote_addr_v[outer_ipver]
+def get_tun_ip_address(encap_tun):
+    pfx_v4 = NetDrvEpEnv.nsim_v4_pfx
+    pfx_v6 = NetDrvEpEnv.nsim_v6_pfx.rsplit("::", 1)[0] + ":"
+    v4_offset = 1 if encap_tun == "outer" else 17
+    v6_offset = "1" if encap_tun == "outer" else "2"
+    local_v4 = f"{pfx_v4}{v4_offset}"
+    remote_v4 = f"{pfx_v4}{v4_offset + 1}"
+    local_v6 = f"{pfx_v6}{v6_offset}::1"
+    remote_v6 = f"{pfx_v6}{v6_offset}::2"
+    return local_v4, remote_v4, 28, local_v6, remote_v6, 80
+
+
+def parse_tun_args(tun_arg, tunnel_arg_dict):
+    data = {}
+    args = tun_arg.split()
+    i = 0
+    while i < len(args):
+        negate = False
+        item = tunnel_arg_dict.get(args[i])
+        if item is None and args[i].startswith("no"):
+            item = tunnel_arg_dict.get(args[i][2:])
+            negate = True
+        if item and len(item) == 2:
+            key, conv = item
+            val = conv(args[i + 1])
+            if isinstance(val, tuple) and len(val) == 2:
+                data[val[0]] = val[1]
+            else:
+                data[key] = val
+            i += 2
+        elif item:
+            data[item[0]] = 0 if negate else 1
+            i += 1
+        else:
+            i += 1
+    return data
+
+
+def create_tunnel(tun_type, tun_name, tun_arg, host=None) -> None:
+    tunnel_arg_dict = YNLLINK_PARAM_MAP.get(tun_type)
+    if tunnel_arg_dict:
+        data = parse_tun_args(tun_arg, tunnel_arg_dict)
+        nl_msg = json.dumps({
+            "ifname": tun_name,
+            "linkinfo": {"kind": tun_type, "data": data},
+        })
+        ynlcli("rt-link",
+               f"--create --excl --do newlink --json '{nl_msg}'",
+               host=host)
+    else:
+        ip(f"link add {tun_name} type {tun_type} {tun_arg}", host=host)
+
+
+def build_tunnel(cfg, encap_ipver, encap_tun, tun_info):
+    if encap_tun == "outer":
+        encap_addr = cfg.addr_v[encap_ipver]
+        decap_addr = cfg.remote_addr_v[encap_ipver]
+    else:
+        local_v4, remote_v4, _, local_v6, remote_v6, _ = get_tun_ip_address(
+            "outer")
+        encap_addr = local_v4 if encap_ipver == "4" else local_v6
+        decap_addr = remote_v4 if encap_ipver == "4" else remote_v6
 
     tun_type = tun_info[0]
-    tun_arg  = tun_info[1]
-    ip(f"link add {tun_type}-ksft type {tun_type} {tun_arg} local {local_addr} remote {remote_addr} dev {cfg.ifname}")
-    defer(ip, f"link del {tun_type}-ksft")
-    ip(f"link set dev {tun_type}-ksft up")
-    ip(f"addr add {local_v4}/24 dev {tun_type}-ksft")
-    ip(f"addr add {local_v6}/64 dev {tun_type}-ksft")
-
-    ip(f"link add {tun_type}-ksft type {tun_type} {tun_arg} local {remote_addr} remote {local_addr} dev {cfg.remote_ifname}",
-        host=cfg.remote)
-    defer(ip, f"link del {tun_type}-ksft", host=cfg.remote)
-    ip(f"link set dev {tun_type}-ksft up", host=cfg.remote)
-    ip(f"addr add {remote_v4}/24 dev {tun_type}-ksft", host=cfg.remote)
-    ip(f"addr add {remote_v6}/64 dev {tun_type}-ksft", host=cfg.remote)
+    tun_name = f"{tun_type}-ksft" if encap_tun == "outer" else f"in-{tun_type}-ksft"
+    local_v4, remote_v4, mask_v4, local_v6, remote_v6, mask_v6 = get_tun_ip_address(
+        encap_tun
+    )
+    tun_features = " ".join(
+        f + " on" for f in tun_info[2].split()) if tun_info[2] else ""
+
+    tun_dev = cfg.ifname if encap_tun == "outer" else f"{tun_type}-ksft"
+    tun_arg = tun_info[1] + \
+        f" local {encap_addr} remote {decap_addr} dev {tun_dev}"
+
+    create_tunnel(tun_type, tun_name, tun_arg)
+    defer(ip, f"link del {tun_name}")
+    ip(f"link set dev {tun_name} up")
+    ip(f"addr add {local_v4}/{mask_v4} dev {tun_name}")
+    ip(f"addr add {local_v6}/{mask_v6} dev {tun_name}")
+    if tun_features:
+        ethtool(f"-K {tun_name} {tun_features}")
+    # pmtu can't be propagated to upper layer devices; need manual adjust
+    if encap_tun == "inner":
+        tun_mtu = 1392 if encap_ipver == "4" else 1352
+        ip(f"link set dev {tun_name} mtu {tun_mtu}")
+
+    tun_dev = cfg.remote_ifname if encap_tun == "outer" else f"{tun_type}-ksft"
+    tun_arg = tun_info[1] + \
+        f" local {decap_addr} remote {encap_addr} dev {tun_dev}"
+
+    create_tunnel(tun_type, tun_name, tun_arg, host=cfg.remote)
+    defer(ip, f"link del {tun_name}", host=cfg.remote)
+    ip(f"link set dev {tun_name} up", host=cfg.remote)
+    ip(f"addr add {remote_v4}/{mask_v4} dev {tun_name}", host=cfg.remote)
+    ip(f"addr add {remote_v6}/{mask_v6} dev {tun_name}", host=cfg.remote)
+    if tun_features:
+        ethtool(f"-K {tun_name} {tun_features}", host=cfg.remote)
+    if encap_tun == "inner":
+        ip(f"link set dev {tun_name} mtu {tun_mtu}", host=cfg.remote)
 
     return remote_v4, remote_v6
 
@@ -145,7 +245,10 @@ def restore_wanted_features(cfg):
         ksft_pr(f"WARNING: failure restoring wanted features: {e}")
 
 
-def test_builder(name, cfg, outer_ipver, feature, tun=None, inner_ipver=None):
+def test_builder(
+    name, cfg, outer_ipver, feature, outer_tun=None,
+    inner_tun=None, inner_ipver=None,
+):
     """Construct specific tests from the common template."""
     def f(cfg):
         cfg.require_ipver(outer_ipver)
@@ -159,8 +262,12 @@ def test_builder(name, cfg, outer_ipver, feature, tun=None, inner_ipver=None):
             raise KsftSkipEx(f"Device does not support {feature}")
 
         ipver = outer_ipver
-        if tun:
-            remote_v4, remote_v6 = build_tunnel(cfg, ipver, tun)
+        if outer_tun and inner_tun:
+            build_tunnel(cfg, ipver, "outer", outer_tun)
+            remote_v4, remote_v6 = build_tunnel(cfg, ipver, "inner", inner_tun)
+            ipver = inner_ipver
+        elif outer_tun:
+            remote_v4, remote_v6 = build_tunnel(cfg, ipver, "outer", outer_tun)
             ipver = inner_ipver
         else:
             remote_v4 = cfg.remote_addr_v["4"]
@@ -182,7 +289,11 @@ def test_builder(name, cfg, outer_ipver, feature, tun=None, inner_ipver=None):
         ethtool(f"-K {cfg.ifname} {feature} on")
         run_one_stream(cfg, ipver, remote_v4, remote_v6, should_lso=True)
 
-    f.__name__ = name + ((outer_ipver + "_") if tun else "") + "ipv" + inner_ipver
+    outer_tun_name = f"{outer_tun[0]}{outer_ipver}_" if outer_tun else ""
+    inner_tun_name = f"{inner_tun[0]}{outer_ipver}_" if inner_tun else ""
+    f.__name__ = (
+        name + "_" if name else ""
+    ) + f"{outer_tun_name}{inner_tun_name}ipv{inner_ipver}"
     return f
 
 
@@ -242,14 +353,43 @@ def main() -> None:
         query_nic_features(cfg)
 
         test_info = (
-            # name,       v4/v6  ethtool_feature               tun:(type, args, inner ip versions)
-            ("",           "4", "tx-tcp-segmentation",         None),
-            ("",           "6", "tx-tcp6-segmentation",        None),
-            ("vxlan",      "4", "tx-udp_tnl-segmentation",     ("vxlan", "id 100 dstport 4789 noudpcsum", ("4", "6"))),
-            ("vxlan",      "6", "tx-udp_tnl-segmentation",     ("vxlan", "id 100 dstport 4789 udp6zerocsumtx udp6zerocsumrx", ("4", "6"))),
-            ("vxlan_csum", "", "tx-udp_tnl-csum-segmentation", ("vxlan", "id 100 dstport 4789 udpcsum", ("4", "6"))),
-            ("gre",        "4", "tx-gre-segmentation",         ("gre",   "", ("4", "6"))),
-            ("gre",        "6", "tx-gre-segmentation",         ("ip6gre","", ("4", "6"))),
+            # name, v4/v6, ethtool_feature,
+            # outer_tun:(type, args, ethtool_feature, inner ip versions),
+            # inner_tun:(type, args, ethtool_feature, inner ip versions)
+            ("", "4", "tx-tcp-segmentation", (), ()),
+            ("", "6", "tx-tcp6-segmentation", (), ()),
+            (
+                "vxlan", "4", "tx-udp_tnl-segmentation",
+                ("vxlan", "id 100 dstport 4789 noudpcsum", "", ("4", "6")), (),
+            ),
+            (
+                "vxlan", "6", "tx-udp_tnl-segmentation",
+                ("vxlan", "id 100 dstport 4789 udp6zerocsumtx udp6zerocsumrx",
+                 "", ("4", "6")),
+                (),
+            ),
+            (
+                "vxlan_csum", "", "tx-udp_tnl-csum-segmentation",
+                ("vxlan", "id 100 dstport 4789 udpcsum", "", ("4", "6")),
+                (),
+            ),
+            (
+                "gre", "4", "tx-gre-segmentation",
+                ("gre", "", "", ("4", "6")),
+                (),
+            ),
+            (
+                "gre", "6", "tx-gre-segmentation",
+                ("ip6gre", "", "", ("4", "6")),
+                (),
+            ),
+            (
+                "geneve2", "4", "tx-udp_tnl-segmentation",
+                ("geneve", "id 100 dstport 6081 noudpcsum udp6zerocsumtx udp6zerocsumrx",
+                 "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),),
+                ("geneve", "id 200 dstport 6082 noudpcsum udp6zerocsumtx udp6zerocsumrx",
+                 "", ("4", "6"),),
+            ),
         )
 
         cases = []
@@ -259,13 +399,21 @@ def main() -> None:
                 if info[1] and outer_ipver != info[1]:
                     continue
 
-                if info[3]:
-                    cases += [
-                        test_builder(info[0], cfg, outer_ipver, info[2], info[3], inner_ipver)
-                        for inner_ipver in info[3][2]
-                    ]
+                outer_tun, inner_tun = info[3], info[4]
+                if outer_tun and inner_tun:
+                    inner_ipvers = inner_tun[3]
+                    cases += [test_builder(info[0], cfg, outer_ipver, info[2],
+                                           outer_tun, inner_tun, inner_ipver)
+                              for inner_ipver in inner_ipvers]
+                elif outer_tun:
+                    inner_ipvers = outer_tun[3]
+                    cases += [test_builder(info[0], cfg, outer_ipver, info[2],
+                                           outer_tun, None, inner_ipver)
+                              for inner_ipver in inner_ipvers]
                 else:
-                    cases.append(test_builder(info[0], cfg, outer_ipver, info[2], None, outer_ipver))
+                    cases.append(
+                        test_builder(info[0], cfg, outer_ipver, info[2],
+                                     None, None, outer_ipver))
 
         ksft_run(cases=cases, args=(cfg, ))
     ksft_exit()
Signature · 1 line
2.53.0
author
#9 lore raw

RFC 4/4 selftests: drv-net: tso: expand double tunnel GSO test coverage

In reply to Xu Du

Add Geneve test variants with GRO hint and TTL inherit options to exercise additional tunnel configuration paths.

Also add double VXLAN tunnel test cases to complement the existing double Geneve coverage.

---
 tools/testing/selftests/drivers/net/hw/tso.py | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)
tools/testing/selftests/drivers/net/hw/tso.py +24 -0
index 6b4639ce6139..6930eb3fde29 100755
@@ -373,6 +373,12 @@ def main() -> None:
                 ("vxlan", "id 100 dstport 4789 udpcsum", "", ("4", "6")),
                 (),
             ),
+            (
+                "vxlan2", "4", "tx-udp_tnl-csum-segmentation",
+                ("vxlan", "id 100 dstport 4789 udpcsum",
+                 "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),),
+                ("vxlan", "id 200 dstport 5789 udpcsum", "", ("4", "6")),
+            ),
             (
                 "gre", "4", "tx-gre-segmentation",
                 ("gre", "", "", ("4", "6")),
@@ -390,6 +396,24 @@ def main() -> None:
                 ("geneve", "id 200 dstport 6082 noudpcsum udp6zerocsumtx udp6zerocsumrx",
                  "", ("4", "6"),),
             ),
+            (
+                "geneve2_csum", "", "tx-udp_tnl-csum-segmentation",
+                ("geneve", "id 100 dstport 6081 udpcsum",
+                 "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),),
+                ("geneve", "id 200 dstport 6082 noudpcsum", "", ("4", "6")),
+            ),
+            (
+                "geneve2_grohint", "", "tx-udp_tnl-csum-segmentation",
+                ("geneve", "id 100 dstport 6081 udpcsum gro-hint",
+                 "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),),
+                ("geneve", "id 200 dstport 6082 udpcsum", "", ("4", "6")),
+            ),
+            (
+                "geneve2_inherit", "4", "tx-udp_tnl-csum-segmentation",
+                ("geneve", "id 100 dstport 6081 udpcsum gro-hint",
+                 "tx-gso-partial tx-udp_tnl-segmentation tx-udp_tnl-csum-segmentation", (),),
+                ("geneve", "id 200 dstport 6082 udpcsum inner-proto-inherit", "", ("4", "6")),
+            ),
         )
 
         cases = []
Signature · 1 line
2.53.0