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 ()
0 commit comments