diff --git a/diagnostics/trace01.etl b/diagnostics/trace01.etl new file mode 100644 index 000000000..52bbb0db7 Binary files /dev/null and b/diagnostics/trace01.etl differ diff --git a/diagnostics/trace02.etl b/diagnostics/trace02.etl new file mode 100644 index 000000000..2c1b3ed2d Binary files /dev/null and b/diagnostics/trace02.etl differ diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index e061370ba..593e8eb7e 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -201,6 +201,26 @@ void EnsureAbsolutePath(const std::filesystem::path& path, bool containerPath) } } +HRESULT InetNtopToHresult(int af, const void* src, char* dst, size_t dstCount) +{ + const char* result = inet_ntop(af, src, dst, dstCount); + if (result) + { + return S_OK; + } + const int wsaError = WSAGetLastError(); + switch (wsaError) + { + case WSAEAFNOSUPPORT: + case WSAEINVAL: + return E_INVALIDARG; + case WSAEFAULT: + return ((dst == nullptr) || (dstCount == 0)) ? E_INVALIDARG : HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + default: + return HRESULT_FROM_WIN32(wsaError); + } +} + bool CopyProcessSettingsToRuntime(WSLCProcessOptions& runtimeOptions, const WslcContainerProcessOptionsInternal* initProcessOptions) { if (initProcessOptions) @@ -617,6 +637,8 @@ try auto result = std::make_unique(); WSLCContainerOptions containerOptions{}; + std::unique_ptr 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; @@ -659,7 +681,6 @@ try containerOptions.NamedVolumesCount = static_cast(internalContainerSettings->namedVolumesCount); } - std::unique_ptr convertedPorts; if (internalContainerSettings->ports && internalContainerSettings->portsCount) { convertedPorts = std::make_unique(internalContainerSettings->portsCount); @@ -671,14 +692,65 @@ 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"); + // Validate IP address if provided and if valid, copy to runtime structure. + if (internalPort.windowsAddress != nullptr) + { + char addrBuf[INET6_ADDRSTRLEN]{}; + switch (internalPort.windowsAddress->ss_family) + { + case AF_INET: + { + const auto* addr4 = reinterpret_cast(internalPort.windowsAddress); + + HRESULT hr = InetNtopToHresult(AF_INET, &addr4->sin_addr, addrBuf, sizeof(addrBuf)); + if (FAILED(hr)) + { + THROW_HR_MSG(hr, "inet_ntop() failed for address family AF_INET"); + } + convertedPort.Family = AF_INET; + break; + } + + case AF_INET6: + { + const auto* addr6 = reinterpret_cast(internalPort.windowsAddress); + HRESULT hr = InetNtopToHresult(AF_INET6, &addr6->sin6_addr, addrBuf, sizeof(addrBuf)); + if (FAILED(hr)) + { + THROW_HR_MSG(hr, "inet_ntop() failed for address family AF_INET6"); + } + convertedPort.Family = AF_INET6; + break; + } + + default: + // Reject unsupported or malformed address families + THROW_HR(E_INVALIDARG); + } + errno_t copyErr = strncpy_s(convertedPort.BindingAddress, sizeof(convertedPort.BindingAddress), addrBuf, _TRUNCATE); + if (copyErr == 0) + { + // Address copied successfully. + } + else if (copyErr == STRUNCATE) + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(copyErr)); + } + } + else + { + // If no binding address is provided, default to local host. + 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(internalContainerSettings->portsCount); } @@ -809,7 +881,11 @@ try for (uint32_t i = 0; i < portMappingCount; ++i) { - RETURN_HR_IF(E_NOTIMPL, portMappings[i].windowsAddress != nullptr); + if (portMappings[i].windowsAddress != nullptr) + { + const auto family = portMappings[i].windowsAddress->ss_family; + RETURN_HR_IF(E_INVALIDARG, family != AF_INET && family != AF_INET6); + } RETURN_HR_IF(E_NOTIMPL, portMappings[i].protocol != 0); } diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index af4e0ee91..81a680f43 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -877,18 +877,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; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings1)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings1, WSLC_CONTAINER_NETWORKING_MODE_NONE)); 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); } @@ -902,19 +902,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)); 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; @@ -927,6 +927,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)); + + sockaddr_storage addr4{}; + auto* sin4 = reinterpret_cast(&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)); + + sockaddr_storage addr6{}; + auto* sin6 = reinterpret_cast(&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 at container creation. + { + WslcContainerSettings containerSettings5; + VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings5)); + VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings5, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED)); + + 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); + } } TEST_METHOD(ContainerVolumeUnit)