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