Keep mirrored autobind ports within the host range - #41448
hoobnn (hoobnn) wants to merge 3 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR addresses a mirrored-networking reliability issue where Linux autobind could select ephemeral source ports outside the host (HCN) reserved range after a distro widens net.ipv4.ip_local_port_range, causing localhost TCP connections to hang (e.g., SYN-RECV). It aligns Linux autobind behavior with the host-reserved range by reserving the complement of the HCN allocation in ip_local_reserved_ports, and adds a configurable ephemeral port-range size to support regression testing.
Changes:
- Make the HCN ephemeral port reservation size configurable (
experimental.ephemeralPortRangeSize) and plumb the requested count through mirrored networking to the HCN allocation call. - Update Linux init to reserve ports outside the HCN-allocated ephemeral range via
net.ipv4.ip_local_reserved_ports, preventing autobind from escaping the host-reserved range. - Add/extend Windows tests to validate config parsing and mirrored-mode behavior for configurable ephemeral port range sizing and reserved-port complements.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/windows/UnitTests.cpp | Adds config-warning coverage for invalid experimental.ephemeralPortRangeSize values. |
| test/windows/NetworkTests.cpp | Adds mirrored-mode test coverage for configurable ephemeral port range sizing and reserved-port complement behavior. |
| test/windows/Common.h | Extends test config defaults with ephemeralPortRangeSize. |
| test/windows/Common.cpp | Emits experimental.ephemeralPortRangeSize in generated test configs. |
| src/windows/service/exe/WslCoreGuestNetworkService.h | Updates API to allocate ephemeral port range with a requested port count. |
| src/windows/service/exe/WslCoreGuestNetworkService.cpp | Implements size-parameterized HCN range allocation + validation and enhanced logging. |
| src/windows/service/exe/MirroredNetworking.cpp | Passes configured ephemeral port count into the guest network service allocation call. |
| src/windows/common/WslCoreConfig.h | Adds experimental.ephemeralPortRangeSize to config settings and default config state. |
| src/windows/common/WslCoreConfig.cpp | Parses/validates experimental.ephemeralPortRangeSize from config. |
| src/linux/init/main.cpp | Writes ip_local_reserved_ports complement of the host-reserved range to constrain Linux autobind. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/linux/init/main.cpp:3675
- Writing the complement directly to /proc/sys/net/ipv4/ip_local_reserved_ports overwrites any existing reserved-port configuration in the distro (e.g., values set by sysctl.conf). That can change application behavior by un-reserving ports the user/distro intended to keep out of autobind. Consider merging the existing ip_local_reserved_ports value with the complement (deduping if needed) rather than replacing it.
// Mirrored networking reserves this exact range on the Windows host. Keep ports outside the range out of Linux
// autobind selection even if a distro later widens ip_local_port_range. Explicit bind() calls are unaffected.
std::string ReservedPorts;
if (Start > 1)
{
ReservedPorts = std::format("1-{}", Start - 1);
}
if (End < USHRT_MAX)
{
if (!ReservedPorts.empty())
{
ReservedPorts += ',';
}
ReservedPorts += std::format("{}-{}", End + 1, USHRT_MAX);
}
return WriteToFile("/proc/sys/net/ipv4/ip_local_reserved_ports", ReservedPorts.c_str());
test/windows/NetworkTests.cpp:4469
- This test binds port 0 but does not validate the actual port chosen, nor does it widen net.ipv4.ip_local_port_range after boot (the scenario that caused autobind to escape the host allocation). As written, it will pass even if autobind still escapes once ip_local_port_range is widened. Consider widening ip_local_port_range and using BindGuestPortZero() to assert the assigned port stays within [startPort, endPort].
auto tcpPort = NetworkTests::BindGuestPort(L"TCP4-LISTEN:0", true);
auto udpPort = NetworkTests::BindGuestPort(L"UDP4-LISTEN:0", true);
Mirrored networking reserves a host ephemeral port range in HCN, but changing ip_local_port_range allows Linux autobind to select ports outside that range. Localhost TCP connections from those ports then hang in SYN-RECV. Reserve the complement of the host range with ip_local_reserved_ports so later range changes cannot escape the HCN allocation. Add an experimental range-size setting and regression coverage for TCP and UDP autobind.
Parse the setting with strtol and require full-string consumption, matching ConfigKey::ParseImpl for integers. Values like 4096abc are now rejected with the invalid-integer warning, and base prefixes such as 0x1000 are accepted consistently with other integer settings. Addresses review feedback on the std::stoi inconsistency. Adds a warning test for a trailing-character value.
Merge the existing ip_local_reserved_ports value before adding the HCN complement so current reservations are not discarded. Exercise the original widened-range failure with a real guest loopback TCP connect, verify UDP autobind source ports, and cover the default and parser boundary values.
b89caac to
773d747
Compare
| // Mirrored networking reserves this exact range on the Windows host. Keep ports outside the range out of Linux | ||
| // autobind selection even if a distro later widens ip_local_port_range. Explicit bind() calls are unaffected. | ||
| constexpr auto ReservedPortsPath = "/proc/sys/net/ipv4/ip_local_reserved_ports"; | ||
| std::string ReservedPorts; | ||
| { | ||
| std::ifstream ExistingReservedPorts{ReservedPortsPath}; | ||
| std::getline(ExistingReservedPorts, ReservedPorts); | ||
| } | ||
|
|
||
| if (Start > 1) | ||
| { | ||
| if (!ReservedPorts.empty()) | ||
| { | ||
| ReservedPorts += ','; | ||
| } | ||
| ReservedPorts += std::format("1-{}", Start - 1); | ||
| } | ||
|
|
||
| if (End < USHRT_MAX) | ||
| { | ||
| if (!ReservedPorts.empty()) | ||
| { | ||
| ReservedPorts += ','; | ||
| } | ||
| ReservedPorts += std::format("{}-{}", End + 1, USHRT_MAX); | ||
| } | ||
|
|
||
| return WriteToFile(ReservedPortsPath, ReservedPorts.c_str()); |
| auto [output, warnings] = LxsstuLaunchWslAndCaptureOutput(command.c_str(), 0); | ||
| VERIFY_IS_TRUE(warnings.empty()); | ||
|
|
||
| const auto assignedPort = static_cast<uint16_t>(std::stoul(output)); | ||
| VERIFY_IS_TRUE(assignedPort > 0); | ||
| return assignedPort; |
Summary of the Pull Request
Mirrored networking reserves a host ephemeral port range in HCN and initially
configures Linux to use the same range. If a distribution later expands
net.ipv4.ip_local_port_range, Linux autobind can select a source port outsidethe HCN allocation. Localhost TCP connections from those ports then remain in
SYN-RECV.This change reserves the complement of the HCN allocation in
net.ipv4.ip_local_reserved_ports. Linux can still changeip_local_port_range, but automatic TCP and UDP assignments remain inside thehost range. Explicit binds are unaffected. Existing reserved-port entries are
preserved when the complement is installed.
It also adds
experimental.ephemeralPortRangeSize, with a default of 4096 andvalidation for even values from 2 through 8192, so the allocated HCN range and
Linux reservation can be covered by regression tests.
PR Checklist
Detailed Description
GuestNetworkService::AllocateEphemeralPortRange()now accepts the configuredport count and returns the inclusive range allocated by HCN. Mirrored
networking passes that exact range to init.
Init continues to set
ip_local_port_rangeto the HCN range. It reads thecurrent
ip_local_reserved_portsvalue, preserves it, and appends the rangesbelow and above the HCN allocation. A later change to
ip_local_port_rangetherefore cannot let root-namespace autobind escape the host reservation.
Configuration parsing rejects odd values, values outside 2-8192, empty or
negative values, and trailing characters. Invalid values use the existing
invalid-integer warning and fall back to the 4096-port default without
disabling mirrored networking.
The reservation is scoped to the initial root network namespace. A later
explicit write to
ip_local_reserved_ports, or a newly created networknamespace, has its own lifecycle; this PR targets the issue's root-distro case
where
ip_local_port_rangealone is changed after boot.Regression Coverage
NetworkTests::ConfigurableEphemeralPortRangeSizenow verifies:ip_local_port_rangeto1024 65535after boot;connect()completes and its implicit source portremains within the HCN range;
connect()autobind also selects a source port within the HCN range.The default mirrored-mode test asserts the existing 4096-port behavior.
Configuration tests cover the legal minimum, default, empty, negative, odd,
out-of-range, and trailing-character values.
Validation
The original implementation was built as WSL 2.9.9.3 and manually validated
against the #41227 reproduction: expanding the range changed 0/20 successful
connections before the fix to 20/20 after the fix.
For the current rebased head:
master.The new head still requires the repository's full Windows build and the added
TAEF paths to run in a Windows test environment.