Skip to content

Commit

Permalink
Merge pull request #1525 from Bastian-Krause/bst/rawnet-conf
Browse files Browse the repository at this point in the history
driver/rawnetworkinterfacedriver: add interface up/down/(basic, EEE, pause) configuration
  • Loading branch information
jluebbe authored Nov 1, 2024
2 parents 47fdd37 + a93f147 commit 0304ec6
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 2 deletions.
72 changes: 70 additions & 2 deletions helpers/labgrid-raw-interface
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import argparse
import os
import string
import sys

import yaml
Expand Down Expand Up @@ -45,7 +46,7 @@ def main(program, options):
if options.ifname in denylist:
raise ValueError(f"Interface name '{options.ifname}' is denied in denylist.")

programs = ["tcpreplay", "tcpdump"]
programs = ["tcpreplay", "tcpdump", "ip", "ethtool"]
if program not in programs:
raise ValueError(f"Invalid program {program} called with wrapper, valid programs are: {programs}")

Expand All @@ -57,7 +58,7 @@ def main(program, options):
args.append(f"--intf1={options.ifname}")
args.append("-")

if program == "tcpdump":
elif program == "tcpdump":
args.append("-n")
args.append(f"--interface={options.ifname}")
# Write out each packet as it is received
Expand All @@ -79,6 +80,43 @@ def main(program, options):
args.append("-W")
args.append("1")

elif program == "ip":
args.append("link")
args.append("set")
args.append("dev")
args.append(options.ifname)
args.append(options.action)

elif program == "ethtool":
allowed_chars = set(string.ascii_letters + string.digits + "-/:")

if options.subcommand == "change":
for arg in options.ethtool_change_args:
if arg.startswith("-") or not allowed_chars.issuperset(arg):
raise ValueError(f"ethtool --change arg '{arg}' contains invalid characters")

args.append("--change")
args.append(options.ifname)
args.extend(options.ethtool_change_args)

elif options.subcommand == "set-eee":
for arg in options.ethtool_set_eee_args:
if arg.startswith("-") or not allowed_chars.issuperset(arg):
raise ValueError(f"ethtool --set-eee arg '{arg}' contains invalid characters")

args.append("--set-eee")
args.append(options.ifname)
args.extend(options.ethtool_set_eee_args)

elif options.subcommand == "pause":
for arg in options.ethtool_pause_args:
if arg.startswith("-") or not allowed_chars.issuperset(arg):
raise ValueError(f"ethtool --pause arg '{arg}' contains invalid characters")

args.append("--pause")
args.append(options.ifname)
args.extend(options.ethtool_pause_args)

try:
os.execvp(args[0], args)
except FileNotFoundError as e:
Expand All @@ -102,6 +140,36 @@ if __name__ == "__main__":
tcpreplay_parser = subparsers.add_parser("tcpreplay")
tcpreplay_parser.add_argument("ifname", type=str, help="interface name")

# ip
ip_parser = subparsers.add_parser("ip")
ip_parser.add_argument("ifname", type=str, help="interface name")
ip_parser.add_argument("action", type=str, choices=["up", "down"], help="action, one of {%(choices)s}")

# ethtool
ethtool_parser = subparsers.add_parser("ethtool")
ethtool_subparsers = ethtool_parser.add_subparsers(dest="subcommand")

# ethtool: change
ethtool_change_parser = ethtool_subparsers.add_parser("change")
ethtool_change_parser.add_argument("ifname", type=str, help="interface name")
ethtool_change_parser.add_argument(
"ethtool_change_args", metavar="ARG", nargs=argparse.REMAINDER, help="ethtool --change args"
)

# ethtool: set-eee
ethtool_change_parser = ethtool_subparsers.add_parser("set-eee")
ethtool_change_parser.add_argument("ifname", type=str, help="interface name")
ethtool_change_parser.add_argument(
"ethtool_set_eee_args", metavar="ARG", nargs=argparse.REMAINDER, help="ethtool --set-eee args"
)

# ethtool: pause
ethtool_change_parser = ethtool_subparsers.add_parser("pause")
ethtool_change_parser.add_argument("ifname", type=str, help="interface name")
ethtool_change_parser.add_argument(
"ethtool_pause_args", metavar="ARG", nargs=argparse.REMAINDER, help="ethtool --pause args"
)

args = parser.parse_args()
try:
main(args.program, args)
Expand Down
130 changes: 130 additions & 0 deletions labgrid/driver/rawnetworkinterfacedriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import contextlib
import json
import subprocess
import time

import attr

Expand All @@ -10,6 +11,7 @@
from ..step import step
from ..util.helper import processwrapper
from ..util.managedfile import ManagedFile
from ..util.timeout import Timeout
from ..resource.common import NetworkResource


Expand All @@ -25,6 +27,14 @@ def __attrs_post_init__(self):
self._record_handle = None
self._replay_handle = None

def on_activate(self):
self._set_interface("up")
self._wait_state("up")

def on_deactivate(self):
self._set_interface("down")
self._wait_state("down")

def _wrap_command(self, args):
wrapper = ["sudo", "labgrid-raw-interface"]

Expand All @@ -35,6 +45,126 @@ def _wrap_command(self, args):
# keep wrapper and args as-is
return wrapper + args

@step(args=["state"])
def _set_interface(self, state):
"""Set interface to given state."""
cmd = ["ip", self.iface.ifname, state]
cmd = self._wrap_command(cmd)
subprocess.check_call(cmd)

@Driver.check_active
def set_interface_up(self):
"""Set bound interface up."""
self._set_interface("up")

@Driver.check_active
def set_interface_down(self):
"""Set bound interface down."""
self._set_interface("down")

def _get_state(self):
"""Returns the bound interface's operstate."""
if_state = self.iface.extra.get("state")
if if_state:
return if_state

cmd = self.iface.command_prefix + ["cat", f"/sys/class/net/{self.iface.ifname}/operstate"]
output = processwrapper.check_output(cmd).decode("ascii")
if_state = output.strip()
return if_state

@Driver.check_active
def get_state(self):
"""Returns the bound interface's operstate."""
return self._get_state()

@step(title="wait_state", args=["expected_state", "timeout"])
def _wait_state(self, expected_state, timeout=60):
"""Wait until the expected state is reached or the timeout expires."""
timeout = Timeout(float(timeout))

while True:
if self._get_state() == expected_state:
return
if timeout.expired:
raise TimeoutError(
f"exported interface {self.iface.ifname} did not go {expected_state} within {timeout.timeout} seconds"
)
time.sleep(1)

@Driver.check_active
def wait_state(self, expected_state, timeout=60):
"""Wait until the expected state is reached or the timeout expires."""
self._wait_state(expected_state, timeout=timeout)

@Driver.check_active
def get_ethtool_settings(self):
"""
Returns settings via ethtool of the bound network interface resource.
"""
cmd = self.iface.command_prefix + ["ethtool", "--json", self.iface.ifname]
output = subprocess.check_output(cmd, encoding="utf-8")
return json.loads(output)[0]

@Driver.check_active
@step(args=["settings"])
def ethtool_configure(self, **settings):
"""
Change settings on interface.
Supported settings are described in ethtool(8) --change (use "_" instead of "-").
"""
cmd = ["ethtool", "change", self.iface.ifname]
cmd += [item.replace("_", "-") for pair in settings.items() for item in pair]
cmd = self._wrap_command(cmd)
subprocess.check_call(cmd)

@Driver.check_active
def get_ethtool_eee_settings(self):
"""
Returns Energy-Efficient Ethernet settings via ethtool of the bound network interface
resource.
"""
cmd = self.iface.command_prefix + ["ethtool", "--show-eee", "--json", self.iface.ifname]
output = subprocess.check_output(cmd, encoding="utf-8")
return json.loads(output)[0]

@Driver.check_active
@step(args=["settings"])
def ethtool_configure_eee(self, **settings):
"""
Change Energy-Efficient Ethernet settings via ethtool of the bound network interface
resource.
Supported settings are described in ethtool(8) --set-eee (use "_" instead of "-").
"""
cmd = ["ethtool", "set-eee", self.iface.ifname]
cmd += [item.replace("_", "-") for pair in settings.items() for item in pair]
cmd = self._wrap_command(cmd)
subprocess.check_call(cmd)

@Driver.check_active
def get_ethtool_pause_settings(self):
"""
Returns pause parameters via ethtool of the bound network interface resource.
"""
cmd = self.iface.command_prefix + ["ethtool", "--json", "--show-pause", self.iface.ifname]
output = subprocess.check_output(cmd, encoding="utf-8")
return json.loads(output)[0]

@Driver.check_active
@step(args=["settings"])
def ethtool_configure_pause(self, **settings):
"""
Change pause parameters via ethtool of the bound network interface resource.
Supported settings are described in ethtool(8) --pause
"""
cmd = ["ethtool", "pause", self.iface.ifname]
cmd += [item for pair in settings.items() for item in pair]
cmd = self._wrap_command(cmd)
subprocess.check_call(cmd)

def _stop(self, proc, *, timeout=None):
assert proc is not None

Expand Down

0 comments on commit 0304ec6

Please sign in to comment.