Repository navigation
WSLC: Add UDP and IPv6 support for exposed image ports - #41124
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates WSLC’s --publish-all (-P) behavior to expand image-declared ExposedPorts into published port mappings for both UDP and IPv6 (dual-stack loopback), aligning the publish-all behavior more closely with Docker-style expectations while retaining WSLC’s loopback-only binding convention.
Changes:
- Expand image
ExposedPortsinto two bindings per exposed port:127.0.0.1(IPv4) and::1(IPv6), preserving the port’s declared protocol (tcp/udp). - Add an end-to-end test that validates
{tcp,udp} × {ipv4,ipv6}publish-all bindings appear ininspectafter container start.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp | Adds an E2E test to validate publish-all creates dual-stack bindings for both TCP and UDP exposed ports. |
| src/windows/wslcsession/WSLCContainer.cpp | Changes publish-all expansion logic to create IPv4+IPv6 loopback mappings and to no longer drop UDP. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/windows/wslcsession/WSLCContainer.cpp:1827
- The PR description says
--publish-allnow publishes everyExposedPortsentry for both TCP and UDP, but this implementation intentionally skips UDP whenUseWslRelayPortForwarding()is true (NAT / Consomme+wslrelay) and only emits a warning once. Please update the PR summary/description (and any user-facing docs/release notes, if applicable) to reflect that UDP publish-all is conditional on networking mode until the wslrelay path supports UDP.
// The userspace wslrelay port relay only forwards TCP. When it's active, adding a UDP
// mapping would fail the whole container (MapPort throws ERROR_NOT_SUPPORTED), so skip
// UDP exposed ports and warn once. UDP is published normally on the virtioNet path.
const bool relayForwarding = virtualMachine.UseWslRelayPortForwarding();
bool warnedUdpSkipped = false;
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/windows/wslcsession/WSLCContainer.cpp:1852
- When
UseWslRelayPortForwarding()is active, the new publish-all IPv6 mapping (AF_INET6, "::1") will go throughWSLCVirtualMachine::MapRelayPort()viaMapPort(). The wslrelay side treats the incoming family value as a LinuxLX_AF_*constant and converts it withWindowsAddressFamily()(see src/windows/wslrelay/localhost.cpp:239-244), so passing WindowsAF_INET6(23) causes anE_INVALIDARGand breaks container start/create in NAT/relay mode. This was previously masked for IPv4 becauseAF_INEThappens to matchLX_AF_INET.
Fix by translating the Windows family to LX_AF_INET/LX_AF_INET6 before sending WSLC_MAP_PORT::AddressFamily over the relay channel (e.g., in MapPort()/MapRelayPort()).
// Exposed ports carry only a port and protocol (tcp/udp), never an address family.
// Mirror Docker's dual-stack default by publishing each exposed port on both the IPv4
// and IPv6 loopback, while keeping wslc's loopback-only default binding convention.
for (const auto& [family, address] : {std::pair{AF_INET, "127.0.0.1"}, std::pair{AF_INET6, "::1"}})
{
auto& createdPort = ports.emplace_back();
Blue (OneBlue)
left a comment
There was a problem hiding this comment.
LGTM, thank you !
| createdPort.Protocol = protocol; | ||
| strcpy_s(createdPort.BindingAddress, "127.0.0.1"); | ||
| // Exposed ports carry only a port and protocol (tcp/udp), never an address family. | ||
| // Mirror Docker's dual-stack default by publishing each exposed port on both the IPv4 |
There was a problem hiding this comment.
Looking through the tests, I'm assuming that docker is doing the ipv4 <-> ipv6 translation for us ? If so, that's pretty cool !
There was a problem hiding this comment.
Yeah it just works! I was surprised at how easy it was, and the E2E test verifies the functionality.
Summary of the Pull Request
Support IPv6 and UDP in image-declared exposed ports. The
wslc -P/--publish-allpath now publishes every port from an image'sExposedPortsconfig on both IPv4 and IPv6 loopback, for both TCP and UDP — previously it silently published only TCP on IPv4 loopback. UDP publishing is conditional on networking relay (which is TCP only) and emits a warning and gracefully skips.PR Checklist
Detailed Description of the Pull Request / Additional comments
When a container is created with
-P/--publish-all, wslc reads the image'sExposedPortsconfig and publishes each declared port to an ephemeral host port. The prior implementation had two limitations:if (protocol != IPPROTO_TCP) continue;), so aEXPOSE 53/udpport was never published.AF_INETand127.0.0.1, so exposed ports were reachable only over IPv4 loopback.An image's
ExposedPortskeys carry only a port and protocol (port/tcporport/udp) and never an address family, so the code has to choose what to bind. This change mirrors Docker's documented-Pdefault, which publishes to all host addresses on both IPv4 and IPv6, while preserving wslc's convention of binding loopback by default. Each exposed port is now published dual-stack: one mapping on127.0.0.1(IPv4) and one on::1(IPv6), preserving the port's declared protocol (tcp/udp).Networking-mode handling for UDP
wslc has two port-forwarding backends. The virtioNet path (
MapVirtioNetPort, Consomme mode) maps ports at the netstack level and natively supports UDP and IPv6. The userspace wslrelay path (NAT mode, or Consomme with the wslrelay feature flag) is a TCP-only stream relay —WSLCVirtualMachine::MapPort()rejects any non-TCP mapping withERROR_NOT_SUPPORTED.Because publish-all previously skipped UDP unconditionally, an image exposing a UDP port worked (the port was just dropped) regardless of mode. Now that UDP mappings are generated, they would fail the entire container create/start under the wslrelay path. To avoid that regression, the exposed-ports loop now skips UDP ports when
UseWslRelayPortForwarding()is active and emits a one-time user warning (MessageWslcPublishAllUdpNotSupported). TCP (IPv4 + IPv6) publishes normally in all modes; UDP publishes fully on the virtioNet path. This guard is self-retiring — it becomes a no-op automatically once the relay path is no longer used. Adding UDP support to the wslrelay stream relay is potential follow-up work; it is a substantial cross-boundary feature (datagram framing over hvsocket, per-source session tracking) and unnecessary given virtioNet already covers UDP.Testing
Added end-to-end test
WSLCE2E_Container_Create_PublishAll_DualStack(test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp). It builds an image withEXPOSE 8080/tcpandEXPOSE 9090/udp, runs-P, and asserts that each port is published with exactly two bindings — one on127.0.0.1and one on::1— each on a non-zero ephemeral host port. This covers the full{tcp, udp} × {ipv4, ipv6}matrix in a single test. Verified passing against a locally built and deployed x64 Debug build.Validation Steps Performed