Skip to content

Commit

Permalink
[QPPB] Implement test suit with common setups, based on BCC
Browse files Browse the repository at this point in the history
  • Loading branch information
1337kerberos committed Feb 9, 2023
1 parent db4ad91 commit 135da45
Show file tree
Hide file tree
Showing 10 changed files with 1,607 additions and 15 deletions.
132 changes: 132 additions & 0 deletions tests/topotests/bgp_qppb_vyos_flow/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import os
import sys
import json

from bcc import BPF, DEBUG_PREPROCESSOR, DEBUG_SOURCE, DEBUG_BPF, DEBUG_BTF
from .ns import pushns, popns
from lib.topolog import logger
from lib.common_config import (
start_router_daemons,
kill_router_daemons,
)

CWD = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(CWD, "../"))
sys.path.append(os.path.join(CWD, "../lib/"))

# from pyroute2.netns import pushns, popns
def router_attach_xdp(rnode, iface):
"""
- swap netns to rnode,
- attach `xdp_qppb` to `iface`
- switch back to root ns
"""
ns = "/proc/%d/ns/net" % rnode.net.pid
qppb_fn = rnode.bpf.funcs[b"xdp_qppb"]

pushns(ns)
logger.debug("Attach XDP handler '{}'\nNetNS --> {})".format(iface, ns))
rnode.bpf.attach_xdp(iface, qppb_fn, BPF.XDP_FLAGS_DRV_MODE)
popns()


def router_remove_xdp(rnode, iface):
" XXX: remove xdp hook ... "
pushns("/proc/%d/ns/net" % rnode.net.pid)
logger.debug("Removing XDP handler for {}:{}".format(rnode.name, iface))
rnode.bpf.remove_xdp(iface)
popns()


def load_qppb_plugin(tgen, rnode, vtysh_cmd=None, cflags=None, debug_on=True):
"""
Initialize rnode XDP hooks and BPF mapping handlers
- compile xdp handlers from `xdp_qppb.c`
- load `xdp_qppb` and `xdp_tc_mark` hooks
- restart router with QPPB plugin
- execute optional vtysh configuration
Parameters
----------
* `tgen`: topogen object
* `vtysh_cmd`: any extra configuration, typically not present in json
* `cflags`: processing mode required, any other optional
* `debug_on`: enable all BPF debug logs
Usage
---------
load_qppb_plugin(tgen, r1, SET_QPPB_TABLE_MAP, cflags=["-DMARK_SKB"])
Returns -> None (XXX)
"""
debug_flags = DEBUG_BPF | DEBUG_PREPROCESSOR | DEBUG_SOURCE | DEBUG_BTF
debug = debug_flags if debug_on else 0
bpf_flags = [
'-DBPF_PIN_DIR="{}"'.format(rnode.bpfdir),
"-DRESPECT_TOS",
"-DLOG_QPPB",
"-DLOG_TC",
"-w",
]

bpf_flags.extend(cflags)
for mode in ["MARK_SKB", "MARK_META"]:
if ("-D%s" % mode) in bpf_flags:
bpf_flags.append('-DMODE_STR="%s"' % mode)

try:
src_file = "%s/xdp_qppb.c" % CWD
logger.info("Preparing the XDP src: " + src_file)
b = BPF(src_file=src_file, cflags=bpf_flags, debug=debug)

logger.info("Loading XDP hooks -- xdp_qppb, xdp_tc_mark")
b.load_func(b"xdp_qppb", BPF.XDP)
b.load_func(b"xdp_tc_mark", BPF.SCHED_CLS)
rnode.bpf = b
except Exception as e:
import pytest # XXX: proper error handling
pytest.skip("Failed to configure XDP environment -- \n%s", str(e))

qppb_module = "-M vyos_qppb:" + rnode.bpfdir
logger.info(
"Restart {}, XDP hooks loading...\nPlugin args:: {}".format(
rnode.name, qppb_module
)
)
kill_router_daemons(tgen, rnode.name, ["bgpd"])
start_router_daemons(tgen, rnode.name, ["bgpd"], {"bgpd": qppb_module})
if vtysh_cmd:
rnode.vtysh_cmd(vtysh_cmd)


def assert_bw(out, bw_target, tolerance, time=10):
" Assert that connection matches BW in Mbits +- %tolerance"
_min = bw_target * (1 - tolerance)
_max = bw_target * (1 + tolerance)
half_samples = time / 2
data = json.loads(out)
bws = []

for sample in data["intervals"]:
bits = int(sample["sum"]["bits_per_second"])
mbits = bits / 1024 / 1024
bw = mbits / 8
logger.debug("BW sample [{} <= {} <= {}]".format(_min, bw, _max))
if _min <= bw <= _max:
bws.append(bw)

_len = len(bws)
assert (
_len >= half_samples
), "Only {} samples are within targeted BW [{}:{}%]".format(
_len, bw_target, tolerance * 100
)


def bpf_print_trace(b):
" XXX: Call this from debugger, to avoid blocking / IO collissions "
logger.debug("===================\nDump bpf log buffer:\n")
line = b.trace_readline(nonblocking=True)
while line:
logger.debug(line)
line = b.trace_readline(nonblocking=True)
10 changes: 10 additions & 0 deletions tests/topotests/bgp_qppb_vyos_flow/bgp_ipv4_nh.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
BGP routing table entry for 10.61.0.1/32, version XX
Paths: (1 available, best #1, table default)
Advertised to non peer-group peers:
10.0.0.2
10 60
10.0.0.2 from 10.0.0.2 (1.0.2.17)
Origin incomplete, valid, external, best (First path received)
Community: 60:1
QOS: Precedence af11 (10)
Last update: XXXX
142 changes: 142 additions & 0 deletions tests/topotests/bgp_qppb_vyos_flow/ns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import platform
import ctypes
import os
import io

"""
XXX:
The `xdp_attach` method expects target dev to be visible (to be in local netns).
It is possible to run some helper via `rnode.popen("bcc_script.py", ...)`,
but we would still need to initialized a separate BPF handler in the root ns.
The functionality is provided by `pyroute2` package - `setns/pushns/popns`
Either we can specify pyroute2 as an required dependencies, or just
adopt the relevant methods, avoiding any extra packages.
/usr/lib/python3/dist-packages/pyroute2/netns/__init__.py
/usr/lib/python3/dist-packages/pyroute2/netns/nslink.py
from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
"""

file = io.IOBase
CLONE_NEWNET = 0x40000000
NETNS_RUN_DIR = '/var/run/netns'
__saved_ns = []

machine = platform.machine()
arch = platform.architecture()[0]
__NR = {'x86_': {'64bit': 308},
'i386': {'32bit': 346},
'i686': {'32bit': 346},
'mips': {'32bit': 4344,
'64bit': 5303}, # FIXME: NABI32?
'armv': {'32bit': 375},
'aarc': {'32bit': 375,
'64bit': 268}, # FIXME: EABI vs. OABI?
'ppc6': {'64bit': 350},
's390': {'64bit': 339}}
__NR_setns = __NR.get(machine[:4], {}).get(arch, 308)


def setns(netns, flags=os.O_CREAT, libc=None):
'''
Set netns for the current process.
The flags semantics is the same as for the `open(2)`
call:
- O_CREAT -- create netns, if doesn't exist
- O_CREAT | O_EXCL -- create only if doesn't exist
Note that "main" netns has no name. But you can access it with::
setns('foo') # move to netns foo
setns('/proc/1/ns/net') # go back to default netns
See also `pushns()`/`popns()`/`dropns()`
Changed in 0.5.1: the routine closes the ns fd if it's
not provided via arguments.
'''
newfd = False
basestring = (str, bytes)
libc = libc or ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
if isinstance(netns, basestring):
netnspath = _get_netnspath(netns)
if os.path.basename(netns) in listnetns(os.path.dirname(netns)):
if flags & (os.O_CREAT | os.O_EXCL) == (os.O_CREAT | os.O_EXCL):
raise OSError(errno.EEXIST, 'netns exists', netns)
else:
if flags & os.O_CREAT:
create(netns, libc=libc)
nsfd = os.open(netnspath, os.O_RDONLY)
newfd = True
elif isinstance(netns, file):
nsfd = netns.fileno()
elif isinstance(netns, int):
nsfd = netns
else:
raise RuntimeError('netns should be a string or an open fd')
error = libc.syscall(__NR_setns, nsfd, CLONE_NEWNET)
if newfd:
os.close(nsfd)
if error != 0:
raise OSError(ctypes.get_errno(), 'failed to open netns', netns)

def _get_netnspath(name):
netnspath = name
dirname = os.path.dirname(name)
if not dirname:
netnspath = '%s/%s' % (NETNS_RUN_DIR, name)
if hasattr(netnspath, 'encode'):
netnspath = netnspath.encode('ascii')
return netnspath

def listnetns(nspath=None):
'''
List available network namespaces.
'''
if nspath:
nsdir = nspath
else:
nsdir = NETNS_RUN_DIR
try:
return os.listdir(nsdir)
except OSError as e:
if e.errno == errno.ENOENT:
return []
else:
raise

def pushns(newns=None, libc=None):
'''
Save the current netns in order to return to it later. If newns is
specified, change to it::
# --> the script in the "main" netns
netns.pushns("test")
# --> changed to "test", the "main" is saved
netns.popns()
# --> "test" is dropped, back to the "main"
'''
global __saved_ns
__saved_ns.append(os.open('/proc/self/ns/net', os.O_RDONLY))
if newns is not None:
setns(newns, libc=libc)

def popns(libc=None):
'''
Restore the previously saved netns.
'''
global __saved_ns
fd = __saved_ns.pop()
try:
setns(fd, libc=libc)
except Exception:
__saved_ns.append(fd)
raise
os.close(fd)

Loading

0 comments on commit 135da45

Please sign in to comment.