@@ -226,9 +226,13 @@ func assertNATApplied(t *testing.T) {
226226 got := lines (cmdOut (t , "iptables" , "-S" , awlForwardChain ))
227227 require .Equal (t , want , got , "AWL-FORWARD chain content/order" )
228228
229+ // iptables -S prints matches in its own canonical order (-s before -i,
230+ // -d before -o), regardless of the order the code passes them in
231+ // (outboundJumpArgs/returnJumpArgs use -i/-s and -o/-d), so assert against
232+ // that canonical form.
229233 filter := cmdOut (t , "iptables" , "-S" , "FORWARD" )
230- require .Contains (t , filter , "-i " + testTunIf + " -s " + testAwlSubnet + " -j " + awlForwardChain , "outbound jump" )
231- require .Contains (t , filter , "-o " + testTunIf + " -d " + testAwlSubnet + " -j " + awlForwardChain , "return jump" )
234+ require .Contains (t , filter , "-s " + testAwlSubnet + " -i " + testTunIf + " -j " + awlForwardChain , "outbound jump" )
235+ require .Contains (t , filter , "-d " + testAwlSubnet + " -o " + testTunIf + " -j " + awlForwardChain , "return jump" )
232236
233237 nat := cmdOut (t , "iptables" , "-t" , "nat" , "-S" , "POSTROUTING" )
234238 require .Contains (t , nat , "-s " + testAwlSubnet + " ! -o " + testTunIf + " -j MASQUERADE" , "MASQUERADE" )
@@ -270,7 +274,7 @@ func snapshotNet(t *testing.T) string {
270274 }
271275 section ("ip rule" , cmdOut (t , "ip" , "rule" , "show" ))
272276 section ("route main" , cmdOut (t , "ip" , "-4" , "route" , "show" ))
273- section ("route awl-table" , cmdOut (t , "ip" , "-4" , "route" , "show" , "table" , strconv . Itoa ( tableID ) ))
277+ section ("route awl-table" , routeTableDump (t , tableID ))
274278 section ("iptables filter" , cmdOut (t , "iptables" , "-S" ))
275279 section ("iptables nat" , cmdOut (t , "iptables" , "-t" , "nat" , "-S" ))
276280 return b .String ()
@@ -340,8 +344,28 @@ func mustCmd(t *testing.T, name string, args ...string) {
340344
341345func cmdOut (t * testing.T , name string , args ... string ) string {
342346 t .Helper ()
343- out , err := exec .Command (name , args ... ).Output ()
344- require .NoErrorf (t , err , "%s %s" , name , strings .Join (args , " " ))
347+ // CombinedOutput (not Output) so a failing command surfaces its stderr
348+ // diagnostic in the test log instead of a bare "exit status N". On success
349+ // these commands print nothing to stderr, so the captured value is unchanged.
350+ out , err := exec .Command (name , args ... ).CombinedOutput ()
351+ require .NoErrorf (t , err , "%s %s: %s" , name , strings .Join (args , " " ), out )
352+ return string (out )
353+ }
354+
355+ // routeTableDump returns the routes in the given table, tolerating the
356+ // "table does not exist" case. Newer iproute2/kernels (e.g. Ubuntu 24.04) make
357+ // `ip route show table <id>` fail with exit 2 ("FIB table does not exist") when
358+ // the table has never held a route, whereas older versions returned empty with
359+ // exit 0. Both mean the same thing here — an empty table — so normalise to "".
360+ func routeTableDump (t * testing.T , table int ) string {
361+ t .Helper ()
362+ out , err := exec .Command ("ip" , "-4" , "route" , "show" , "table" , strconv .Itoa (table )).CombinedOutput ()
363+ if err != nil {
364+ if strings .Contains (string (out ), "does not exist" ) {
365+ return ""
366+ }
367+ require .NoErrorf (t , err , "ip -4 route show table %d: %s" , table , out )
368+ }
345369 return string (out )
346370}
347371
0 commit comments