Fix tracked routes being collapsed by incomplete comparison (#41393)
Mirrored route tracking used an incomplete comparator that considered only route class, destination address, and metric. Distinct routes with different prefix lengths or next hops could therefore be treated as equivalent and silently omitted. The fix preserves the existing route-class ordering while using the complete EndpointRoute comparison for route identity. Regression tests cover prefix length, next hop, metric, exact duplicates, and dependency ordering.
Feng Wang committed
Aug 26, 2026 at 11:04 UTC
98892b29140d5bc3aff859aa926aea45587896cf
3 files changed
+44
-32
src/windows/service/exe/WslCoreTcpIpStateTracking.h
+11
-32
@@ -161,44 +161,23 @@ struct TrackedRoute
161
162
bool operator<(const TrackedRoute& other) const noexcept
163
{
164
- // return true if 'this' is less than (i.e. is ordered before) the input argument
165
- if (Route.IsAutoGeneratedPrefixRoute || other.Route.IsAutoGeneratedPrefixRoute)
166
- {
167
- if (Route.IsAutoGeneratedPrefixRoute && other.Route.IsAutoGeneratedPrefixRoute)
164
+ const auto routeClass = [](const EndpointRoute& route) noexcept {
165
+ if (route.IsAutoGeneratedPrefixRoute)
166
{
169
- // if both are effectively equivalent, sort by their addresses
170
- if (Route.DestinationPrefixString == other.Route.DestinationPrefixString)
171
- {
172
- return Route.Metric < other.Route.Metric;
173
- }
174
- return Route.DestinationPrefixString < other.Route.DestinationPrefixString;
167
+ return 0;
168
}
176
- // else return true if it's the left that's IsAutoGeneratedPrefixRoute
177
- return Route.IsAutoGeneratedPrefixRoute;
178
- }
169
180
- if (Route.IsNextHopOnlink() || other.Route.IsNextHopOnlink())
181
- {
182
- if (Route.IsNextHopOnlink() && other.Route.IsNextHopOnlink())
183
- {
184
- // if both are effectively equivalent, sort by their addresses
185
- if (Route.DestinationPrefixString == other.Route.DestinationPrefixString)
186
- {
187
- return Route.Metric < other.Route.Metric;
188
- }
189
- return Route.DestinationPrefixString < other.Route.DestinationPrefixString;
190
- }
191
- // else return true if it's the left that's IsNextHopOnlink()
192
- return Route.IsNextHopOnlink();
193
- }
170
+ return route.IsNextHopOnlink() ? 1 : 2;
171
+ };
172
195
- // else it's an Add or Update for a route that's not an auto-generated route
196
- // and whose next-hop address is not on-link
197
- if (Route.DestinationPrefixString == other.Route.DestinationPrefixString)
173
+ const auto thisRouteClass = routeClass(Route);
174
+ const auto otherRouteClass = routeClass(other.Route);
175
+ if (thisRouteClass != otherRouteClass)
176
{
199
- return Route.Metric < other.Route.Metric;
177
+ return thisRouteClass < otherRouteClass;
178
}
201
- return Route.DestinationPrefixString < other.Route.DestinationPrefixString;
179
+
180
+ return Route < other.Route;
181
}
182
};
183
test/windows/CMakeLists.txt
+2
@@ -35,6 +35,8 @@ endif ()
35
target_include_directories(wsltests PRIVATE
36
${CMAKE_SOURCE_DIR}/src/windows/WslcSDK
37
${CMAKE_BINARY_DIR}/src/windows/WslcSDK/winrt/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE})
38
+set_property(SOURCE NetworkTests.cpp APPEND PROPERTY INCLUDE_DIRECTORIES
39
+ ${CMAKE_SOURCE_DIR}/src/windows/service/exe)
40
target_link_directories(wsltests PRIVATE ${BIN})
41
target_precompile_headers(wsltests REUSE_FROM common)
42
target_link_libraries(wsltests
test/windows/NetworkTests.cpp
+31
@@ -19,6 +19,7 @@ Abstract:
19
#include "hns_schema.h"
20
#include "WslCoreNetworkEndpointSettings.h"
21
#include "WslCoreNetworkingSupport.h"
22
+#include "WslCoreTcpIpStateTracking.h"
23
24
#include <mstcpip.h>
25
#include <winhttp.h>
@@ -608,6 +609,36 @@ class NetworkTests
609
VERIFY_IS_TRUE(state.Routes.empty());
610
}
611
612
+ TEST_METHOD(TrackedRouteOrderingPreservesRouteIdentity)
613
+ {
614
+ const auto makeRoute = [](const wchar_t* destination, uint8_t prefixLength, const wchar_t* nextHop, ULONG metric = 10) {
615
+ MIB_IPFORWARD_ROW2 routeRow{};
616
+ routeRow.DestinationPrefix.Prefix = wsl::windows::common::string::StringToSockAddrInet(destination);
617
+ routeRow.DestinationPrefix.PrefixLength = prefixLength;
618
+ routeRow.NextHop = wsl::windows::common::string::StringToSockAddrInet(nextHop);
619
+ routeRow.Metric = metric;
620
+ return wsl::core::networking::EndpointRoute(routeRow);
621
+ };
622
+
623
+ std::set<wsl::core::networking::TrackedRoute> routes;
624
+ routes.emplace(makeRoute(L"10.0.0.0", 8, L"192.168.0.1"));
625
+ routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.1"));
626
+ routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.2"));
627
+ routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.2", 20));
628
+ routes.emplace(makeRoute(L"10.0.0.0", 24, L"192.168.0.2"));
629
+
630
+ VERIFY_ARE_EQUAL(static_cast<size_t>(4), routes.size());
631
+
632
+ auto autoGeneratedRoute = makeRoute(L"192.168.0.0", 24, L"0.0.0.0");
633
+ autoGeneratedRoute.IsAutoGeneratedPrefixRoute = true;
634
+ const wsl::core::networking::TrackedRoute trackedAutoGeneratedRoute(autoGeneratedRoute);
635
+ const wsl::core::networking::TrackedRoute trackedOnlinkRoute(makeRoute(L"192.168.1.0", 24, L"0.0.0.0"));
636
+ const wsl::core::networking::TrackedRoute trackedOfflinkRoute(makeRoute(L"192.168.2.0", 24, L"192.168.0.1"));
637
+
638
+ VERIFY_IS_TRUE(trackedAutoGeneratedRoute < trackedOnlinkRoute);
639
+ VERIFY_IS_TRUE(trackedOnlinkRoute < trackedOfflinkRoute);
640
+ }
641
+
642
WSL2_TEST_METHOD(UpdateIpAddress)
643
{
644
TestCase({{L"eth0", {{L"192.168.0.2", 24}}, L"192.168.0.1", {{L"fc00::2", 64}}, L"fc00::1"}});