Skip to content

Commit edd2e60

Browse files
authored
Merge pull request #1059 from kernelkit/speed_duplex
Verify auto-negotiated speed/duplex
2 parents 9d6d0e2 + ec82d68 commit edd2e60

File tree

9 files changed

+325
-2
lines changed

9 files changed

+325
-2
lines changed

test/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# shellcheck disable=SC2034,SC2154
33

44
# Current container image
5-
INFIX_TEST=ghcr.io/kernelkit/infix-test:2.2
5+
INFIX_TEST=ghcr.io/kernelkit/infix-test:2.3
66

77
ixdir=$(readlink -f "$testdir/..")
88
logdir=$(readlink -f "$testdir/.log")

test/case/ietf_interfaces/Readme.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ include::bridge_veth/Readme.adoc[]
2929

3030
include::bridge_vlan/Readme.adoc[]
3131

32+
include::speed_duplex_copper/Readme.adoc[]
33+
3234
include::bridge_vlan_separation/Readme.adoc[]
3335

3436
include::dual_bridge/Readme.adoc[]

test/case/ietf_interfaces/ietf_interfaces.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
- name: vlan_qos
2121
case: vlan_qos/test.py
2222

23+
- name: speed_duplex_copper
24+
case: speed_duplex_copper/test.py
25+
2326
- name: verify_all_interface_types
2427
case: verify_all_interface_types/test.py
2528

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
speed_duplex_copper.adoc
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== Interface Speed Duplex (Copper)
2+
==== Description
3+
Verify that auto-negotiation results in expected speed/duplex mode.
4+
5+
==== Topology
6+
ifdef::topdoc[]
7+
image::{topdoc}../../test/case/ietf_interfaces/speed_duplex_copper/topology.svg[Interface Speed Duplex (Copper) topology]
8+
endif::topdoc[]
9+
ifndef::topdoc[]
10+
ifdef::testgroup[]
11+
image::speed_duplex_copper/topology.svg[Interface Speed Duplex (Copper) topology]
12+
endif::testgroup[]
13+
ifndef::testgroup[]
14+
image::topology.svg[Interface Speed Duplex (Copper) topology]
15+
endif::testgroup[]
16+
endif::topdoc[]
17+
==== Test sequence
18+
. Set up topology and attach to target DUT
19+
. Enable target interface
20+
. Set fixed 10/full
21+
. Set fixed 10/half
22+
. Set fixed 100/full
23+
. Set fixed 100/half
24+
. Switch to auto-negotiation mode for target and host
25+
. Configure host to advertise 10/Full only
26+
. Configure host to advertise 10/Half only
27+
. Configure host to advertise 100/Full only
28+
. Configure host to advertise 100/Half only
29+
. Configure host to advertise 10/half + 10/full + 100/half
30+
. Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full
31+
32+
33+
<<<
34+
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Interface Speed Duplex (Copper)
4+
5+
Verify that auto-negotiation results in expected speed/duplex mode.
6+
"""
7+
8+
import infamy
9+
import subprocess
10+
from infamy.util import until
11+
12+
ADVERTISE_MODES = {
13+
# Values from ethtool's ETHTOOL_LINK_MODE bit positions
14+
# See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/ethtool.h
15+
"10half": 0x0001,
16+
"10full": 0x0002,
17+
"100half": 0x0004,
18+
"100full": 0x0008,
19+
"1000full": 0x0020
20+
}
21+
22+
def advertise_host_modes(interface, modes):
23+
mask = 0
24+
for mode in modes:
25+
mask |= ADVERTISE_MODES[mode]
26+
try:
27+
subprocess.run([
28+
"ethtool", "-s", interface,
29+
"advertise", hex(mask)
30+
], check=True)
31+
except subprocess.CalledProcessError as e:
32+
raise RuntimeError(f"Failed to advertise modes via ethtool: {e}")
33+
34+
def get_target_speed_duplex(target, interface):
35+
data = target.get_data(f"/ietf-interfaces:interfaces/interface[name='{interface}']") \
36+
["interfaces"]["interface"][interface]
37+
eth = data.get("ethernet", {})
38+
39+
return eth.get("speed"), eth.get("duplex")
40+
41+
def set_target_speed_duplex(target, interface, speed, duplex):
42+
target.put_config_dicts({
43+
"ietf-interfaces": {
44+
"interfaces": {
45+
"interface": [{
46+
"name": interface,
47+
"ethernet": {
48+
"auto-negotiation": {
49+
"enable": False
50+
},
51+
"speed": speed / 1000,
52+
"duplex": duplex
53+
}
54+
}]
55+
}
56+
}
57+
})
58+
59+
def set_host_speed_duplex(interface, speed, duplex):
60+
try:
61+
subprocess.run([
62+
"ethtool", "-s", interface,
63+
"speed", str(speed),
64+
"duplex", duplex,
65+
"autoneg", "off"
66+
], check=True)
67+
except subprocess.CalledProcessError as e:
68+
raise RuntimeError(f"Failed to set speed/duplex via ethtool: {e}")
69+
70+
def verify_speed_duplex(target, ns, interface, exp_speed, exp_duplex):
71+
until(lambda: speed_duplex_present(target, interface))
72+
act_speed, act_duplex = get_target_speed_duplex(target, interface)
73+
if act_speed is None or act_duplex is None:
74+
print(f"Could not fetch speed or duplex from target for interface {interface}")
75+
test.fail()
76+
77+
exp_speed_gbps = exp_speed / 1000
78+
if float(act_speed) != exp_speed_gbps:
79+
print(f"act_speed: {act_speed}, exp_speed: {exp_speed_gbps}")
80+
test.fail()
81+
82+
if act_duplex.lower() != exp_duplex.lower():
83+
print(f"act_duplex: {act_duplex}, exp_duplex: {exp_duplex}")
84+
test.fail()
85+
86+
ns.must_reach("10.0.0.2")
87+
88+
print(f"Verified: {interface} is operating at {act_speed} Gbps, {act_duplex} duplex")
89+
90+
def speed_duplex_present(target, interface):
91+
speed, duplex = get_target_speed_duplex(target, interface)
92+
return speed is not None and duplex is not None
93+
94+
def enable_target_interface(target, interface):
95+
target.put_config_dicts({
96+
"ietf-interfaces": {
97+
"interfaces": {
98+
"interface": [{
99+
"name": interface,
100+
"enabled": True,
101+
"ipv4": {
102+
"address": [
103+
{
104+
"ip": "10.0.0.2",
105+
"prefix-length": 24
106+
}
107+
]
108+
}
109+
}]
110+
}
111+
}
112+
})
113+
114+
def enable_target_autoneg(target, interface):
115+
target.put_config_dicts({
116+
"ietf-interfaces": {
117+
"interfaces": {
118+
"interface": [{
119+
"name": interface,
120+
"ethernet": {
121+
"auto-negotiation": {
122+
"enable": True
123+
}
124+
}
125+
}]
126+
}
127+
}
128+
})
129+
130+
def enable_host_autoneg(interface):
131+
subprocess.run(["ethtool", "-s", interface, "autoneg", "on"], check=True)
132+
133+
def cleanup(target, hdata, tdata):
134+
"""
135+
Restore both host and target interfaces to autonegotiation mode
136+
to ensure clean state for future tests.
137+
"""
138+
139+
print("Restoring interfaces to default (autoneg on)")
140+
try:
141+
enable_host_autoneg(hdata)
142+
except Exception as e:
143+
print(f"Host autoneg restore failed: {e}")
144+
try:
145+
enable_target_interface(target, tdata)
146+
enable_target_autoneg(target, tdata)
147+
except Exception as e:
148+
print(f"Target autoneg restore failed: {e}")
149+
150+
with infamy.Test() as test:
151+
with test.step("Set up topology and attach to target DUT"):
152+
env = infamy.Env()
153+
target = env.attach("target", "mgmt")
154+
_, hdata = env.ltop.xlate("host", "data")
155+
_, tdata = env.ltop.xlate("target", "data")
156+
157+
# Append a test cleanup function
158+
test.push_test_cleanup(lambda: cleanup(target, hdata, tdata))
159+
160+
with test.step("Enable target interface"):
161+
enable_target_interface(target, tdata)
162+
163+
with infamy.IsolatedMacVlan(hdata) as ns:
164+
ns.addip("10.0.0.1")
165+
166+
# Fixed mode tests
167+
with test.step("Set fixed 10/full"):
168+
set_host_speed_duplex(hdata, 10, "full")
169+
set_target_speed_duplex(target, tdata, 10, "full")
170+
verify_speed_duplex(target, ns, tdata, 10, "full")
171+
172+
with test.step("Set fixed 10/half"):
173+
set_host_speed_duplex(hdata, 10, "half")
174+
set_target_speed_duplex(target, tdata, 10, "half")
175+
verify_speed_duplex(target, ns, tdata, 10, "half")
176+
177+
with test.step("Set fixed 100/full"):
178+
set_host_speed_duplex(hdata, 100, "full")
179+
set_target_speed_duplex(target, tdata, 100, "full")
180+
verify_speed_duplex(target, ns, tdata, 100, "full")
181+
182+
with test.step("Set fixed 100/half"):
183+
set_host_speed_duplex(hdata, 100, "half")
184+
set_target_speed_duplex(target, tdata, 100, "half")
185+
verify_speed_duplex(target, ns, tdata, 100, "half")
186+
187+
# Auto-negotiation tests: host advertises, Infix negotiates
188+
with test.step("Switch to auto-negotiation mode for target and host"):
189+
enable_host_autoneg(hdata)
190+
enable_target_autoneg(target, tdata)
191+
192+
with test.step("Configure host to advertise 10/Full only"):
193+
advertise_host_modes(hdata, ["10full"])
194+
verify_speed_duplex(target, ns, tdata, 10, "full")
195+
196+
with test.step("Configure host to advertise 10/Half only"):
197+
advertise_host_modes(hdata, ["10half"])
198+
verify_speed_duplex(target, ns, tdata, 10, "half")
199+
200+
with test.step("Configure host to advertise 100/Full only"):
201+
advertise_host_modes(hdata, ["100full"])
202+
verify_speed_duplex(target, ns, tdata, 100, "full")
203+
204+
with test.step("Configure host to advertise 100/Half only"):
205+
advertise_host_modes(hdata, ["100half"])
206+
verify_speed_duplex(target, ns, tdata, 100, "half")
207+
208+
with test.step("Configure host to advertise 10/half + 10/full + 100/half"):
209+
advertise_host_modes(hdata, ["10half", "10full", "100half"])
210+
verify_speed_duplex(target, ns, tdata, 100, "half")
211+
212+
with test.step("Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full"):
213+
advertise_host_modes(hdata, ["10half", "10full", "100half", "100full", "1000full"])
214+
verify_speed_duplex(target, ns, tdata, 1000, "full")
215+
216+
test.succeed()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
graph "1x2" {
2+
layout="neato";
3+
overlap="false";
4+
esep="+80";
5+
6+
node [shape=record, fontname="DejaVu Sans Mono, Book"];
7+
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
8+
9+
host [
10+
label="host | { <mgmt> mgmt | <data> data }",
11+
pos="0,12!",
12+
requires="controller",
13+
];
14+
15+
target [
16+
label="{ <mgmt> mgmt | <data> data } | target",
17+
pos="10,12!",
18+
19+
requires="infix",
20+
];
21+
22+
host:mgmt -- target:mgmt [requires="mgmt", color=lightgrey]
23+
host:data -- target:data [color=black, requires="link-ctrl copper"]
24+
}
Lines changed: 42 additions & 0 deletions
Loading

test/docker/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ RUN apk add --no-cache \
2626
openssl \
2727
curl \
2828
e2tools \
29-
make
29+
make \
30+
ethtool
3031

3132
ARG MTOOL_VERSION="3.0"
3233
RUN wget https://github.com/troglobit/mtools/releases/download/v3.0/mtools-$MTOOL_VERSION.tar.gz -O /tmp/mtools-$MTOOL_VERSION.tar.gz

0 commit comments

Comments
 (0)