Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fe043b9
Portmapping: add windowsAddress support
richfr Mar 30, 2026
04a4541
Portmapping: add windowsAddress support
richfr Mar 30, 2026
cf34fb8
Addressing PR feedback: update wslcsdk.cpp
richfr Apr 1, 2026
954d1a0
Addressing clang formatting error
richfr Apr 1, 2026
31e28b4
Providing default values for port bindings: wslcsdk.cpp
richfr Apr 1, 2026
7ca8e51
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 1, 2026
7f1eca4
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 1, 2026
eff50d2
Fixed merge conflicts. Set default BindingAddress to 127.0.0.1. Valid…
richfr Apr 7, 2026
41a39eb
Added new function InetNtopToHresult() to map inet result error value…
richfr Apr 7, 2026
895a604
return family name if socket address is invalid
richfr Apr 7, 2026
a788da8
Removed unnecessasry variable bindingAddressStrings. Also, commented …
richfr Apr 7, 2026
644ebab
Fixed clang formatting errors
richfr Apr 7, 2026
47c28f2
Added WSLC Sdk API tests for WslcContainerPortMapping.windowsAddress …
richfr Apr 7, 2026
9ed0f18
Add unique name to containerSettings var to make test debugging from …
richfr Apr 7, 2026
fb9c466
Adjust AF_UNIX portmapping test so that it fails if value accepted
richfr Apr 8, 2026
13ba738
Return more accurate strncpy_s error result
richfr Apr 8, 2026
e482e48
Improved error return value of IP address verification
richfr Apr 8, 2026
1e7656a
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 8, 2026
f4fd0ba
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 8, 2026
45b6841
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 8, 2026
75fa4ac
Provide better error reporting
richfr Apr 13, 2026
fdf802d
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 13, 2026
573786f
Respond to misc copilot feedback
richfr Apr 13, 2026
5a8a0ae
Merge branch 'richfr/portmapping' of https://github.com/microsoft/WSL…
richfr Apr 13, 2026
7f2e047
Resolving merge after syncing with origin
richfr Apr 13, 2026
6b5ffdd
Adjusted error messages
richfr Apr 13, 2026
dd50413
Removed 2 unnecessary lines of code
richfr Apr 13, 2026
86eb5b7
Mark internal function as static to avoid external linkage
richfr Apr 13, 2026
15c2e67
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 13, 2026
b9a3176
Merge branch 'feature/wsl-for-apps' into richfr/portmapping
1wizkid Apr 14, 2026
9618aad
Delete diagnostics/trace01.etl
1wizkid Apr 14, 2026
759f2f7
Delete diagnostics/trace02.etl
1wizkid Apr 14, 2026
3e6f691
Remove ETL files and Add Ignore rule to .gitignore
richfr Apr 14, 2026
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: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,5 @@ doc/site/
directory.build.targets
test-storage/
*.vhdx
*.tar
*.tar
*.etl
78 changes: 68 additions & 10 deletions src/windows/WslcSDK/wslcsdk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@ void EnsureAbsolutePath(const std::filesystem::path& path, bool containerPath)
THROW_HR_IF(E_INVALIDARG, path.is_relative());
}
}
static HRESULT InetNtopToHresult(int af, const void* src, char* dst, size_t dstCount)
{
if (inet_ntop(af, src, dst, dstCount) == nullptr)
{
return HRESULT_FROM_WIN32(WSAGetLastError());
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If inet_ntop() returns null but WSAGetLastError() happens to be 0 (e.g., stale last-error state), HRESULT_FROM_WIN32(0) will incorrectly return S_OK. Consider capturing the error immediately and returning a non-success fallback when the error code is 0 (e.g., E_FAIL), or proactively setting/clearing last error before calling inet_ntop().

Suggested change
return HRESULT_FROM_WIN32(WSAGetLastError());
const auto lastError = WSAGetLastError();
return lastError == 0 ? E_FAIL : HRESULT_FROM_WIN32(lastError);

Copilot uses AI. Check for mistakes.
}
return S_OK;
}

bool CopyProcessSettingsToRuntime(WSLCProcessOptions& runtimeOptions, const WslcContainerProcessOptionsInternal* initProcessOptions)
{
Expand Down Expand Up @@ -617,6 +625,8 @@ try
auto result = std::make_unique<WslcContainerImpl>();

WSLCContainerOptions containerOptions{};
std::unique_ptr<WSLCPortMapping[]> convertedPorts; // this must stay in same scope as containerOptions since containerOptions.Ports is getting a raw pointer to the array owned by convertedPorts.

containerOptions.Image = internalContainerSettings->image;
containerOptions.Name = internalContainerSettings->runtimeName;
containerOptions.HostName = internalContainerSettings->HostName;
Expand Down Expand Up @@ -659,7 +669,6 @@ try
containerOptions.NamedVolumesCount = static_cast<ULONG>(internalContainerSettings->namedVolumesCount);
}

std::unique_ptr<WSLCPortMapping[]> convertedPorts;
if (internalContainerSettings->ports && internalContainerSettings->portsCount)
{
convertedPorts = std::make_unique<WSLCPortMapping[]>(internalContainerSettings->portsCount);
Expand All @@ -671,14 +680,58 @@ try
convertedPort.HostPort = internalPort.windowsPort;
convertedPort.ContainerPort = internalPort.containerPort;

// TODO: Ipv6 & custom binding address support.
convertedPort.Family = AF_INET;

// TODO: Consider using standard protocol numbers instead of our own enum.
convertedPort.Protocol = internalPort.protocol == WSLC_PORT_PROTOCOL_TCP ? IPPROTO_TCP : IPPROTO_UDP;
strcpy_s(convertedPort.BindingAddress, "127.0.0.1");
switch (internalPort.protocol)
{
case WSLC_PORT_PROTOCOL_TCP:
convertedPort.Protocol = IPPROTO_TCP;
break;
case WSLC_PORT_PROTOCOL_UDP:
convertedPort.Protocol = IPPROTO_UDP;
break;
default:
THROW_HR_MSG(E_INVALIDARG, "Unsupported port protocol: %u", internalPort.protocol);
}
// Validate IP address if provided and if valid, copy to runtime structure.
if (internalPort.windowsAddress != nullptr)
{
switch (internalPort.windowsAddress->ss_family)
{
case AF_INET:
{
const auto* addr4 = reinterpret_cast<const sockaddr_in*>(internalPort.windowsAddress);
HRESULT hr = InetNtopToHresult(AF_INET, &addr4->sin_addr, convertedPort.BindingAddress, sizeof(convertedPort.BindingAddress));
if (FAILED(hr))
{
THROW_HR_MSG(hr, "inet_ntop() failed for AF_INET address");
}
Comment thread
1wizkid marked this conversation as resolved.
convertedPort.Family = AF_INET;
break;
}

case AF_INET6:
{
const auto* addr6 = reinterpret_cast<const sockaddr_in6*>(internalPort.windowsAddress);
HRESULT hr = InetNtopToHresult(AF_INET6, &addr6->sin6_addr, convertedPort.BindingAddress, sizeof(convertedPort.BindingAddress));
if (FAILED(hr))
{
THROW_HR_MSG(hr, "inet_ntop() failed for AF_INET6 address");
}
convertedPort.Family = AF_INET6;
break;
}

default:
THROW_HR_MSG(E_INVALIDARG, "Unsupported address family: %d", internalPort.windowsAddress->ss_family);
}
}
else
{
convertedPort.Family = AF_INET;
strcpy_s(convertedPort.BindingAddress, "127.0.0.1");
}
}
containerOptions.Ports = convertedPorts.get();
containerOptions.Ports = convertedPorts.get(); // Make sure convertedPorts stays in scope for life of containerOptions
containerOptions.PortsCount = static_cast<ULONG>(internalContainerSettings->portsCount);
}

Expand Down Expand Up @@ -808,10 +861,15 @@ try

for (uint32_t i = 0; i < portMappingCount; ++i)
{
RETURN_HR_IF(E_NOTIMPL, portMappings[i].windowsAddress != nullptr);
RETURN_HR_IF(E_NOTIMPL, portMappings[i].protocol != 0);
if (portMappings[i].windowsAddress != nullptr)
{
const auto family = portMappings[i].windowsAddress->ss_family;
RETURN_HR_IF_MSG(
E_INVALIDARG, family != AF_INET && family != AF_INET6, "Unsupported address family: %d at port mapping index %u", family, i);
}
RETURN_HR_IF_MSG(
E_NOTIMPL, portMappings[i].protocol != 0, "Unsupported protocol: %d at port mapping index %u", portMappings[i].protocol, i);
Comment thread
1wizkid marked this conversation as resolved.
}

internalType->ports = portMappings;
internalType->portsCount = portMappingCount;

Expand Down
121 changes: 110 additions & 11 deletions test/windows/WslcSdkTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,18 +775,18 @@ class WslcSdkTests

// Negative: port mappings with NONE networking must fail at container creation.
{
WslcContainerSettings containerSettings;
VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings));
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings, WSLC_CONTAINER_NETWORKING_MODE_NONE));
WslcContainerSettings containerSettings1;
Comment thread
1wizkid marked this conversation as resolved.
VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings1));
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings1, WSLC_CONTAINER_NETWORKING_MODE_NONE));
Comment on lines +778 to +780
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new containerSettings1containerSettings5 names don’t communicate intent and make the test harder to scan. Consider using descriptive names tied to the scenario (e.g., noneNetworkingSettings, defaultBindSettings, ipv4BindSettings, ipv6BindSettings, badFamilySettings) to improve readability and reduce the chance of mixing settings between blocks.

Copilot uses AI. Check for mistakes.

WslcContainerPortMapping mapping{};
mapping.windowsPort = 12342;
mapping.containerPort = 8000;
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings, &mapping, 1));
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings1, &mapping, 1));

WslcContainer rawContainer = nullptr;
VERIFY_ARE_EQUAL(WslcCreateContainer(m_defaultSession, &containerSettings, &rawContainer, nullptr), E_INVALIDARG);
VERIFY_ARE_EQUAL(WslcCreateContainer(m_defaultSession, &containerSettings1, &rawContainer, nullptr), E_INVALIDARG);
VERIFY_IS_NULL(rawContainer);
}

Expand All @@ -800,19 +800,19 @@ class WslcSdkTests
const char* env[] = {"PYTHONUNBUFFERED=1"};
VERIFY_SUCCEEDED(WslcSetProcessSettingsEnvVariables(&procSettings, env, ARRAYSIZE(env)));

WslcContainerSettings containerSettings;
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings));
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings));
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
WslcContainerSettings containerSettings2;
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings2));
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings2, &procSettings));
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings2, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
Comment on lines +803 to +806
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new containerSettings1containerSettings5 names don’t communicate intent and make the test harder to scan. Consider using descriptive names tied to the scenario (e.g., noneNetworkingSettings, defaultBindSettings, ipv4BindSettings, ipv6BindSettings, badFamilySettings) to improve readability and reduce the chance of mixing settings between blocks.

Copilot uses AI. Check for mistakes.

WslcContainerPortMapping mapping{};
mapping.windowsPort = 12341;
mapping.containerPort = 8000;
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings, &mapping, 1));
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings2, &mapping, 1));

UniqueContainer container;
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr));
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings2, &container, nullptr));
VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr));

UniqueProcess process;
Expand All @@ -825,6 +825,105 @@ class WslcSdkTests

ExpectHttpResponse(L"http://127.0.0.1:12341", 200);
}

// Functional: port mapping with explicit IPv4 windowsAddress (127.0.0.1).
{
WslcProcessSettings procSettings;
VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings));
const char* argv[] = {"python3", "-m", "http.server", "8000"};
VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv)));
const char* env[] = {"PYTHONUNBUFFERED=1"};
VERIFY_SUCCEEDED(WslcSetProcessSettingsEnvVariables(&procSettings, env, ARRAYSIZE(env)));

WslcContainerSettings containerSettings3;
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings3));
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings3, &procSettings));
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings3, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
Comment on lines +838 to +841
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new containerSettings1containerSettings5 names don’t communicate intent and make the test harder to scan. Consider using descriptive names tied to the scenario (e.g., noneNetworkingSettings, defaultBindSettings, ipv4BindSettings, ipv6BindSettings, badFamilySettings) to improve readability and reduce the chance of mixing settings between blocks.

Copilot uses AI. Check for mistakes.

sockaddr_storage addr4{};
auto* sin4 = reinterpret_cast<sockaddr_in*>(&addr4);
sin4->sin_family = AF_INET;
VERIFY_ARE_EQUAL(inet_pton(AF_INET, "127.0.0.1", &sin4->sin_addr), 1);

WslcContainerPortMapping mapping{};
mapping.windowsPort = 12343;
mapping.containerPort = 8000;
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
mapping.windowsAddress = &addr4;
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings3, &mapping, 1));

UniqueContainer container;
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings3, &container, nullptr));
VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr));

UniqueProcess process;
VERIFY_SUCCEEDED(WslcGetContainerInitProcess(container.get(), &process));

wil::unique_handle ownedStdout;
VERIFY_SUCCEEDED(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &ownedStdout));

WaitForOutput(std::move(ownedStdout), "Serving HTTP on", 30s);

ExpectHttpResponse(L"http://127.0.0.1:12343", 200);
}

// Functional: port mapping with explicit IPv6 windowsAddress (::1).
{
WslcProcessSettings procSettings;
VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings));
const char* argv[] = {"python3", "-m", "http.server", "8000", "--bind", "::"};
VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv)));
const char* env[] = {"PYTHONUNBUFFERED=1"};
VERIFY_SUCCEEDED(WslcSetProcessSettingsEnvVariables(&procSettings, env, ARRAYSIZE(env)));

WslcContainerSettings containerSettings4;
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings4));
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings4, &procSettings));
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings4, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
Comment on lines +879 to +882
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new containerSettings1containerSettings5 names don’t communicate intent and make the test harder to scan. Consider using descriptive names tied to the scenario (e.g., noneNetworkingSettings, defaultBindSettings, ipv4BindSettings, ipv6BindSettings, badFamilySettings) to improve readability and reduce the chance of mixing settings between blocks.

Copilot uses AI. Check for mistakes.

sockaddr_storage addr6{};
auto* sin6 = reinterpret_cast<sockaddr_in6*>(&addr6);
sin6->sin6_family = AF_INET6;
VERIFY_ARE_EQUAL(inet_pton(AF_INET6, "::1", &sin6->sin6_addr), 1);

WslcContainerPortMapping mapping{};
mapping.windowsPort = 12344;
mapping.containerPort = 8000;
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
mapping.windowsAddress = &addr6;
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings4, &mapping, 1));

UniqueContainer container;
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings4, &container, nullptr));
VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr));

UniqueProcess process;
VERIFY_SUCCEEDED(WslcGetContainerInitProcess(container.get(), &process));

wil::unique_handle ownedStdout;
VERIFY_SUCCEEDED(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &ownedStdout));

WaitForOutput(std::move(ownedStdout), "Serving HTTP on", 30s);

ExpectHttpResponse(L"http://[::1]:12344", 200);
}

// Negative: unsupported address family must fail when setting container portmapping values.
{
WslcContainerSettings containerSettings5;
VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings5));
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings5, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
Comment on lines +913 to +915
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new containerSettings1containerSettings5 names don’t communicate intent and make the test harder to scan. Consider using descriptive names tied to the scenario (e.g., noneNetworkingSettings, defaultBindSettings, ipv4BindSettings, ipv6BindSettings, badFamilySettings) to improve readability and reduce the chance of mixing settings between blocks.

Copilot uses AI. Check for mistakes.

sockaddr_storage badAddr{};
badAddr.ss_family = AF_UNIX; // unsupported for port mapping

WslcContainerPortMapping mapping{};
mapping.windowsPort = 12345;
mapping.containerPort = 8000;
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
mapping.windowsAddress = &badAddr;
VERIFY_ARE_EQUAL(WslcSetContainerSettingsPortMappings(&containerSettings5, &mapping, 1), E_INVALIDARG);
}
}

WSLC_TEST_METHOD(ContainerVolumeUnit)
Expand Down