205 lines
7.2 KiB
Python
Executable File
205 lines
7.2 KiB
Python
Executable File
#!/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()
|