@samitouri / QOSAMI-WSL / commits / 225f1f87

Mark routes via a link-local gateway as onlink to fix VPN /32 networking failures (#41003)

* Mark routes via a link-local gateway as onlink in the guest GnsEngine adds a default route via the link-local NAT gateway (169.254.254.1). When the guest interface has no on-link prefix covering that gateway (e.g. a mirrored host address plumbed as a /32), the kernel rejects the route add with ENETUNREACH. In wslc this aborts ConfigureNetworking, kills /gns, and fails session creation, surfacing to the user as "Catastrophic failure / E_UNEXPECTED". A link-local next hop is only ever reachable on-link, so set RTNH_F_ONLINK for any route whose gateway is link-local (169.254.0.0/16 or fe80::/10). This bypasses the kernel reachability check and has no effect on NAT gateways, which are not link-local. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * IsLinkLocal: return false for non-IP families instead of throwing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed Jul 9, 2026 at 11:26 UTC 225f1f87860f0ea76b7b477e26b0e4e54058ed37
3 files changed +21
src/linux/netlinkutil/Address.cpp
+16
@@ -141,6 +141,22 @@ bool Address::IsIpv4() const noexcept
141 return m_family == AF_INET;
142 }
143
144 +bool Address::IsLinkLocal() const
145 +{
146 + if (m_family == AF_INET)
147 + {
148 + return (ntohl(AsBytes<in_addr>().s_addr) & 0xFFFF0000) == 0xA9FE0000;
149 + }
150 +
151 + if (m_family == AF_INET6)
152 + {
153 + const auto bytes = AsBytes<in6_addr>();
154 + return bytes.s6_addr[0] == 0xFE && (bytes.s6_addr[1] & 0xC0) == 0x80;
155 + }
156 +
157 + return false;
158 +}
159 +
160 bool Address::IsPrefixRouteAutogenerationDisabled() const noexcept
161 {
162 return m_disableAutogeneratedPrefixRoute;
src/linux/netlinkutil/RoutingTable.cpp
+4
@@ -139,6 +139,10 @@ void RoutingTable::SendMessage(const Route& route, int operation, int flags, con
139 message.route.rtm_type = route.IsMulticast() ? RTN_MULTICAST : RTN_UNICAST;
140 message.route.rtm_scope = route.IsOnlink() ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE;
141 message.route.rtm_flags = RTM_F_NOTIFY;
142 + if (route.via.has_value() && route.via.value().IsLinkLocal())
143 + {
144 + message.route.rtm_flags |= RTNH_F_ONLINK;
145 + }
146 message.route.rtm_dst_len = route.to.has_value() ? route.to.value().PrefixLength() : 0;
147
148 utils::InitializeIntegerAttribute(message.tableId, m_table, RTA_TABLE);
src/linux/netlinkutil/address.h
+1
@@ -75,6 +75,7 @@ public:
75 int Scope() const noexcept;
76 int PreferredLifetime() const noexcept;
77 bool IsIpv4() const noexcept;
78 + bool IsLinkLocal() const;
79 bool IsPrefixRouteAutogenerationDisabled() const noexcept;
80
81 private: