-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1157 from Bastian-Krause/bst/network-test
Add RawNetworkInterfaceDriver, labgrid-raw-interface helper and example
- Loading branch information
Showing
7 changed files
with
396 additions
and
0 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,14 @@ | ||
targets: | ||
main: | ||
resources: | ||
NetworkService: | ||
address: 192.168.1.5 | ||
username: root | ||
NetworkInterface: | ||
ifname: enp2s0f3 | ||
drivers: | ||
SSHDriver: {} | ||
RawNetworkInterfaceDriver: {} | ||
options: | ||
local_iface_to_dut_iface: | ||
enp2s0f3: uplink |
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,55 @@ | ||
#!/usr/bin/env python3 | ||
# Generates an Ethernet frame via scapy using pcap, copies pcap to DUT, replays pcap on interface, | ||
# records frame locally (or on exporter, adjust env.yaml accordingly), and compares both. | ||
|
||
import logging | ||
import os | ||
from tempfile import NamedTemporaryFile, TemporaryDirectory | ||
|
||
from scapy.all import Ether, Raw, rdpcap, wrpcap, conf | ||
|
||
from labgrid import Environment | ||
from labgrid.logging import basicConfig, StepLogger | ||
|
||
def generate_frame(): | ||
frame = [Ether(dst="11:22:33:44:55:66", src="66:55:44:33:22:11", type=0x9000)] | ||
padding = "\x00" * (conf.min_pkt_size - len(frame)) | ||
frame = frame[0] / Raw(load=padding) | ||
return frame | ||
|
||
|
||
basicConfig(level=logging.INFO) | ||
StepLogger.start() | ||
env = Environment("env.yaml") | ||
target = env.get_target() | ||
|
||
netdrv = target.get_driver("RawNetworkInterfaceDriver") | ||
ssh = target.get_driver("SSHDriver") | ||
|
||
# get DUT interface | ||
exporter_iface = netdrv.iface.ifname | ||
dut_iface = env.config.get_target_option(target.name, "local_iface_to_dut_iface")[exporter_iface] | ||
|
||
# generate test frame | ||
generated_frame = generate_frame() | ||
|
||
# write pcap, copy to DUT | ||
remote_pcap = "/tmp/pcap" | ||
with NamedTemporaryFile() as pcap: | ||
wrpcap(pcap.name, generated_frame) | ||
ssh.put(pcap.name, remote_pcap) | ||
|
||
# copy recorded pcap from DUT, compare with generated frame | ||
with TemporaryDirectory() as tempdir: | ||
# start record on exporter | ||
tempf = os.path.join(tempdir, "record.pcap") | ||
with netdrv.record(tempf, count=1) as record: | ||
# replay pcap on DUT | ||
ssh.run_check(f"ip link set {dut_iface} up") | ||
ssh.run_check(f"tcpreplay -i {dut_iface} {remote_pcap}") | ||
|
||
remote_frame = rdpcap(tempf) | ||
assert remote_frame[0] == generated_frame[0] | ||
|
||
print("statistics", netdrv.get_statistics()) | ||
print("address", netdrv.get_address()) |
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,96 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# Wrapper script to be deployed on machines whose network interfaces should be | ||
# controllable via the RawNetworkInterfaceDriver. A /etc/labgrid/helpers.yaml | ||
# can deny access to network interfaces. See below. | ||
# | ||
# This is intended to be used via sudo. For example, add via visudo: | ||
# %developers ALL = NOPASSWD: /usr/sbin/labgrid-raw-interface | ||
|
||
import argparse | ||
import os | ||
import sys | ||
|
||
import yaml | ||
|
||
|
||
def get_denylist(): | ||
denylist_file = "/etc/labgrid/helpers.yaml" | ||
try: | ||
with open(denylist_file) as stream: | ||
data = yaml.load(stream, Loader=yaml.SafeLoader) | ||
except (PermissionError, FileNotFoundError, AttributeError) as e: | ||
raise Exception(f"No configuration file ({denylist_file}), inaccessable or invalid yaml") from e | ||
|
||
denylist = data.get("raw-interface", {}).get("denied-interfaces", []) | ||
|
||
if not isinstance(denylist, list): | ||
raise Exception("No explicit denied-interfaces or not a list, please check your configuration") | ||
|
||
denylist.append("lo") | ||
|
||
return denylist | ||
|
||
|
||
def main(program, ifname, count): | ||
if not ifname: | ||
raise ValueError("Empty interface name.") | ||
if any((c == "/" or c.isspace()) for c in ifname): | ||
raise ValueError(f"Interface name '{ifname}' contains invalid characters.") | ||
if len(ifname) > 16: | ||
raise ValueError(f"Interface name '{ifname}' is too long.") | ||
|
||
denylist = get_denylist() | ||
|
||
if ifname in denylist: | ||
raise ValueError(f"Interface name '{ifname}' is denied in denylist.") | ||
|
||
programs = ["tcpreplay", "tcpdump"] | ||
if program not in programs: | ||
raise ValueError(f"Invalid program {program} called with wrapper, valid programs are: {programs}") | ||
|
||
args = [ | ||
program, | ||
] | ||
|
||
if program == "tcpreplay": | ||
args.append(f"--intf1={ifname}") | ||
args.append('-') | ||
|
||
if program == "tcpdump": | ||
args.append("-n") | ||
args.append(f"--interface={ifname}") | ||
args.append("-w") | ||
args.append('-') | ||
|
||
if count: | ||
args.append("-c") | ||
args.append(str(count)) | ||
|
||
try: | ||
os.execvp(args[0], args) | ||
except FileNotFoundError as e: | ||
raise RuntimeError(f"Missing {program} binary") from e | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
'-d', | ||
'--debug', | ||
action='store_true', | ||
default=False, | ||
help="enable debug mode" | ||
) | ||
parser.add_argument('program', type=str, help='program to run, either tcpreplay or tcpdump') | ||
parser.add_argument('interface', type=str, help='interface name') | ||
parser.add_argument('count', nargs="?", type=int, default=None, help='amount of frames to capture while recording') | ||
args = parser.parse_args() | ||
try: | ||
main(args.program, args.interface, args.count) | ||
except Exception as e: # pylint: disable=broad-except | ||
if args.debug: | ||
import traceback | ||
traceback.print_exc(file=sys.stderr) | ||
print(f"ERROR: {e}", file=sys.stderr) | ||
exit(1) |
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
Oops, something went wrong.