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.