Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,9 @@ Usage:
<value>Container '{}' not found.</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcContainerNetworkNameRequired" xml:space="preserve">
<value>Container network name is required for custom network type.</value>
</data>
<data name="WSLCCLI_UnrecognizedCommandError" xml:space="preserve">
<value>Unrecognized command: '{}'</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
Expand Down
6 changes: 6 additions & 0 deletions src/windows/common/WSLCContainerLauncher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -250,6 +255,7 @@ std::pair<HRESULT, std::optional<RunningWSLCContainer>> 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<ULONG>(m_ports.size());
options.StopSignal = m_stopSignal;
Expand Down
2 changes: 2 additions & 0 deletions src/windows/common/WSLCContainerLauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
void SetEntrypoint(std::vector<std::string>&& 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<std::string>&& DnsServers);
Expand All @@ -93,6 +94,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
std::deque<std::string> m_volumeNames;
std::deque<std::string> m_containerPaths;
WSLCContainerNetworkType m_containerNetworkType;
std::string m_containerNetworkName;
std::vector<std::string> m_entrypoint;
WSLCSignal m_stopSignal = WSLCSignalNone;
WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone;
Expand Down
2 changes: 1 addition & 1 deletion src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 25 additions & 6 deletions src/windows/wslcsession/WSLCContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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::vector<ContainerPortMapping>, 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;
Expand All @@ -162,6 +162,16 @@ std::pair<std::vector<ContainerPortMapping>, 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;
}
Comment thread
beena352 marked this conversation as resolved.
else
{
THROW_HR_MSG(E_INVALIDARG, "Invalid networking mode: %i", networkType);
Expand All @@ -185,8 +195,8 @@ std::pair<std::vector<ContainerPortMapping>, 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));
}
Expand Down Expand Up @@ -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:<id>, service:<name>, 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)
Expand Down Expand Up @@ -1330,7 +1344,12 @@ std::unique_ptr<WSLCContainerImpl> 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;

Expand Down Expand Up @@ -1438,7 +1457,7 @@ std::unique_ptr<WSLCContainerImpl> 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);

Expand Down
7 changes: 7 additions & 0 deletions src/windows/wslcsession/WSLCSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2280,6 +2280,13 @@ try
}
CATCH_RETURN();

bool WSLCSession::HasNetwork(const std::string& Name)
{
Comment thread
beena352 marked this conversation as resolved.
ValidateName(Name.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH);
std::lock_guard networksLock(m_networksLock);
return m_networks.contains(Name);
}

HRESULT WSLCSession::Terminate()
try
{
Expand Down
3 changes: 3 additions & 0 deletions src/windows/wslcsession/WSLCSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 51 additions & 0 deletions test/windows/WSLCTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Comment thread
beena352 marked this conversation as resolved.

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.
Expand Down
Loading