diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index de258d29d..602696521 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..68b0e768b 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..aed6badfc 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..7ae44d7a3 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,11 @@ WSLCContainerNetworkType DockerNetworkModeToWSLCNetworkType(const std::string& m return WSLCContainerNetworkTypeNone; } - THROW_HR_MSG(E_INVALIDARG, "Invalid networking mode: %hs", mode.c_str()); + // 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; } std::uint64_t ParseDockerTimestamp(const std::string& timestamp) @@ -1330,7 +1344,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 +1457,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..c9af7ebb2 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2280,6 +2280,13 @@ try } 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); +} + HRESULT WSLCSession::Terminate() try { diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index 935888b6a..d659713a1 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -137,6 +137,9 @@ 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. + __requires_lock_held(m_lock) 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.