| #!/usr/bin/env python3 |
| # SPDX-License-Identifier: GPL-2.0 |
| """Traffic test for VXLAN + IPsec crypto-offload.""" |
| |
| import os |
| |
| from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge |
| from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx |
| from lib.py import CmdExitFailure, NetDrvEpEnv, cmd, defer, ethtool, ip |
| from lib.py import Iperf3Runner |
| |
| # Inner tunnel addresses - TEST-NET-2 (RFC 5737) / doc prefix (RFC 3849) |
| INNER_V4_LOCAL = "198.51.100.1" |
| INNER_V4_REMOTE = "198.51.100.2" |
| INNER_V6_LOCAL = "2001:db8:100::1" |
| INNER_V6_REMOTE = "2001:db8:100::2" |
| |
| # ESP parameters |
| SPI_OUT = "0x1000" |
| SPI_IN = "0x1001" |
| # 128-bit key + 32-bit salt = 20 bytes hex, 128-bit ICV |
| ESP_AEAD = "aead 'rfc4106(gcm(aes))' 0x" + "01" * 20 + " 128" |
| |
| |
| def xfrm(args, host=None): |
| """Runs 'ip xfrm' via shell to preserve parentheses in algo names.""" |
| cmd(f"ip xfrm {args}", shell=True, host=host) |
| |
| |
| def check_xfrm_offload_support(): |
| """Skips if iproute2 lacks xfrm offload support.""" |
| out = cmd("ip xfrm state help", fail=False) |
| if "offload" not in out.stdout + out.stderr: |
| raise KsftSkipEx("iproute2 too old, missing xfrm offload") |
| |
| |
| def check_esp_hw_offload(cfg): |
| """Skips if device lacks esp-hw-offload support.""" |
| check_xfrm_offload_support() |
| try: |
| feat = ethtool(f"-k {cfg.ifname}", json=True)[0] |
| except (CmdExitFailure, IndexError) as e: |
| raise KsftSkipEx(f"can't query features: {e}") from e |
| if not feat.get("esp-hw-offload", {}).get("active"): |
| raise KsftSkipEx("Device does not support esp-hw-offload") |
| |
| |
| def get_tx_drops(cfg): |
| """Returns TX dropped counter from the physical device.""" |
| stats = ip("-s -s link show dev " + cfg.ifname, json=True)[0] |
| return stats["stats64"]["tx"]["dropped"] |
| |
| |
| def setup_vxlan_ipsec(cfg, outer_ipver, inner_ipver): |
| """Sets up VXLAN tunnel with IPsec transport-mode crypto-offload.""" |
| vxlan_name = f"vx{os.getpid()}" |
| local_addr = cfg.addr_v[outer_ipver] |
| remote_addr = cfg.remote_addr_v[outer_ipver] |
| |
| if inner_ipver == "4": |
| inner_local = f"{INNER_V4_LOCAL}/24" |
| inner_remote = f"{INNER_V4_REMOTE}/24" |
| addr_extra = "" |
| else: |
| inner_local = f"{INNER_V6_LOCAL}/64" |
| inner_remote = f"{INNER_V6_REMOTE}/64" |
| addr_extra = " nodad" |
| |
| if outer_ipver == "6": |
| vxlan_opts = "udp6zerocsumtx udp6zerocsumrx" |
| else: |
| vxlan_opts = "noudpcsum" |
| |
| # VXLAN tunnel - local side |
| ip(f"link add {vxlan_name} type vxlan id 100 dstport 4789 {vxlan_opts} " |
| f"local {local_addr} remote {remote_addr} dev {cfg.ifname}") |
| defer(ip, f"link del {vxlan_name}") |
| ip(f"addr add {inner_local} dev {vxlan_name}{addr_extra}") |
| ip(f"link set {vxlan_name} up") |
| |
| # VXLAN tunnel - remote side |
| ip(f"link add {vxlan_name} type vxlan id 100 dstport 4789 {vxlan_opts} " |
| f"local {remote_addr} remote {local_addr} dev {cfg.remote_ifname}", |
| host=cfg.remote) |
| defer(ip, f"link del {vxlan_name}", host=cfg.remote) |
| ip(f"addr add {inner_remote} dev {vxlan_name}{addr_extra}", |
| host=cfg.remote) |
| ip(f"link set {vxlan_name} up", host=cfg.remote) |
| |
| # xfrm state - local outbound SA |
| xfrm(f"state add src {local_addr} dst {remote_addr} " |
| f"proto esp spi {SPI_OUT} " |
| f"{ESP_AEAD} " |
| f"mode transport offload crypto dev {cfg.ifname} dir out") |
| defer(xfrm, f"state del src {local_addr} dst {remote_addr} " |
| f"proto esp spi {SPI_OUT}") |
| |
| # xfrm state - local inbound SA |
| xfrm(f"state add src {remote_addr} dst {local_addr} " |
| f"proto esp spi {SPI_IN} " |
| f"{ESP_AEAD} " |
| f"mode transport offload crypto dev {cfg.ifname} dir in") |
| defer(xfrm, f"state del src {remote_addr} dst {local_addr} " |
| f"proto esp spi {SPI_IN}") |
| |
| # xfrm state - remote outbound SA (mirror, software crypto) |
| xfrm(f"state add src {remote_addr} dst {local_addr} " |
| f"proto esp spi {SPI_IN} " |
| f"{ESP_AEAD} " |
| f"mode transport", |
| host=cfg.remote) |
| defer(xfrm, f"state del src {remote_addr} dst {local_addr} " |
| f"proto esp spi {SPI_IN}", host=cfg.remote) |
| |
| # xfrm state - remote inbound SA (mirror, software crypto) |
| xfrm(f"state add src {local_addr} dst {remote_addr} " |
| f"proto esp spi {SPI_OUT} " |
| f"{ESP_AEAD} " |
| f"mode transport", |
| host=cfg.remote) |
| defer(xfrm, f"state del src {local_addr} dst {remote_addr} " |
| f"proto esp spi {SPI_OUT}", host=cfg.remote) |
| |
| # xfrm policy - local out |
| xfrm(f"policy add src {local_addr} dst {remote_addr} " |
| f"proto udp dport 4789 dir out " |
| f"tmpl src {local_addr} dst {remote_addr} proto esp mode transport") |
| defer(xfrm, f"policy del src {local_addr} dst {remote_addr} " |
| f"proto udp dport 4789 dir out") |
| |
| # xfrm policy - local in |
| xfrm(f"policy add src {remote_addr} dst {local_addr} " |
| f"proto udp dport 4789 dir in " |
| f"tmpl src {remote_addr} dst {local_addr} proto esp mode transport") |
| defer(xfrm, f"policy del src {remote_addr} dst {local_addr} " |
| f"proto udp dport 4789 dir in") |
| |
| # xfrm policy - remote out |
| xfrm(f"policy add src {remote_addr} dst {local_addr} " |
| f"proto udp dport 4789 dir out " |
| f"tmpl src {remote_addr} dst {local_addr} proto esp mode transport", |
| host=cfg.remote) |
| defer(xfrm, f"policy del src {remote_addr} dst {local_addr} " |
| f"proto udp dport 4789 dir out", host=cfg.remote) |
| |
| # xfrm policy - remote in |
| xfrm(f"policy add src {local_addr} dst {remote_addr} " |
| f"proto udp dport 4789 dir in " |
| f"tmpl src {local_addr} dst {remote_addr} proto esp mode transport", |
| host=cfg.remote) |
| defer(xfrm, f"policy del src {local_addr} dst {remote_addr} " |
| f"proto udp dport 4789 dir in", host=cfg.remote) |
| |
| |
| def _vxlan_ipsec_variants(): |
| """Generates outer/inner IP version variants.""" |
| for outer in ["4", "6"]: |
| for inner in ["4", "6"]: |
| yield KsftNamedVariant(f"outer_v{outer}_inner_v{inner}", outer, inner) |
| |
| |
| @ksft_variants(_vxlan_ipsec_variants()) |
| def test_vxlan_ipsec_crypto_offload(cfg, outer_ipver, inner_ipver): |
| """Tests VXLAN+IPsec crypto-offload has no TX drops.""" |
| cfg.require_ipver(outer_ipver) |
| check_esp_hw_offload(cfg) |
| |
| setup_vxlan_ipsec(cfg, outer_ipver, inner_ipver) |
| |
| if inner_ipver == "4": |
| inner_local = INNER_V4_LOCAL |
| inner_remote = INNER_V4_REMOTE |
| ping = "ping" |
| else: |
| inner_local = INNER_V6_LOCAL |
| inner_remote = INNER_V6_REMOTE |
| ping = "ping -6" |
| |
| cmd(f"{ping} -c 1 -W 2 {inner_remote}") |
| |
| drops_before = get_tx_drops(cfg) |
| |
| runner = Iperf3Runner(cfg, server_ip=inner_local, |
| client_ip=inner_remote) |
| bw_gbps = runner.measure_bandwidth(reverse=True) |
| |
| cfg.wait_hw_stats_settle() |
| drops_after = get_tx_drops(cfg) |
| |
| ksft_eq(drops_after - drops_before, 0, |
| comment="TX drops during VXLAN+IPsec") |
| ksft_ge(bw_gbps, 0.1, |
| comment="Minimum 100Mbps over VXLAN+IPsec") |
| |
| |
| def main(): |
| """Runs VXLAN+IPsec crypto-offload GSO selftest.""" |
| with NetDrvEpEnv(__file__, nsim_test=False) as cfg: |
| ksft_run([test_vxlan_ipsec_crypto_offload], args=(cfg,)) |
| ksft_exit() |
| |
| |
| if __name__ == "__main__": |
| main() |