From 9f08625a5824c5b6ccea2f703cf3ec89bf32d0d9 Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 24 Apr 2026 11:04:25 -0700 Subject: [PATCH 1/5] Add WSLCContainerNetworkTypeCustom support for containers on user-created networks --- localization/strings/en-US/Resources.resw | 3 ++ src/windows/common/WSLCContainerLauncher.cpp | 6 +++ src/windows/common/WSLCContainerLauncher.h | 2 + src/windows/service/inc/wslc.idl | 2 +- src/windows/wslcsession/WSLCContainer.cpp | 28 ++++++++--- src/windows/wslcsession/WSLCSession.cpp | 6 +++ src/windows/wslcsession/WSLCSession.h | 2 + test/windows/WSLCTests.cpp | 51 ++++++++++++++++++++ 8 files changed, 93 insertions(+), 7 deletions(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index de258d29d..2d041ccf8 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -1982,6 +1982,9 @@ Usage: Container '{}' not found. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Container network name is required for custom network type + Unrecognized command: '{}' {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated diff --git a/src/windows/common/WSLCContainerLauncher.cpp b/src/windows/common/WSLCContainerLauncher.cpp index 3d75c7d73..aff1e6daf 100644 --- a/src/windows/common/WSLCContainerLauncher.cpp +++ b/src/windows/common/WSLCContainerLauncher.cpp @@ -139,6 +139,11 @@ void WSLCContainerLauncher::SetContainerFlags(WSLCContainerFlags Flags) m_containerFlags = Flags; } +void WSLCContainerLauncher::SetContainerNetworkName(std::string&& name) +{ + m_containerNetworkName = std::move(name); +} + void WSLCContainerLauncher::SetHostname(std::string&& Hostname) { m_hostname = std::move(Hostname); @@ -250,6 +255,7 @@ std::pair> WSLCContainerLauncher::C auto [processOptions, commandLinePtrs, environmentPtrs] = CreateProcessOptions(); options.InitProcessOptions = processOptions; options.ContainerNetwork.ContainerNetworkType = m_containerNetworkType; + options.ContainerNetwork.ContainerNetworkName = m_containerNetworkName.empty() ? nullptr : m_containerNetworkName.c_str(); options.Ports = m_ports.data(); options.PortsCount = static_cast(m_ports.size()); options.StopSignal = m_stopSignal; diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h index 4643bc875..016012d8d 100644 --- a/src/windows/common/WSLCContainerLauncher.h +++ b/src/windows/common/WSLCContainerLauncher.h @@ -73,6 +73,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher void SetEntrypoint(std::vector&& entrypoint); void SetDefaultStopSignal(WSLCSignal Signal); void SetContainerFlags(WSLCContainerFlags Flags); + void SetContainerNetworkName(std::string&& name); void SetHostname(std::string&& Hostname); void SetDomainname(std::string&& Domainame); void SetDnsServers(std::vector&& DnsServers); @@ -93,6 +94,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher std::deque m_volumeNames; std::deque m_containerPaths; WSLCContainerNetworkType m_containerNetworkType; + std::string m_containerNetworkName; std::vector m_entrypoint; WSLCSignal m_stopSignal = WSLCSignalNone; WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone; diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 70d61d5cf..928397251 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -231,7 +231,7 @@ typedef enum _WSLCContainerNetworkType WSLCContainerNetworkTypeNone = 0, WSLCContainerNetworkTypeHost = 1, WSLCContainerNetworkTypeBridged = 2, - // WSLCContainerNetworkTypeCustom = 3 // TODO: Implement when implementing custom networks + WSLCContainerNetworkTypeCustom = 3 } WSLCContainerNetworkType; typedef struct _WSLCContainerNetwork diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 65a0306db..04f59c772 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -146,7 +146,7 @@ uint16_t AllocateEphemeralPort(int family, const char* address) // Builds port mapping list from container options and returns the network mode string. std::pair, std::string> ProcessPortMappings( - std::vector<_WSLCPortMapping>& requestedPorts, WSLCContainerNetworkType networkType, WSLCVirtualMachine& virtualMachine) + std::vector<_WSLCPortMapping>& requestedPorts, WSLCContainerNetworkType networkType, WSLCVirtualMachine& virtualMachine, WSLCSession& session, LPCSTR containerNetworkName) { // Determine network mode string. std::string networkMode; @@ -162,6 +162,16 @@ std::pair, std::string> ProcessPortMappings( { networkMode = "none"; } + else if (networkType == WSLCContainerNetworkTypeCustom) + { + THROW_HR_WITH_USER_ERROR_IF( + E_INVALIDARG, Localization::MessageWslcContainerNetworkNameRequired(), !containerNetworkName || strlen(containerNetworkName) == 0); + + THROW_HR_WITH_USER_ERROR_IF( + WSLC_E_NETWORK_NOT_FOUND, Localization::MessageWslcNetworkNotFound(containerNetworkName), !session.HasNetwork(containerNetworkName)); + + networkMode = containerNetworkName; + } else { THROW_HR_MSG(E_INVALIDARG, "Invalid networking mode: %i", networkType); @@ -185,8 +195,8 @@ std::pair, std::string> ProcessPortMappings( auto& entry = ports.emplace_back(VMPortMapping::FromWSLCPortMapping(e), e.ContainerPort); - // Only allocate port for bridged network. Host mode ports are allocated when the container starts. - if (networkType == WSLCContainerNetworkTypeBridged) + // Allocate VM ports for bridged and custom networks. Host mode ports are allocated when the container starts. + if (networkType == WSLCContainerNetworkTypeBridged || networkType == WSLCContainerNetworkTypeCustom) { entry.VmMapping.AssignVmPort(virtualMachine.AllocatePort(e.Family, e.Protocol)); } @@ -268,7 +278,8 @@ WSLCContainerNetworkType DockerNetworkModeToWSLCNetworkType(const std::string& m return WSLCContainerNetworkTypeNone; } - THROW_HR_MSG(E_INVALIDARG, "Invalid networking mode: %hs", mode.c_str()); + // Any unrecognized mode is a custom user-defined network. + return WSLCContainerNetworkTypeCustom; } std::uint64_t ParseDockerTimestamp(const std::string& timestamp) @@ -1330,7 +1341,12 @@ std::unique_ptr WSLCContainerImpl::Create( } // Process port mappings from container options. - auto [mappedPorts, networkMode] = ProcessPortMappings(ports, containerOptions.ContainerNetwork.ContainerNetworkType, virtualMachine); + auto [mappedPorts, networkMode] = ProcessPortMappings( + ports, + containerOptions.ContainerNetwork.ContainerNetworkType, + virtualMachine, + wslcSession, + containerOptions.ContainerNetwork.ContainerNetworkName); request.HostConfig.NetworkMode = networkMode; @@ -1438,7 +1454,7 @@ std::unique_ptr WSLCContainerImpl::Open( { auto& inserted = ports.emplace_back(ContainerPortMapping{VMPortMapping::FromContainerMetaData(e), e.ContainerPort}); - if (networkingMode == WSLCContainerNetworkTypeBridged) + if (networkingMode == WSLCContainerNetworkTypeBridged || networkingMode == WSLCContainerNetworkTypeCustom) { auto allocation = virtualMachine.TryAllocatePort(e.VmPort, e.Family, e.Protocol); diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index a755f846c..98d8f2517 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2280,6 +2280,12 @@ try } CATCH_RETURN(); +bool WSLCSession::HasNetwork(const std::string& Name) +{ + std::lock_guard networksLock(m_networksLock); + return m_networks.contains(Name); +} + HRESULT WSLCSession::Terminate() try { diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index 935888b6a..537e251be 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -137,6 +137,8 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession IFACEMETHOD(ListNetworks)(_Out_ WSLCNetworkInformation** Networks, _Out_ ULONG* Count) override; IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override; + bool HasNetwork(const std::string& Name); + IFACEMETHOD(Terminate()) override; // ISupportErrorInfo diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 7bde747f4..194369eef 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -5284,6 +5284,57 @@ class WSLCTests } } + WSLC_TEST_METHOD(ContainerCustomNetworkTest) + { + const std::string networkName = "custom-net-test"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCDriverOption opts[] = {{"Subnet", "172.30.0.0/16"}}; + + WSLCNetworkOptions networkOptions{}; + networkOptions.Name = networkName.c_str(); + networkOptions.Driver = "bridge"; + networkOptions.DriverOpts = opts; + networkOptions.DriverOptsCount = ARRAYSIZE(opts); + + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&networkOptions)); + + auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); }); + + WSLCContainerLauncher launcher( + "debian:latest", "test-custom-network", {"sleep", "99999"}, {}, WSLCContainerNetworkType::WSLCContainerNetworkTypeCustom); + launcher.SetContainerNetworkName(std::string(networkName)); + + auto container = launcher.Launch(*m_defaultSession); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); + VERIFY_ARE_EQUAL(container.Inspect().HostConfig.NetworkMode, networkName); + VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGTERM, 0)); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateExited); + VERIFY_SUCCEEDED(container.Get().Delete(WSLCDeleteFlagsNone)); + } + + WSLC_TEST_METHOD(ContainerCustomNetworkNotFoundTest) + { + WSLCContainerLauncher launcher( + "debian:latest", "test-custom-network-notfound", {"sleep", "99999"}, {}, WSLCContainerNetworkType::WSLCContainerNetworkTypeCustom); + launcher.SetContainerNetworkName(std::string("nonexistent-net")); + + auto retVal = launcher.LaunchNoThrow(*m_defaultSession); + VERIFY_ARE_EQUAL(WSLC_E_NETWORK_NOT_FOUND, retVal.first); + ValidateCOMErrorMessageContains(L"nonexistent-net"); + } + + WSLC_TEST_METHOD(ContainerCustomNetworkMissingNameTest) + { + WSLCContainerLauncher launcher( + "debian:latest", "test-custom-network-noname", {"sleep", "99999"}, {}, WSLCContainerNetworkType::WSLCContainerNetworkTypeCustom); + + auto retVal = launcher.LaunchNoThrow(*m_defaultSession); + VERIFY_ARE_EQUAL(E_INVALIDARG, retVal.first); + ValidateCOMErrorMessageContains(L"Container network name is required"); + } + WSLC_TEST_METHOD(ContainerInspect) { // Helper to verify port mappings. From 6002d2e9e92b181e7b281d829df4657ac52fe817 Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 24 Apr 2026 13:09:00 -0700 Subject: [PATCH 2/5] PR feedback --- src/windows/common/WSLCContainerLauncher.cpp | 4 ++-- src/windows/common/WSLCContainerLauncher.h | 2 +- src/windows/wslcsession/WSLCContainer.cpp | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/windows/common/WSLCContainerLauncher.cpp b/src/windows/common/WSLCContainerLauncher.cpp index aff1e6daf..68b0e768b 100644 --- a/src/windows/common/WSLCContainerLauncher.cpp +++ b/src/windows/common/WSLCContainerLauncher.cpp @@ -139,9 +139,9 @@ void WSLCContainerLauncher::SetContainerFlags(WSLCContainerFlags Flags) m_containerFlags = Flags; } -void WSLCContainerLauncher::SetContainerNetworkName(std::string&& name) +void WSLCContainerLauncher::SetContainerNetworkName(std::string&& Name) { - m_containerNetworkName = std::move(name); + m_containerNetworkName = std::move(Name); } void WSLCContainerLauncher::SetHostname(std::string&& Hostname) diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h index 016012d8d..aed6badfc 100644 --- a/src/windows/common/WSLCContainerLauncher.h +++ b/src/windows/common/WSLCContainerLauncher.h @@ -73,7 +73,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher void SetEntrypoint(std::vector&& entrypoint); void SetDefaultStopSignal(WSLCSignal Signal); void SetContainerFlags(WSLCContainerFlags Flags); - void SetContainerNetworkName(std::string&& name); + void SetContainerNetworkName(std::string&& Name); void SetHostname(std::string&& Hostname); void SetDomainname(std::string&& Domainame); void SetDnsServers(std::vector&& DnsServers); diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 04f59c772..7ae44d7a3 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -278,7 +278,10 @@ WSLCContainerNetworkType DockerNetworkModeToWSLCNetworkType(const std::string& m return WSLCContainerNetworkTypeNone; } - // Any unrecognized mode is a custom user-defined network. + // Reject Docker special syntaxes (container:, service:, etc.); + // any plain name is treated as a user-defined custom network. + THROW_HR_IF_MSG(E_INVALIDARG, mode.empty() || mode.find(':') != std::string::npos, "Unsupported Docker network mode: %hs", mode.c_str()); + return WSLCContainerNetworkTypeCustom; } From 940b5a85361a3b40c88d8af38d457b4f0e3e0b75 Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 24 Apr 2026 13:32:19 -0700 Subject: [PATCH 3/5] Address lock-ordering comment and add name validation to HasNetwork --- src/windows/wslcsession/WSLCSession.cpp | 1 + src/windows/wslcsession/WSLCSession.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 98d8f2517..c9af7ebb2 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2282,6 +2282,7 @@ CATCH_RETURN(); bool WSLCSession::HasNetwork(const std::string& Name) { + ValidateName(Name.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH); std::lock_guard networksLock(m_networksLock); return m_networks.contains(Name); } diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index 537e251be..c885ff850 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -137,6 +137,7 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession IFACEMETHOD(ListNetworks)(_Out_ WSLCNetworkInformation** Networks, _Out_ ULONG* Count) override; IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override; + // N.B. Caller must hold m_lock (shared) before calling this method. bool HasNetwork(const std::string& Name); IFACEMETHOD(Terminate()) override; From 0b9b852dc133d53d282201b0ac60e28329e021cc Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 24 Apr 2026 13:43:43 -0700 Subject: [PATCH 4/5] Add __requires_lock_held annotation to HasNetwork --- src/windows/wslcsession/WSLCSession.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index c885ff850..d659713a1 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -138,7 +138,7 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override; // N.B. Caller must hold m_lock (shared) before calling this method. - bool HasNetwork(const std::string& Name); + __requires_lock_held(m_lock) bool HasNetwork(const std::string& Name); IFACEMETHOD(Terminate()) override; From 2cd7aef55a48168295e889c053a5d1afbed32b12 Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 24 Apr 2026 13:58:05 -0700 Subject: [PATCH 5/5] Fix formatting --- localization/strings/en-US/Resources.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 2d041ccf8..602696521 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -1983,7 +1983,7 @@ Usage: {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated - Container network name is required for custom network type + Container network name is required for custom network type. Unrecognized command: '{}'