Skip to content

Commit c301638

Browse files
authored
Merge pull request #1157 from Bastian-Krause/bst/network-test
Add RawNetworkInterfaceDriver, labgrid-raw-interface helper and example
2 parents 3ab7ab6 + e3ee7f6 commit c301638

7 files changed

Lines changed: 396 additions & 0 deletions

File tree

debian/labgrid.install

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ debian/labgrid-exporter /usr/bin
55
debian/labgrid-pytest /usr/bin
66
debian/labgrid-suggest /usr/bin
77
helpers/labgrid-bound-connect /usr/sbin
8+
helpers/labgrid-raw-interface /usr/sbin
89
contrib/completion/labgrid-client.bash => /usr/share/bash-completion/completions/labgrid-client

doc/configuration.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,48 @@ It supports:
29602960
- connection sharing (DHCP server with NAT)
29612961
- listing DHCP leases (if the client has sufficient permissions)
29622962

2963+
Binds to:
2964+
iface:
2965+
- `NetworkInterface`_
2966+
- `USBNetworkInterface`_
2967+
- `RemoteNetworkInterface`_
2968+
2969+
Implements:
2970+
- None yet
2971+
2972+
Arguments:
2973+
- None
2974+
2975+
RawNetworkInterfaceDriver
2976+
~~~~~~~~~~~~~~~~~~~~~~~~~
2977+
This driver allows "raw" control of a network interface (such as Ethernet or
2978+
WiFi).
2979+
2980+
The labgrid-raw-interface helper (``helpers/labgrid-raw-interface``) needs to
2981+
be installed in the PATH and usable via sudo without password.
2982+
A configuration file ``/etc/labgrid/helpers.yaml`` must be installed on hosts
2983+
exporting network interfaces for the RawNetworkInterfaceDriver, e.g.:
2984+
2985+
.. code-block:: yaml
2986+
2987+
raw-interface:
2988+
denied-interfaces:
2989+
- eth1
2990+
2991+
It supports:
2992+
- recording traffic
2993+
- replaying traffic
2994+
- basic statistic collection
2995+
2996+
For now, the RawNetworkInterfaceDriver leaves pre-configuration of the exported
2997+
network interface to the user, including:
2998+
- disabling DHCP
2999+
- disabling IPv6 Duplicate Address Detection (DAD) by SLAAC (Stateless
3000+
Address Autoconfiguration) and Neighbor Discovery
3001+
- disabling Generic Receive Offload (GRO)
3002+
3003+
This might change in the future.
3004+
29633005
Binds to:
29643006
iface:
29653007
- `NetworkInterface`_

examples/network-test/env.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
targets:
2+
main:
3+
resources:
4+
NetworkService:
5+
address: 192.168.1.5
6+
username: root
7+
NetworkInterface:
8+
ifname: enp2s0f3
9+
drivers:
10+
SSHDriver: {}
11+
RawNetworkInterfaceDriver: {}
12+
options:
13+
local_iface_to_dut_iface:
14+
enp2s0f3: uplink
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python3
2+
# Generates an Ethernet frame via scapy using pcap, copies pcap to DUT, replays pcap on interface,
3+
# records frame locally (or on exporter, adjust env.yaml accordingly), and compares both.
4+
5+
import logging
6+
import os
7+
from tempfile import NamedTemporaryFile, TemporaryDirectory
8+
9+
from scapy.all import Ether, Raw, rdpcap, wrpcap, conf
10+
11+
from labgrid import Environment
12+
from labgrid.logging import basicConfig, StepLogger
13+
14+
def generate_frame():
15+
frame = [Ether(dst="11:22:33:44:55:66", src="66:55:44:33:22:11", type=0x9000)]
16+
padding = "\x00" * (conf.min_pkt_size - len(frame))
17+
frame = frame[0] / Raw(load=padding)
18+
return frame
19+
20+
21+
basicConfig(level=logging.INFO)
22+
StepLogger.start()
23+
env = Environment("env.yaml")
24+
target = env.get_target()
25+
26+
netdrv = target.get_driver("RawNetworkInterfaceDriver")
27+
ssh = target.get_driver("SSHDriver")
28+
29+
# get DUT interface
30+
exporter_iface = netdrv.iface.ifname
31+
dut_iface = env.config.get_target_option(target.name, "local_iface_to_dut_iface")[exporter_iface]
32+
33+
# generate test frame
34+
generated_frame = generate_frame()
35+
36+
# write pcap, copy to DUT
37+
remote_pcap = "/tmp/pcap"
38+
with NamedTemporaryFile() as pcap:
39+
wrpcap(pcap.name, generated_frame)
40+
ssh.put(pcap.name, remote_pcap)
41+
42+
# copy recorded pcap from DUT, compare with generated frame
43+
with TemporaryDirectory() as tempdir:
44+
# start record on exporter
45+
tempf = os.path.join(tempdir, "record.pcap")
46+
with netdrv.record(tempf, count=1) as record:
47+
# replay pcap on DUT
48+
ssh.run_check(f"ip link set {dut_iface} up")
49+
ssh.run_check(f"tcpreplay -i {dut_iface} {remote_pcap}")
50+
51+
remote_frame = rdpcap(tempf)
52+
assert remote_frame[0] == generated_frame[0]
53+
54+
print("statistics", netdrv.get_statistics())
55+
print("address", netdrv.get_address())

helpers/labgrid-raw-interface

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Wrapper script to be deployed on machines whose network interfaces should be
4+
# controllable via the RawNetworkInterfaceDriver. A /etc/labgrid/helpers.yaml
5+
# can deny access to network interfaces. See below.
6+
#
7+
# This is intended to be used via sudo. For example, add via visudo:
8+
# %developers ALL = NOPASSWD: /usr/sbin/labgrid-raw-interface
9+
10+
import argparse
11+
import os
12+
import sys
13+
14+
import yaml
15+
16+
17+
def get_denylist():
18+
denylist_file = "/etc/labgrid/helpers.yaml"
19+
try:
20+
with open(denylist_file) as stream:
21+
data = yaml.load(stream, Loader=yaml.SafeLoader)
22+
except (PermissionError, FileNotFoundError, AttributeError) as e:
23+
raise Exception(f"No configuration file ({denylist_file}), inaccessable or invalid yaml") from e
24+
25+
denylist = data.get("raw-interface", {}).get("denied-interfaces", [])
26+
27+
if not isinstance(denylist, list):
28+
raise Exception("No explicit denied-interfaces or not a list, please check your configuration")
29+
30+
denylist.append("lo")
31+
32+
return denylist
33+
34+
35+
def main(program, ifname, count):
36+
if not ifname:
37+
raise ValueError("Empty interface name.")
38+
if any((c == "/" or c.isspace()) for c in ifname):
39+
raise ValueError(f"Interface name '{ifname}' contains invalid characters.")
40+
if len(ifname) > 16:
41+
raise ValueError(f"Interface name '{ifname}' is too long.")
42+
43+
denylist = get_denylist()
44+
45+
if ifname in denylist:
46+
raise ValueError(f"Interface name '{ifname}' is denied in denylist.")
47+
48+
programs = ["tcpreplay", "tcpdump"]
49+
if program not in programs:
50+
raise ValueError(f"Invalid program {program} called with wrapper, valid programs are: {programs}")
51+
52+
args = [
53+
program,
54+
]
55+
56+
if program == "tcpreplay":
57+
args.append(f"--intf1={ifname}")
58+
args.append('-')
59+
60+
if program == "tcpdump":
61+
args.append("-n")
62+
args.append(f"--interface={ifname}")
63+
args.append("-w")
64+
args.append('-')
65+
66+
if count:
67+
args.append("-c")
68+
args.append(str(count))
69+
70+
try:
71+
os.execvp(args[0], args)
72+
except FileNotFoundError as e:
73+
raise RuntimeError(f"Missing {program} binary") from e
74+
75+
76+
if __name__ == "__main__":
77+
parser = argparse.ArgumentParser()
78+
parser.add_argument(
79+
'-d',
80+
'--debug',
81+
action='store_true',
82+
default=False,
83+
help="enable debug mode"
84+
)
85+
parser.add_argument('program', type=str, help='program to run, either tcpreplay or tcpdump')
86+
parser.add_argument('interface', type=str, help='interface name')
87+
parser.add_argument('count', nargs="?", type=int, default=None, help='amount of frames to capture while recording')
88+
args = parser.parse_args()
89+
try:
90+
main(args.program, args.interface, args.count)
91+
except Exception as e: # pylint: disable=broad-except
92+
if args.debug:
93+
import traceback
94+
traceback.print_exc(file=sys.stderr)
95+
print(f"ERROR: {e}", file=sys.stderr)
96+
exit(1)

labgrid/driver/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from .httpvideodriver import HTTPVideoDriver
4242
from .networkinterfacedriver import NetworkInterfaceDriver
4343
from .provider import HTTPProviderDriver, NFSProviderDriver, TFTPProviderDriver
44+
from .rawnetworkinterfacedriver import RawNetworkInterfaceDriver
4445
from .mqtt import TasmotaPowerDriver
4546
from .manualswitchdriver import ManualSwitchDriver
4647
from .usbtmcdriver import USBTMCDriver

0 commit comments

Comments
 (0)