Skip to content

Commit 0304ec6

Browse files
authored
Merge pull request #1525 from Bastian-Krause/bst/rawnet-conf
driver/rawnetworkinterfacedriver: add interface up/down/(basic, EEE, pause) configuration
2 parents 47fdd37 + a93f147 commit 0304ec6

File tree

2 files changed

+200
-2
lines changed

2 files changed

+200
-2
lines changed

helpers/labgrid-raw-interface

+70-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import argparse
1111
import os
12+
import string
1213
import sys
1314

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

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

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

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

83+
elif program == "ip":
84+
args.append("link")
85+
args.append("set")
86+
args.append("dev")
87+
args.append(options.ifname)
88+
args.append(options.action)
89+
90+
elif program == "ethtool":
91+
allowed_chars = set(string.ascii_letters + string.digits + "-/:")
92+
93+
if options.subcommand == "change":
94+
for arg in options.ethtool_change_args:
95+
if arg.startswith("-") or not allowed_chars.issuperset(arg):
96+
raise ValueError(f"ethtool --change arg '{arg}' contains invalid characters")
97+
98+
args.append("--change")
99+
args.append(options.ifname)
100+
args.extend(options.ethtool_change_args)
101+
102+
elif options.subcommand == "set-eee":
103+
for arg in options.ethtool_set_eee_args:
104+
if arg.startswith("-") or not allowed_chars.issuperset(arg):
105+
raise ValueError(f"ethtool --set-eee arg '{arg}' contains invalid characters")
106+
107+
args.append("--set-eee")
108+
args.append(options.ifname)
109+
args.extend(options.ethtool_set_eee_args)
110+
111+
elif options.subcommand == "pause":
112+
for arg in options.ethtool_pause_args:
113+
if arg.startswith("-") or not allowed_chars.issuperset(arg):
114+
raise ValueError(f"ethtool --pause arg '{arg}' contains invalid characters")
115+
116+
args.append("--pause")
117+
args.append(options.ifname)
118+
args.extend(options.ethtool_pause_args)
119+
82120
try:
83121
os.execvp(args[0], args)
84122
except FileNotFoundError as e:
@@ -102,6 +140,36 @@ if __name__ == "__main__":
102140
tcpreplay_parser = subparsers.add_parser("tcpreplay")
103141
tcpreplay_parser.add_argument("ifname", type=str, help="interface name")
104142

143+
# ip
144+
ip_parser = subparsers.add_parser("ip")
145+
ip_parser.add_argument("ifname", type=str, help="interface name")
146+
ip_parser.add_argument("action", type=str, choices=["up", "down"], help="action, one of {%(choices)s}")
147+
148+
# ethtool
149+
ethtool_parser = subparsers.add_parser("ethtool")
150+
ethtool_subparsers = ethtool_parser.add_subparsers(dest="subcommand")
151+
152+
# ethtool: change
153+
ethtool_change_parser = ethtool_subparsers.add_parser("change")
154+
ethtool_change_parser.add_argument("ifname", type=str, help="interface name")
155+
ethtool_change_parser.add_argument(
156+
"ethtool_change_args", metavar="ARG", nargs=argparse.REMAINDER, help="ethtool --change args"
157+
)
158+
159+
# ethtool: set-eee
160+
ethtool_change_parser = ethtool_subparsers.add_parser("set-eee")
161+
ethtool_change_parser.add_argument("ifname", type=str, help="interface name")
162+
ethtool_change_parser.add_argument(
163+
"ethtool_set_eee_args", metavar="ARG", nargs=argparse.REMAINDER, help="ethtool --set-eee args"
164+
)
165+
166+
# ethtool: pause
167+
ethtool_change_parser = ethtool_subparsers.add_parser("pause")
168+
ethtool_change_parser.add_argument("ifname", type=str, help="interface name")
169+
ethtool_change_parser.add_argument(
170+
"ethtool_pause_args", metavar="ARG", nargs=argparse.REMAINDER, help="ethtool --pause args"
171+
)
172+
105173
args = parser.parse_args()
106174
try:
107175
main(args.program, args)

labgrid/driver/rawnetworkinterfacedriver.py

+130
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import contextlib
33
import json
44
import subprocess
5+
import time
56

67
import attr
78

@@ -10,6 +11,7 @@
1011
from ..step import step
1112
from ..util.helper import processwrapper
1213
from ..util.managedfile import ManagedFile
14+
from ..util.timeout import Timeout
1315
from ..resource.common import NetworkResource
1416

1517

@@ -25,6 +27,14 @@ def __attrs_post_init__(self):
2527
self._record_handle = None
2628
self._replay_handle = None
2729

30+
def on_activate(self):
31+
self._set_interface("up")
32+
self._wait_state("up")
33+
34+
def on_deactivate(self):
35+
self._set_interface("down")
36+
self._wait_state("down")
37+
2838
def _wrap_command(self, args):
2939
wrapper = ["sudo", "labgrid-raw-interface"]
3040

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

48+
@step(args=["state"])
49+
def _set_interface(self, state):
50+
"""Set interface to given state."""
51+
cmd = ["ip", self.iface.ifname, state]
52+
cmd = self._wrap_command(cmd)
53+
subprocess.check_call(cmd)
54+
55+
@Driver.check_active
56+
def set_interface_up(self):
57+
"""Set bound interface up."""
58+
self._set_interface("up")
59+
60+
@Driver.check_active
61+
def set_interface_down(self):
62+
"""Set bound interface down."""
63+
self._set_interface("down")
64+
65+
def _get_state(self):
66+
"""Returns the bound interface's operstate."""
67+
if_state = self.iface.extra.get("state")
68+
if if_state:
69+
return if_state
70+
71+
cmd = self.iface.command_prefix + ["cat", f"/sys/class/net/{self.iface.ifname}/operstate"]
72+
output = processwrapper.check_output(cmd).decode("ascii")
73+
if_state = output.strip()
74+
return if_state
75+
76+
@Driver.check_active
77+
def get_state(self):
78+
"""Returns the bound interface's operstate."""
79+
return self._get_state()
80+
81+
@step(title="wait_state", args=["expected_state", "timeout"])
82+
def _wait_state(self, expected_state, timeout=60):
83+
"""Wait until the expected state is reached or the timeout expires."""
84+
timeout = Timeout(float(timeout))
85+
86+
while True:
87+
if self._get_state() == expected_state:
88+
return
89+
if timeout.expired:
90+
raise TimeoutError(
91+
f"exported interface {self.iface.ifname} did not go {expected_state} within {timeout.timeout} seconds"
92+
)
93+
time.sleep(1)
94+
95+
@Driver.check_active
96+
def wait_state(self, expected_state, timeout=60):
97+
"""Wait until the expected state is reached or the timeout expires."""
98+
self._wait_state(expected_state, timeout=timeout)
99+
100+
@Driver.check_active
101+
def get_ethtool_settings(self):
102+
"""
103+
Returns settings via ethtool of the bound network interface resource.
104+
"""
105+
cmd = self.iface.command_prefix + ["ethtool", "--json", self.iface.ifname]
106+
output = subprocess.check_output(cmd, encoding="utf-8")
107+
return json.loads(output)[0]
108+
109+
@Driver.check_active
110+
@step(args=["settings"])
111+
def ethtool_configure(self, **settings):
112+
"""
113+
Change settings on interface.
114+
115+
Supported settings are described in ethtool(8) --change (use "_" instead of "-").
116+
"""
117+
cmd = ["ethtool", "change", self.iface.ifname]
118+
cmd += [item.replace("_", "-") for pair in settings.items() for item in pair]
119+
cmd = self._wrap_command(cmd)
120+
subprocess.check_call(cmd)
121+
122+
@Driver.check_active
123+
def get_ethtool_eee_settings(self):
124+
"""
125+
Returns Energy-Efficient Ethernet settings via ethtool of the bound network interface
126+
resource.
127+
"""
128+
cmd = self.iface.command_prefix + ["ethtool", "--show-eee", "--json", self.iface.ifname]
129+
output = subprocess.check_output(cmd, encoding="utf-8")
130+
return json.loads(output)[0]
131+
132+
@Driver.check_active
133+
@step(args=["settings"])
134+
def ethtool_configure_eee(self, **settings):
135+
"""
136+
Change Energy-Efficient Ethernet settings via ethtool of the bound network interface
137+
resource.
138+
139+
Supported settings are described in ethtool(8) --set-eee (use "_" instead of "-").
140+
"""
141+
cmd = ["ethtool", "set-eee", self.iface.ifname]
142+
cmd += [item.replace("_", "-") for pair in settings.items() for item in pair]
143+
cmd = self._wrap_command(cmd)
144+
subprocess.check_call(cmd)
145+
146+
@Driver.check_active
147+
def get_ethtool_pause_settings(self):
148+
"""
149+
Returns pause parameters via ethtool of the bound network interface resource.
150+
"""
151+
cmd = self.iface.command_prefix + ["ethtool", "--json", "--show-pause", self.iface.ifname]
152+
output = subprocess.check_output(cmd, encoding="utf-8")
153+
return json.loads(output)[0]
154+
155+
@Driver.check_active
156+
@step(args=["settings"])
157+
def ethtool_configure_pause(self, **settings):
158+
"""
159+
Change pause parameters via ethtool of the bound network interface resource.
160+
161+
Supported settings are described in ethtool(8) --pause
162+
"""
163+
cmd = ["ethtool", "pause", self.iface.ifname]
164+
cmd += [item for pair in settings.items() for item in pair]
165+
cmd = self._wrap_command(cmd)
166+
subprocess.check_call(cmd)
167+
38168
def _stop(self, proc, *, timeout=None):
39169
assert proc is not None
40170

0 commit comments

Comments
 (0)