From 12179e704f7c448d39480d41b967cc85e82dcd4a Mon Sep 17 00:00:00 2001 From: Bastian Krause Date: Fri, 5 Jul 2024 19:29:45 +0200 Subject: [PATCH 1/4] driver/rawnetworkinterfacedriver: add interface up/down/wait_state Add methods to the RawNetworkInterfaceDriver to set interfaces up and down, as well as getting and waiting for the interface state. This allows the driver to take more control over the interface, preconfiguration is not needed anymore. Tests that expect the exporter interface to be down (such as ethernet selftests, cable tests) are now possible. Note that the RawNetworkInterfaceDriver now brings the bound interface up on activate and down on deactivate. Signed-off-by: Bastian Krause --- helpers/labgrid-raw-interface | 16 +++++- labgrid/driver/rawnetworkinterfacedriver.py | 62 +++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/helpers/labgrid-raw-interface b/helpers/labgrid-raw-interface index 8aa4c9eac..b2cfdf6a4 100755 --- a/helpers/labgrid-raw-interface +++ b/helpers/labgrid-raw-interface @@ -45,7 +45,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"] if program not in programs: raise ValueError(f"Invalid program {program} called with wrapper, valid programs are: {programs}") @@ -57,7 +57,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 @@ -79,6 +79,13 @@ 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) + try: os.execvp(args[0], args) except FileNotFoundError as e: @@ -102,6 +109,11 @@ 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}") + args = parser.parse_args() try: main(args.program, args) diff --git a/labgrid/driver/rawnetworkinterfacedriver.py b/labgrid/driver/rawnetworkinterfacedriver.py index 7fafa6e08..3bc89992b 100644 --- a/labgrid/driver/rawnetworkinterfacedriver.py +++ b/labgrid/driver/rawnetworkinterfacedriver.py @@ -2,6 +2,7 @@ import contextlib import json import subprocess +import time import attr @@ -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 @@ -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"] @@ -35,6 +45,58 @@ 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) + def _stop(self, proc, *, timeout=None): assert proc is not None From f63dde966aa679f71961abca0685a10fb47bdc94 Mon Sep 17 00:00:00 2001 From: Bastian Krause Date: Tue, 15 Oct 2024 12:07:03 +0200 Subject: [PATCH 2/4] driver/rawnetworkinterfacedriver: ethtool get/change settings Add interface configuration (`ethtool --change`) support to the RawNetworkInterfaceDriver. This allows configuring the bound interface (speed, lanes, duplex, port, master-slave, mdix, autoneg, advertise, phyad, xcvr, wol, sopass, msglvl). Also add add a `get_settings()` method to query those settings. Note that ethtool gained the required --json support for the default sub command in v6.10. Signed-off-by: Bastian Krause --- helpers/labgrid-raw-interface | 26 ++++++++++++++++++++- labgrid/driver/rawnetworkinterfacedriver.py | 22 +++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/helpers/labgrid-raw-interface b/helpers/labgrid-raw-interface index b2cfdf6a4..3bed0d140 100755 --- a/helpers/labgrid-raw-interface +++ b/helpers/labgrid-raw-interface @@ -9,6 +9,7 @@ import argparse import os +import string import sys import yaml @@ -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", "ip"] + programs = ["tcpreplay", "tcpdump", "ip", "ethtool"] if program not in programs: raise ValueError(f"Invalid program {program} called with wrapper, valid programs are: {programs}") @@ -86,6 +87,18 @@ def main(program, options): 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) + try: os.execvp(args[0], args) except FileNotFoundError as e: @@ -114,6 +127,17 @@ if __name__ == "__main__": 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" + ) + args = parser.parse_args() try: main(args.program, args) diff --git a/labgrid/driver/rawnetworkinterfacedriver.py b/labgrid/driver/rawnetworkinterfacedriver.py index 3bc89992b..c71cee4df 100644 --- a/labgrid/driver/rawnetworkinterfacedriver.py +++ b/labgrid/driver/rawnetworkinterfacedriver.py @@ -97,6 +97,28 @@ 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) + def _stop(self, proc, *, timeout=None): assert proc is not None From 63b38ceca274d746f4ac5a6894e62dbdecaede00 Mon Sep 17 00:00:00 2001 From: Bastian Krause Date: Thu, 10 Oct 2024 18:13:17 +0200 Subject: [PATCH 3/4] driver/rawnetworkinterfacedriver: ethtool get/change EEE settings Add interface Energy Efficient Ethernet (EEE) configuration (`ethtool --set-eee`) support to the RawNetworkInterfaceDriver. This allows configuring the EEE parameters eee, tx-lpi, tx-timer and advertise on the bound interface. Also add a `get_eee_settings()` method to query those settings. Note that ethtool gained the required --json support for this sub command in v6.10. Signed-off-by: Bastian Krause --- helpers/labgrid-raw-interface | 16 ++++++++++++++ labgrid/driver/rawnetworkinterfacedriver.py | 24 +++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/helpers/labgrid-raw-interface b/helpers/labgrid-raw-interface index 3bed0d140..d2426c2ef 100755 --- a/helpers/labgrid-raw-interface +++ b/helpers/labgrid-raw-interface @@ -99,6 +99,15 @@ def main(program, options): 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) + try: os.execvp(args[0], args) except FileNotFoundError as e: @@ -138,6 +147,13 @@ if __name__ == "__main__": "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" + ) + args = parser.parse_args() try: main(args.program, args) diff --git a/labgrid/driver/rawnetworkinterfacedriver.py b/labgrid/driver/rawnetworkinterfacedriver.py index c71cee4df..8fd9768b8 100644 --- a/labgrid/driver/rawnetworkinterfacedriver.py +++ b/labgrid/driver/rawnetworkinterfacedriver.py @@ -119,6 +119,30 @@ def ethtool_configure(self, **settings): 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) + def _stop(self, proc, *, timeout=None): assert proc is not None From a93f147423c9e76d966cc6944d2b4692b892afa9 Mon Sep 17 00:00:00 2001 From: Bastian Krause Date: Thu, 10 Oct 2024 19:20:44 +0200 Subject: [PATCH 4/4] driver/rawnetworkinterfacedriver: ethtool get/change pause settings Add interface pause configuration (`ethtool --pause`) support to the RawNetworkInterfaceDriver. This allows configuring the pause parameters autoneg, rx and tx on the bound interface. Also add a `get_pause_settings()` method to query those settings. Signed-off-by: Bastian Krause --- helpers/labgrid-raw-interface | 16 +++++++++++++++ labgrid/driver/rawnetworkinterfacedriver.py | 22 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/helpers/labgrid-raw-interface b/helpers/labgrid-raw-interface index d2426c2ef..f640fc5b0 100755 --- a/helpers/labgrid-raw-interface +++ b/helpers/labgrid-raw-interface @@ -108,6 +108,15 @@ def main(program, options): 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: @@ -154,6 +163,13 @@ if __name__ == "__main__": "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) diff --git a/labgrid/driver/rawnetworkinterfacedriver.py b/labgrid/driver/rawnetworkinterfacedriver.py index 8fd9768b8..861628b15 100644 --- a/labgrid/driver/rawnetworkinterfacedriver.py +++ b/labgrid/driver/rawnetworkinterfacedriver.py @@ -143,6 +143,28 @@ def ethtool_configure_eee(self, **settings): 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