-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tests: implement dscp -> vrf packet switching demo
Signed-off-by: Volodymyr Huti <[email protected]>
- Loading branch information
Volodymyr Huti
committed
Jan 8, 2024
1 parent
c985170
commit b06c51a
Showing
5 changed files
with
375 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
#!/usr/bin/env python | ||
# | ||
# SPDX-License-Identifier: ISC | ||
# Copyright (c) 2023 VyOS Inc. | ||
# Volodymyr Huti <[email protected]> | ||
# | ||
|
||
|
||
import os | ||
import re | ||
import sys | ||
import time | ||
import functools | ||
import subprocess | ||
|
||
from lib import topotest | ||
from lib.topolog import logger | ||
from lib.topojson import build_config_from_json | ||
from lib.topogen import Topogen, TopoRouter, get_topogen | ||
from lib.bgp import verify_bgp_convergence | ||
from lib.common_config import ( | ||
create_debug_log_config, | ||
apply_raw_config, | ||
start_topology, | ||
TcpDumpHelper, | ||
IPerfHelper, | ||
step, | ||
) | ||
|
||
from lib.topotest import version_cmp, interface_to_ifindex | ||
from bcc import BPF, DEBUG_PREPROCESSOR, DEBUG_SOURCE, DEBUG_BPF, DEBUG_BTF | ||
from ctypes import Structure, c_int, c_uint, c_ubyte, c_uint32 | ||
|
||
import pyroute2 | ||
from pyroute2.netns import pushns, popns | ||
|
||
# Module | ||
# ------------------------------------------------------- | ||
def teardown_module(_mod): | ||
"Teardown the pytest environment" | ||
tgen = get_topogen() | ||
tgen.stop_topology() | ||
|
||
DEV_DEBUG = True | ||
CWD = os.path.dirname(os.path.realpath(__file__)) | ||
|
||
def setup_module(mod): | ||
testsuite_run_time = time.asctime(time.localtime(time.time())) | ||
logger.info("Testsuite start time: {}".format(testsuite_run_time)) | ||
logger.info("=" * 40) | ||
|
||
logger.info("Running setup_module to create topology") | ||
|
||
json_file = f"{CWD}/topo_vrf.json" | ||
tgen = Topogen(json_file, mod.__name__) | ||
global topo | ||
topo = tgen.json_topo | ||
|
||
start_topology(tgen) | ||
build_config_from_json(tgen, topo) | ||
if tgen.routers_have_failure(): | ||
pytest.skip(tgen.errors) | ||
|
||
# ----------------------------------------------------------------------- | ||
r1 = tgen.gears["r1"] | ||
debug_rmap_dict = {"r1": {"raw_config": ["end", "debug route-map"]}} | ||
debug_config_dict = { | ||
"r1": {"debug": {"log_file": "debug.log", "enable": ["bgpd", "zebra"]}} | ||
} | ||
if DEV_DEBUG: | ||
create_debug_log_config(tgen, debug_config_dict) | ||
apply_raw_config(tgen, debug_rmap_dict) | ||
|
||
|
||
# Test Cases | ||
# ------------------------------------------------------- | ||
xdp_ifindex = lambda host, iface: c_uint(interface_to_ifindex(host, iface)) | ||
xdp_dscp = lambda x: c_ubyte(dscp_tos(x)) | ||
dscp_tos = lambda x: x << 2 | ||
tag10 = c_uint32(10) | ||
tag20 = c_uint32(20) | ||
|
||
def tc_bpf_filter(rnode, ifid): | ||
"Attach tc bpf filter, depends on pyroute2 package" | ||
|
||
tc_fn = rnode.bpf.funcs[b"xdp_vrf"] | ||
rnode_ns = "/proc/{}/ns/net".format(rnode.net.pid) | ||
|
||
logger.debug("Attach TC-BPF handler '{}'\nNetNS --> {})".format(ifid, rnode_ns)) | ||
# ip.tc("add", "clsact", ifid, "1:") | ||
pushns(rnode_ns) | ||
ip = pyroute2.IPRoute() | ||
ip.tc("add", "ingress", ifid, "ffff:") | ||
ip.tc("add-filter", "bpf", ifid, 20, fd=tc_fn.fd, name=tc_fn.name, | ||
parent="ffff:", classid=1, action="drop", direct_action=True) | ||
popns() | ||
|
||
|
||
def test_dscp_vrf(tgen): | ||
r1 = tgen.gears['r1'] | ||
r2 = tgen.gears['r2'] | ||
r3 = tgen.gears['r3'] | ||
r4 = tgen.gears['r4'] | ||
|
||
red = xdp_ifindex(r1, "RED") | ||
blue = xdp_ifindex(r1, "BLUE") | ||
# red = xdp_ifindex(r1, "r1-r2-eth0") | ||
# blue = xdp_ifindex(r1, "r1-r3-eth1") | ||
|
||
r1.cmd_raises("sysctl -w net.ipv4.conf.all.proxy_arp=1") | ||
r1.cmd_raises(""" | ||
ip rule add table 1 | ||
ip rule add pref 32765 table local | ||
ip rule del pref 0 | ||
ip rule show | ||
""") | ||
|
||
for r in tgen.gears.values(): | ||
load_vrf_plugin(tgen, r) | ||
|
||
for r in [r2, r3, r4]: | ||
router_attach_xdp(r, "%s-r1-eth0" % r.name, b"xdp_dummy") | ||
r.cmd_raises("ip route add default dev %s-r1-eth0" % r.name) | ||
|
||
for iface in ["r1-r2-eth0", "r1-r3-eth1"]: | ||
router_attach_xdp(r1, iface, b"xdp_dummy"); | ||
|
||
ingress_if = interface_to_ifindex(r1, "r1-r4-eth2") | ||
tc_bpf_filter(r1, ingress_if) | ||
# router_attach_xdp(r1, "r1-r4-eth2") | ||
# r1.cmd_raises("ip l set r1-r4-eth2 master RED") | ||
|
||
dscp_iface_map = r1.bpf['dscp_iface_map'] | ||
dscp_iface_map[tag10] = red | ||
dscp_iface_map[tag20] = blue | ||
|
||
# let kernel initialize caches & remove rule | ||
r4.cmd("ping 192.168.1.1 -c 5") | ||
r1.cmd("ip rule del lookup 1") | ||
# breakpoint() | ||
|
||
from . import _check | ||
lo_ip = "192.168.1.1" | ||
tcpdump = TcpDumpHelper(tgen, "icmp[0] == 8") # ICMP Echo requst | ||
r2_check_conn = functools.partial(_check, tcpdump, r4, r2, lo_ip, "r2-r1-eth0") | ||
r3_check_conn = functools.partial(_check, tcpdump, r4, r3, lo_ip, "r3-r1-eth0") | ||
|
||
found, matches = r2_check_conn(tag10, ping_tos=10) | ||
logger.info("{} {}".format(found, matches)) | ||
|
||
found, matches = r3_check_conn(tag20, ping_tos=20) | ||
logger.info("{} {}".format(found, matches)) | ||
|
||
breakpoint() | ||
|
||
|
||
def router_attach_xdp(rnode, iface, fn=b"xdp_vrf"): | ||
""" | ||
- swap netns to rnode, | ||
- attach `xdp_qppb` to `iface` | ||
- switch back to root ns | ||
""" | ||
ns = "/proc/%d/ns/net" % rnode.net.pid | ||
vrf_fn = rnode.bpf.funcs[fn] | ||
|
||
pushns(ns) | ||
logger.debug("Attach XDP handler '{}|{}'\nNetNS --> {})".format(iface, fn, ns)) | ||
rnode.bpf.attach_xdp(iface, vrf_fn, 0) | ||
popns() | ||
|
||
|
||
def load_vrf_plugin(tgen, rnode, debug_on=True): | ||
""" | ||
Initialize rnode XDP hooks and BPF mapping handlers | ||
- compile xdp handlers from `xdp_qppb.c` in specified `mode` | ||
- load `xdp_qppb` and `xdp_tc_mark` hooks | ||
- restart router with QPPB plugin | ||
Parameters | ||
---------- | ||
* `tgen`: topogen object | ||
* `rnode`: router object | ||
* `mode`: xdp processing mode required | ||
* `debug_on`: enable debug logs for bpf compilation / xdp handlers | ||
Usage | ||
--------- | ||
load_qppb_plugin(tgen, r1, mode=XdpMode.META) | ||
Returns -> None (XXX) | ||
""" | ||
debug_flags = DEBUG_BPF | DEBUG_PREPROCESSOR | DEBUG_SOURCE | DEBUG_BTF | ||
debug = debug_flags if debug_on else 0 | ||
src_file = CWD + "/xdp_vrf.c" | ||
bpf_flags = [ "-w" ] | ||
|
||
ns = "/proc/%d/ns/mnt" % rnode.net.pid | ||
nsfd = os.open(ns, os.O_RDONLY) | ||
|
||
import ctypes | ||
libc = ctypes.CDLL("libc.so.6", use_errno=True) | ||
libc.setns(nsfd, 0) | ||
|
||
tgen.qppb_nodes.append(rnode) | ||
rnode.cmd_raises( | ||
""" | ||
mkdir -p /sys/fs/bpf | ||
mount -t bpf bpf /sys/fs/bpf | ||
""" | ||
) | ||
|
||
try: | ||
logger.info("Preparing the XDP src: " + src_file) | ||
b = BPF(src_file=src_file.encode(), cflags=bpf_flags, debug=debug) | ||
|
||
logger.info("Loading XDP hooks -- xdp_vrf") | ||
b.load_func(b"xdp_vrf", BPF.SCHED_CLS) | ||
b.load_func(b"xdp_dummy", BPF.XDP) | ||
rnode.bpf = b | ||
except Exception as e: | ||
breakpoint() | ||
pytest.skip("Failed to configure XDP environment -- \n" + str(e)) | ||
|
||
|
||
if __name__ == "__main__": | ||
args = ["-s"] + sys.argv[1:] | ||
sys.exit(pytest.main(args)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
{ | ||
"address_types": ["ipv4"], | ||
"ipv4base": "10.0.0.0", | ||
"ipv4mask": 30, | ||
"ipv6base": "fd00::", | ||
"ipv6mask": 64, | ||
"link_ip_start": { | ||
"ipv4": "10.0.0.0", | ||
"v4mask": 30, | ||
"ipv6": "fd00::", | ||
"v6mask": 64 | ||
}, | ||
"lo_prefix": { | ||
"ipv4": "1.0.", | ||
"v4mask": 32, | ||
"ipv6": "2001:DB8:F::", | ||
"v6mask": 128 | ||
}, | ||
"routers": { | ||
"r1": { | ||
"links": { | ||
"r2": { | ||
"ipv4": "192.168.1.253/24", "vrf": "RED", | ||
"ipv6": "auto" | ||
}, | ||
"r3": { | ||
"ipv4": "192.168.1.253/24", "vrf": "BLUE", | ||
"ipv6": "auto" | ||
}, | ||
"r4": { | ||
"ipv4": "20.0.0.2/24", | ||
"ipv6": "auto" | ||
} | ||
}, | ||
"vrfs":[ | ||
{"name": "RED", "id": "1"}, | ||
{"name": "BLUE", "id": "2"} | ||
] | ||
}, | ||
"r2": { | ||
"links": { | ||
"r1": { | ||
"ipv4": "192.168.1.1/24", | ||
"ipv6": "auto" | ||
} | ||
} | ||
}, | ||
"r3": { | ||
"links": { | ||
"r1": { | ||
"ipv4": "192.168.1.1/24", | ||
"ipv6": "auto" | ||
} | ||
} | ||
}, | ||
"r4": { | ||
"links": { | ||
"r1": { | ||
"ipv4": "20.0.0.1/24", | ||
"ipv6": "auto" | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.