From f458c1f26563f2eacec2c9b9b5a8b7fbd1044bf2 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Sat, 15 Aug 2026 08:13:06 +0000 Subject: [PATCH] [client] Skip IPv6 route tests when the default nexthop is unusable (#7212) * [client] Skip IPv6 route tests when the default nexthop is unusable ensureIPv6DefaultRoute treated a successful netlink RouteAdd as proof that a usable IPv6 nexthop exists. Installing ::/0 via loopback can succeed while the kernel still rejects that nexthop for a concrete prefix, which surfaced on ubuntu22/20260810.260 runners as: add route to table: netlink add route: invalid argument Probe the resolved nexthop by installing and removing a discard-prefix route through the same code path the tests use, and skip when it fails. EEXIST means the nexthop already carries a route, so it counts as usable. * [client] Probe the IPv6 nexthop through raw netlink addRoute swallows EAFNOSUPPORT and EOPNOTSUPP via isOpErr, so a nil return did not prove the probe route was installed. Call netlink directly so an unsupported operation skips the test instead of passing as usable. --- .../systemops/v6route_linux_test.go | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/client/internal/routemanager/systemops/v6route_linux_test.go b/client/internal/routemanager/systemops/v6route_linux_test.go index 449d4cbd2..d8c0012d1 100644 --- a/client/internal/routemanager/systemops/v6route_linux_test.go +++ b/client/internal/routemanager/systemops/v6route_linux_test.go @@ -5,6 +5,7 @@ package systemops import ( "errors" "net" + "net/netip" "syscall" "testing" @@ -29,6 +30,7 @@ func ensureIPv6DefaultRoute(t *testing.T) { } if err := netlink.RouteAdd(route); err != nil { if errors.Is(err, syscall.EEXIST) { + requireUsableIPv6Nexthop(t) return } t.Skipf("install IPv6 fallback default route: %v", err) @@ -38,4 +40,36 @@ func ensureIPv6DefaultRoute(t *testing.T) { t.Logf("delete IPv6 fallback default route: %v", err) } }) + + requireUsableIPv6Nexthop(t) +} + +// requireUsableIPv6Nexthop skips the test unless the resolved IPv6 default +// nexthop can actually carry a route. Installing the default route succeeding +// does not imply the kernel accepts it as a nexthop for a concrete prefix. +func requireUsableIPv6Nexthop(t *testing.T) { + t.Helper() + + nexthop, err := GetNextHop(netip.IPv6Unspecified()) + if err != nil { + t.Skipf("resolve IPv6 default nexthop: %v", err) + } + + probe := &netlink.Route{ + Scope: netlink.SCOPE_UNIVERSE, + Table: syscall.RT_TABLE_MAIN, + Family: netlink.FAMILY_V6, + Dst: &net.IPNet{IP: net.ParseIP("100::64"), Mask: net.CIDRMask(128, 128)}, + } + require.NoError(t, addNextHop(nexthop, probe), "build IPv6 probe route") + + switch err := netlink.RouteAdd(probe); { + case err == nil: + if err := netlink.RouteDel(probe); err != nil && !errors.Is(err, syscall.ESRCH) { + t.Logf("delete IPv6 probe route: %v", err) + } + case errors.Is(err, syscall.EEXIST): + default: + t.Skipf("IPv6 nexthop %s unusable for route installation: %v", nexthop, err) + } }