diff --git a/src/linux/netlinkutil/Route.cpp b/src/linux/netlinkutil/Route.cpp index c159fb299..7c0979da1 100644 --- a/src/linux/netlinkutil/Route.cpp +++ b/src/linux/netlinkutil/Route.cpp @@ -3,9 +3,14 @@ #include "Route.h" #include "Utils.h" -Route::Route(int family, const std::optional
& via, int dev, bool defaultRoute, const std::optional
& to, int metric) : - family(family), via(via), dev(dev), defaultRoute(defaultRoute), to(to), metric(metric) +Route::Route(int routeFamily, const std::optional
& routeNextHop, int routeInterface, bool isRouteDefault, const std::optional
& routeDestination, int routeMetric) : + family(routeFamily), via(routeNextHop), dev(routeInterface), defaultRoute(isRouteDefault), to(routeDestination), metric(routeMetric) { + // For onlink routes, ensure the via field is empty + if (via.has_value() && ((family == AF_INET && via->Addr() == "0.0.0.0") || (family == AF_INET6 && via->Addr() == "::"))) + { + via.reset(); + } } std::ostream& operator<<(std::ostream& out, const Route& route) @@ -30,7 +35,7 @@ std::ostream& operator<<(std::ostream& out, const Route& route) bool Route::IsOnlink() const { - return !via.has_value() || (family == AF_INET && via->Addr() == "0.0.0.0") || (family == AF_INET6 && via->Addr() == "::"); + return !via.has_value(); } bool Route::IsMulticast() const diff --git a/src/linux/netlinkutil/Route.h b/src/linux/netlinkutil/Route.h index 7a098c32f..9ff8acee0 100644 --- a/src/linux/netlinkutil/Route.h +++ b/src/linux/netlinkutil/Route.h @@ -14,7 +14,7 @@ struct Route int metric = 0; bool isLoopbackRoute = false; - Route(int family, const std::optional
& via, int dev, bool defaultRoute, const std::optional
& to, int metric); + Route(int routeFamily, const std::optional
& routeNextHop, int routeInterface, bool isRouteDefault, const std::optional
& routeDestination, int routeMetric); bool IsOnlink() const; bool IsMulticast() const; diff --git a/src/linux/netlinkutil/RoutingTable.cpp b/src/linux/netlinkutil/RoutingTable.cpp index 9ec761c0c..56be77537 100644 --- a/src/linux/netlinkutil/RoutingTable.cpp +++ b/src/linux/netlinkutil/RoutingTable.cpp @@ -111,6 +111,10 @@ void RoutingTable::ModifyRouteImpl(const Route& route, Operation action) { ModifyLoopbackRouteImpl(route, operation, flags); } + else if (route.defaultRoute && route.IsOnlink()) + { + ModifyDefaultLinkLocalRouteImpl(route, operation, flags); + } else if (route.defaultRoute) { ModifyDefaultRouteImpl(route, operation, flags); @@ -181,7 +185,7 @@ void RoutingTable::ModifyLoopbackRouteImpl(const Route& route, int operation, in { if (!route.to.has_value() || !route.via.has_value()) { - throw RuntimeErrorWithSourceLocation(std::format("Loopback route {} missing destination or gateway address", utils::Stringify(route))); + throw RuntimeErrorWithSourceLocation(std::format("Loopback route {} missing destination or next hop", utils::Stringify(route))); } struct Message : RouteMessage @@ -193,8 +197,8 @@ void RoutingTable::ModifyLoopbackRouteImpl(const Route& route, int operation, in GNS_LOG_INFO( "SendMessage Route (to {}, via {}), operation ({}), netLinkflags ({})", - route.to.has_value() ? route.to.value().Addr().c_str() : "[empty]", - route.via.has_value() ? route.via.value().Addr().c_str() : "[empty]", + route.to.value().Addr().c_str(), + route.via.value().Addr().c_str(), RouteOperationToString(operation), NetLinkFormatFlagsToString(flags).c_str()); @@ -222,7 +226,7 @@ void RoutingTable::ModifyLoopbackRouteImpl(const Route& route, int operation, in message.route.rtm_flags |= RTNH_F_ONLINK; GNS_LOG_INFO( - "InitializeAddressAttribute RTA_DST ({}) RTA_GATEWAY ({}) RTA_PRIORITY ([not set])", + "Netlink message configuration: RTA_DST ({}) RTA_GATEWAY ({}) RTA_PRIORITY ([not set])", route.to.value().Addr().c_str(), route.via.value().Addr().c_str()); utils::InitializeAddressAttribute(message.to, route.to.value(), RTA_DST); @@ -230,12 +234,44 @@ void RoutingTable::ModifyLoopbackRouteImpl(const Route& route, int operation, in }); } +template +void RoutingTable::ModifyDefaultLinkLocalRouteImpl(const Route& route, int operation, int flags) +{ + if (route.via.has_value()) + { + throw RuntimeErrorWithSourceLocation("Default route has unexpected next hop"); + } + if (route.to.has_value()) + { + throw RuntimeErrorWithSourceLocation("Default route has unexpected destination address"); + } + + struct Message : RouteMessage + { + utils::IntegerAttribute metric; + } __attribute__((packed)); + + GNS_LOG_INFO( + "SendMessage Route (default onlink), operation ({}), netLinkflags ({})", + RouteOperationToString(operation), + NetLinkFormatFlagsToString(flags).c_str()); + + SendMessage(route, operation, flags, [&](Message& message) { + GNS_LOG_INFO("Netlink message configuration: RTA_DST ([not set]) RTA_GATEWAY ([not set]), RTA_PRIORITY ({})", route.metric); + utils::InitializeIntegerAttribute(message.metric, route.metric, RTA_PRIORITY); + }); +} + template void RoutingTable::ModifyDefaultRouteImpl(const Route& route, int operation, int flags) { if (!route.via.has_value()) { - throw RuntimeErrorWithSourceLocation("Default route is missing its gateway address"); + throw RuntimeErrorWithSourceLocation("Default route is missing its next hop"); + } + if (route.to.has_value()) + { + throw RuntimeErrorWithSourceLocation("Default route has unexpected destination address"); } struct Message : RouteMessage @@ -246,15 +282,15 @@ void RoutingTable::ModifyDefaultRouteImpl(const Route& route, int operation, int GNS_LOG_INFO( "SendMessage Route (to {}, via {}), operation ({}), netLinkflags ({})", - route.to.has_value() ? route.to.value().Addr().c_str() : "[empty]", - route.via.has_value() ? route.via.value().Addr().c_str() : "[empty]", + "[empty]", + route.via.value().Addr().c_str(), RouteOperationToString(operation), NetLinkFormatFlagsToString(flags).c_str()); SendMessage(route, operation, flags, [&](Message& message) { GNS_LOG_INFO( - "InitializeAddressAttribute RTA_DST ([not set]) RTA_GATEWAY ({}), RTA_PRIORITY ({})", - route.to.has_value() ? route.to.value().Addr().c_str() : "[empty]", + "Netlink message configuration: RTA_DST ([not set]) RTA_GATEWAY ({}), RTA_PRIORITY ({})", + route.via.value().Addr().c_str(), route.metric); utils::InitializeAddressAttribute(message.via, route.via.value(), RTA_GATEWAY); utils::InitializeIntegerAttribute(message.metric, route.metric, RTA_PRIORITY); @@ -264,6 +300,15 @@ void RoutingTable::ModifyDefaultRouteImpl(const Route& route, int operation, int template void RoutingTable::ModifyLinkLocalRouteImpl(const Route& route, int operation, int flags) { + if (!route.to.has_value()) + { + throw RuntimeErrorWithSourceLocation("Link-local route is missing its destination address"); + } + if (route.via.has_value()) + { + throw RuntimeErrorWithSourceLocation("Link-local route has unexpected next hop"); + } + struct Message : RouteMessage { utils::AddressAttribute to; @@ -272,15 +317,15 @@ void RoutingTable::ModifyLinkLocalRouteImpl(const Route& route, int operation, i GNS_LOG_INFO( "SendMessage Route (to {}, via {}), operation ({}), netLinkflags ({})", - route.to.has_value() ? route.to.value().Addr().c_str() : "[empty]", - route.via.has_value() ? route.via.value().Addr().c_str() : "[empty]", + route.to.value().Addr().c_str(), + "[empty]", RouteOperationToString(operation), NetLinkFormatFlagsToString(flags).c_str()); SendMessage(route, operation, flags, [&](Message& message) { GNS_LOG_INFO( - "InitializeAddressAttribute RTA_DST ({}) RTA_GATEWAY ([not set]), RTA_PRIORITY ({})", - route.to.has_value() ? route.to.value().Addr().c_str() : "[empty]", + "Netlink message configuration: RTA_DST ({}) RTA_GATEWAY ([not set]), RTA_PRIORITY ({})", + route.to.value().Addr().c_str(), route.metric); utils::InitializeAddressAttribute(message.to, route.to.value(), RTA_DST); utils::InitializeIntegerAttribute(message.metric, route.metric, RTA_PRIORITY); @@ -294,6 +339,10 @@ void RoutingTable::ModifyOfflinkRouteImpl(const Route& route, int operation, int { throw RuntimeErrorWithSourceLocation("Offlink route is missing its next hop"); } + if (!route.to.has_value()) + { + throw RuntimeErrorWithSourceLocation("Offlink route is missing its destination address"); + } struct Message : RouteMessage { @@ -304,16 +353,16 @@ void RoutingTable::ModifyOfflinkRouteImpl(const Route& route, int operation, int GNS_LOG_INFO( "SendMessage Route (to {}, via {}), operation ({}), netLinkflags ({})", - route.to.has_value() ? route.to.value().Addr().c_str() : "[empty]", - route.via.has_value() ? route.via.value().Addr().c_str() : "[empty]", + route.to.value().Addr().c_str(), + route.via.value().Addr().c_str(), RouteOperationToString(operation), NetLinkFormatFlagsToString(flags).c_str()); SendMessage(route, operation, flags, [&](Message& message) { GNS_LOG_INFO( - "InitializeAddressAttribute RTA_DST ({}) RTA_GATEWAY ({}), RTA_PRIORITY ({})", - route.to.has_value() ? route.to.value().Addr().c_str() : "[empty]", - route.via.has_value() ? route.via.value().Addr().c_str() : "[empty]", + "Netlink message configuration: RTA_DST ({}) RTA_GATEWAY ({}), RTA_PRIORITY ({})", + route.to.value().Addr().c_str(), + route.via.value().Addr().c_str(), route.metric); utils::InitializeAddressAttribute(message.to, route.to.value(), RTA_DST); utils::InitializeAddressAttribute(message.via, route.via.value(), RTA_GATEWAY); diff --git a/src/linux/netlinkutil/RoutingTable.h b/src/linux/netlinkutil/RoutingTable.h index a5e0d05e4..0f9bfb02c 100644 --- a/src/linux/netlinkutil/RoutingTable.h +++ b/src/linux/netlinkutil/RoutingTable.h @@ -51,6 +51,9 @@ class RoutingTable template void ModifyDefaultRouteImpl(const Route& route, int operation, int flags); + template + void ModifyDefaultLinkLocalRouteImpl(const Route& route, int operation, int flags); + template void ModifyLinkLocalRouteImpl(const Route& route, int operation, int flags); diff --git a/src/windows/service/exe/WslMirroredNetworking.cpp b/src/windows/service/exe/WslMirroredNetworking.cpp index d0f1f8b1a..28b8d3137 100644 --- a/src/windows/service/exe/WslMirroredNetworking.cpp +++ b/src/windows/service/exe/WslMirroredNetworking.cpp @@ -347,6 +347,15 @@ void wsl::core::networking::WslMirroredNetworkManager::ProcessRouteChange() { endpointRoute.Metric = UINT16_MAX; } + + // Some Windows interfaces (like VPNs) can have metric 0 and routes over that interface with metric also 0, adding + // up to 0. Linux treats metric 0 as unspecified and will default to a 1024 metric. The highest priority metric in + // Linux is 1 instead so we need to switch the metric from 0 to 1. + if (endpointRoute.Metric == 0) + { + endpointRoute.Metric = 1; + } + endpoint.Network->Routes.insert(endpointRoute); } } diff --git a/test/windows/MountTests.cpp b/test/windows/MountTests.cpp index 224a4e532..ed1e1f1b4 100644 --- a/test/windows/MountTests.cpp +++ b/test/windows/MountTests.cpp @@ -438,10 +438,9 @@ class MountTests VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--unmount " + absolutePath.wstring()), (DWORD)0); } - TEST_METHOD(AbsolutePathVhdUnmountAfterVMTimeout) + WSL2_TEST_METHOD(AbsolutePathVhdUnmountAfterVMTimeout) { SKIP_UNSUPPORTED_ARM64_MOUNT_TEST(); - WSL2_TEST_ONLY(); WslKeepAlive keepAlive; diff --git a/test/windows/NetworkTests.cpp b/test/windows/NetworkTests.cpp index 28e1822aa..3e519e536 100644 --- a/test/windows/NetworkTests.cpp +++ b/test/windows/NetworkTests.cpp @@ -345,6 +345,41 @@ class NetworkTests VERIFY_ARE_EQUAL(v6State.DefaultRoute->Device, L"eth0"); } + WSL2_TEST_METHOD(AddRemoveDefaultOnlinkRoutes) + { + wsl::shared::hns::Route defaultRouteV4; + defaultRouteV4.NextHop = L"0.0.0.0"; + defaultRouteV4.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_PREFIX; + defaultRouteV4.Family = AF_INET; + defaultRouteV4.Metric = 1; + SendDeviceSettingsRequest(L"eth0", defaultRouteV4, ModifyRequestType::Add, GuestEndpointResourceType::Route); + + wsl::shared::hns::Route defaultRouteV6; + defaultRouteV6.NextHop = L"::"; + defaultRouteV6.DestinationPrefix = LX_INIT_DEFAULT_ROUTE_V6_PREFIX; + defaultRouteV6.Family = AF_INET6; + defaultRouteV6.Metric = 1; + SendDeviceSettingsRequest(L"eth0", defaultRouteV6, ModifyRequestType::Add, GuestEndpointResourceType::Route); + + const bool defaultV4RouteExists = + LxsstuLaunchWsl(L"ip -4 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") == (DWORD)0; + const bool defaultV6RouteExists = + LxsstuLaunchWsl(L"ip -6 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") == (DWORD)0; + + SendDeviceSettingsRequest(L"eth0", defaultRouteV4, ModifyRequestType::Remove, GuestEndpointResourceType::Route); + SendDeviceSettingsRequest(L"eth0", defaultRouteV6, ModifyRequestType::Remove, GuestEndpointResourceType::Route); + + const bool defaultV4RouteRemoved = + LxsstuLaunchWsl(L"ip -4 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") != (DWORD)0; + const bool defaultV6RouteRemoved = + LxsstuLaunchWsl(L"ip -6 route show | grep \"default dev eth0\" | grep -w \"metric 1\"") != (DWORD)0; + + VERIFY_IS_TRUE(defaultV4RouteExists); + VERIFY_IS_TRUE(defaultV6RouteExists); + VERIFY_IS_TRUE(defaultV4RouteRemoved); + VERIFY_IS_TRUE(defaultV6RouteRemoved); + } + WSL2_TEST_METHOD(SetInterfaceDownAndUp) { // Disconnect interface