Skip to content

Commit 630929d

Browse files
1wizkidrichfr
andauthored
Richfr/ Add windowsAddress support to WslcCreateContainer() API (#40037)
* Portmapping: add windowsAddress support * Portmapping: add windowsAddress support * Addressing PR feedback: update wslcsdk.cpp * Addressing clang formatting error * Providing default values for port bindings: wslcsdk.cpp * Added new function InetNtopToHresult() to map inet result error values to the correct HRESULTS * return family name if socket address is invalid * Removed unnecessasry variable bindingAddressStrings. Also, commented that convertedPorts must stay in same scope as containerOptions and moved the declaration to be with that var * Fixed clang formatting errors * Added WSLC Sdk API tests for WslcContainerPortMapping.windowsAddress for Ipv4 and Ipv6 local host. * Add unique name to containerSettings var to make test debugging from log reports easier. * Adjust AF_UNIX portmapping test so that it fails if value accepted * Return more accurate strncpy_s error result * Improved error return value of IP address verification * Provide better error reporting * Respond to misc copilot feedback * Resolving merge after syncing with origin * Adjusted error messages * Removed 2 unnecessary lines of code * Mark internal function as static to avoid external linkage --------- Co-authored-by: Richard Fricks <richfr@microsoft.com>
1 parent a05c9f8 commit 630929d

3 files changed

Lines changed: 180 additions & 22 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ doc/site/
6767
directory.build.targets
6868
test-storage/
6969
*.vhdx
70-
*.tar
70+
*.tar
71+
*.etl

src/windows/WslcSDK/wslcsdk.cpp

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ void EnsureAbsolutePath(const std::filesystem::path& path, bool containerPath)
200200
THROW_HR_IF(E_INVALIDARG, path.is_relative());
201201
}
202202
}
203+
static HRESULT InetNtopToHresult(int af, const void* src, char* dst, size_t dstCount)
204+
{
205+
if (inet_ntop(af, src, dst, dstCount) == nullptr)
206+
{
207+
return HRESULT_FROM_WIN32(WSAGetLastError());
208+
}
209+
return S_OK;
210+
}
203211

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

619627
WSLCContainerOptions containerOptions{};
628+
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.
629+
620630
containerOptions.Image = internalContainerSettings->image;
621631
containerOptions.Name = internalContainerSettings->runtimeName;
622632
containerOptions.HostName = internalContainerSettings->HostName;
@@ -659,7 +669,6 @@ try
659669
containerOptions.NamedVolumesCount = static_cast<ULONG>(internalContainerSettings->namedVolumesCount);
660670
}
661671

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

674-
// TODO: Ipv6 & custom binding address support.
675-
convertedPort.Family = AF_INET;
676-
677683
// TODO: Consider using standard protocol numbers instead of our own enum.
678-
convertedPort.Protocol = internalPort.protocol == WSLC_PORT_PROTOCOL_TCP ? IPPROTO_TCP : IPPROTO_UDP;
679-
strcpy_s(convertedPort.BindingAddress, "127.0.0.1");
684+
switch (internalPort.protocol)
685+
{
686+
case WSLC_PORT_PROTOCOL_TCP:
687+
convertedPort.Protocol = IPPROTO_TCP;
688+
break;
689+
case WSLC_PORT_PROTOCOL_UDP:
690+
convertedPort.Protocol = IPPROTO_UDP;
691+
break;
692+
default:
693+
THROW_HR_MSG(E_INVALIDARG, "Unsupported port protocol: %u", internalPort.protocol);
694+
}
695+
// Validate IP address if provided and if valid, copy to runtime structure.
696+
if (internalPort.windowsAddress != nullptr)
697+
{
698+
switch (internalPort.windowsAddress->ss_family)
699+
{
700+
case AF_INET:
701+
{
702+
const auto* addr4 = reinterpret_cast<const sockaddr_in*>(internalPort.windowsAddress);
703+
HRESULT hr = InetNtopToHresult(AF_INET, &addr4->sin_addr, convertedPort.BindingAddress, sizeof(convertedPort.BindingAddress));
704+
if (FAILED(hr))
705+
{
706+
THROW_HR_MSG(hr, "inet_ntop() failed for AF_INET address");
707+
}
708+
convertedPort.Family = AF_INET;
709+
break;
710+
}
711+
712+
case AF_INET6:
713+
{
714+
const auto* addr6 = reinterpret_cast<const sockaddr_in6*>(internalPort.windowsAddress);
715+
HRESULT hr = InetNtopToHresult(AF_INET6, &addr6->sin6_addr, convertedPort.BindingAddress, sizeof(convertedPort.BindingAddress));
716+
if (FAILED(hr))
717+
{
718+
THROW_HR_MSG(hr, "inet_ntop() failed for AF_INET6 address");
719+
}
720+
convertedPort.Family = AF_INET6;
721+
break;
722+
}
723+
724+
default:
725+
THROW_HR_MSG(E_INVALIDARG, "Unsupported address family: %d", internalPort.windowsAddress->ss_family);
726+
}
727+
}
728+
else
729+
{
730+
convertedPort.Family = AF_INET;
731+
strcpy_s(convertedPort.BindingAddress, "127.0.0.1");
732+
}
680733
}
681-
containerOptions.Ports = convertedPorts.get();
734+
containerOptions.Ports = convertedPorts.get(); // Make sure convertedPorts stays in scope for life of containerOptions
682735
containerOptions.PortsCount = static_cast<ULONG>(internalContainerSettings->portsCount);
683736
}
684737

@@ -808,10 +861,15 @@ try
808861

809862
for (uint32_t i = 0; i < portMappingCount; ++i)
810863
{
811-
RETURN_HR_IF(E_NOTIMPL, portMappings[i].windowsAddress != nullptr);
812-
RETURN_HR_IF(E_NOTIMPL, portMappings[i].protocol != 0);
864+
if (portMappings[i].windowsAddress != nullptr)
865+
{
866+
const auto family = portMappings[i].windowsAddress->ss_family;
867+
RETURN_HR_IF_MSG(
868+
E_INVALIDARG, family != AF_INET && family != AF_INET6, "Unsupported address family: %d at port mapping index %u", family, i);
869+
}
870+
RETURN_HR_IF_MSG(
871+
E_NOTIMPL, portMappings[i].protocol != 0, "Unsupported protocol: %d at port mapping index %u", portMappings[i].protocol, i);
813872
}
814-
815873
internalType->ports = portMappings;
816874
internalType->portsCount = portMappingCount;
817875

test/windows/WslcSdkTests.cpp

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -775,18 +775,18 @@ class WslcSdkTests
775775

776776
// Negative: port mappings with NONE networking must fail at container creation.
777777
{
778-
WslcContainerSettings containerSettings;
779-
VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings));
780-
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings, WSLC_CONTAINER_NETWORKING_MODE_NONE));
778+
WslcContainerSettings containerSettings1;
779+
VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings1));
780+
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings1, WSLC_CONTAINER_NETWORKING_MODE_NONE));
781781

782782
WslcContainerPortMapping mapping{};
783783
mapping.windowsPort = 12342;
784784
mapping.containerPort = 8000;
785785
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
786-
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings, &mapping, 1));
786+
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings1, &mapping, 1));
787787

788788
WslcContainer rawContainer = nullptr;
789-
VERIFY_ARE_EQUAL(WslcCreateContainer(m_defaultSession, &containerSettings, &rawContainer, nullptr), E_INVALIDARG);
789+
VERIFY_ARE_EQUAL(WslcCreateContainer(m_defaultSession, &containerSettings1, &rawContainer, nullptr), E_INVALIDARG);
790790
VERIFY_IS_NULL(rawContainer);
791791
}
792792

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

803-
WslcContainerSettings containerSettings;
804-
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings));
805-
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings, &procSettings));
806-
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
803+
WslcContainerSettings containerSettings2;
804+
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings2));
805+
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings2, &procSettings));
806+
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings2, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
807807

808808
WslcContainerPortMapping mapping{};
809809
mapping.windowsPort = 12341;
810810
mapping.containerPort = 8000;
811811
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
812-
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings, &mapping, 1));
812+
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings2, &mapping, 1));
813813

814814
UniqueContainer container;
815-
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings, &container, nullptr));
815+
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings2, &container, nullptr));
816816
VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr));
817817

818818
UniqueProcess process;
@@ -825,6 +825,105 @@ class WslcSdkTests
825825

826826
ExpectHttpResponse(L"http://127.0.0.1:12341", 200);
827827
}
828+
829+
// Functional: port mapping with explicit IPv4 windowsAddress (127.0.0.1).
830+
{
831+
WslcProcessSettings procSettings;
832+
VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings));
833+
const char* argv[] = {"python3", "-m", "http.server", "8000"};
834+
VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv)));
835+
const char* env[] = {"PYTHONUNBUFFERED=1"};
836+
VERIFY_SUCCEEDED(WslcSetProcessSettingsEnvVariables(&procSettings, env, ARRAYSIZE(env)));
837+
838+
WslcContainerSettings containerSettings3;
839+
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings3));
840+
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings3, &procSettings));
841+
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings3, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
842+
843+
sockaddr_storage addr4{};
844+
auto* sin4 = reinterpret_cast<sockaddr_in*>(&addr4);
845+
sin4->sin_family = AF_INET;
846+
VERIFY_ARE_EQUAL(inet_pton(AF_INET, "127.0.0.1", &sin4->sin_addr), 1);
847+
848+
WslcContainerPortMapping mapping{};
849+
mapping.windowsPort = 12343;
850+
mapping.containerPort = 8000;
851+
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
852+
mapping.windowsAddress = &addr4;
853+
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings3, &mapping, 1));
854+
855+
UniqueContainer container;
856+
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings3, &container, nullptr));
857+
VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr));
858+
859+
UniqueProcess process;
860+
VERIFY_SUCCEEDED(WslcGetContainerInitProcess(container.get(), &process));
861+
862+
wil::unique_handle ownedStdout;
863+
VERIFY_SUCCEEDED(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &ownedStdout));
864+
865+
WaitForOutput(std::move(ownedStdout), "Serving HTTP on", 30s);
866+
867+
ExpectHttpResponse(L"http://127.0.0.1:12343", 200);
868+
}
869+
870+
// Functional: port mapping with explicit IPv6 windowsAddress (::1).
871+
{
872+
WslcProcessSettings procSettings;
873+
VERIFY_SUCCEEDED(WslcInitProcessSettings(&procSettings));
874+
const char* argv[] = {"python3", "-m", "http.server", "8000", "--bind", "::"};
875+
VERIFY_SUCCEEDED(WslcSetProcessSettingsCmdLine(&procSettings, argv, ARRAYSIZE(argv)));
876+
const char* env[] = {"PYTHONUNBUFFERED=1"};
877+
VERIFY_SUCCEEDED(WslcSetProcessSettingsEnvVariables(&procSettings, env, ARRAYSIZE(env)));
878+
879+
WslcContainerSettings containerSettings4;
880+
VERIFY_SUCCEEDED(WslcInitContainerSettings("python:3.12-alpine", &containerSettings4));
881+
VERIFY_SUCCEEDED(WslcSetContainerSettingsInitProcess(&containerSettings4, &procSettings));
882+
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings4, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
883+
884+
sockaddr_storage addr6{};
885+
auto* sin6 = reinterpret_cast<sockaddr_in6*>(&addr6);
886+
sin6->sin6_family = AF_INET6;
887+
VERIFY_ARE_EQUAL(inet_pton(AF_INET6, "::1", &sin6->sin6_addr), 1);
888+
889+
WslcContainerPortMapping mapping{};
890+
mapping.windowsPort = 12344;
891+
mapping.containerPort = 8000;
892+
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
893+
mapping.windowsAddress = &addr6;
894+
VERIFY_SUCCEEDED(WslcSetContainerSettingsPortMappings(&containerSettings4, &mapping, 1));
895+
896+
UniqueContainer container;
897+
VERIFY_SUCCEEDED(WslcCreateContainer(m_defaultSession, &containerSettings4, &container, nullptr));
898+
VERIFY_SUCCEEDED(WslcStartContainer(container.get(), WSLC_CONTAINER_START_FLAG_ATTACH, nullptr));
899+
900+
UniqueProcess process;
901+
VERIFY_SUCCEEDED(WslcGetContainerInitProcess(container.get(), &process));
902+
903+
wil::unique_handle ownedStdout;
904+
VERIFY_SUCCEEDED(WslcGetProcessIOHandle(process.get(), WSLC_PROCESS_IO_HANDLE_STDOUT, &ownedStdout));
905+
906+
WaitForOutput(std::move(ownedStdout), "Serving HTTP on", 30s);
907+
908+
ExpectHttpResponse(L"http://[::1]:12344", 200);
909+
}
910+
911+
// Negative: unsupported address family must fail when setting container portmapping values.
912+
{
913+
WslcContainerSettings containerSettings5;
914+
VERIFY_SUCCEEDED(WslcInitContainerSettings("debian:latest", &containerSettings5));
915+
VERIFY_SUCCEEDED(WslcSetContainerSettingsNetworkingMode(&containerSettings5, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED));
916+
917+
sockaddr_storage badAddr{};
918+
badAddr.ss_family = AF_UNIX; // unsupported for port mapping
919+
920+
WslcContainerPortMapping mapping{};
921+
mapping.windowsPort = 12345;
922+
mapping.containerPort = 8000;
923+
mapping.protocol = WSLC_PORT_PROTOCOL_TCP;
924+
mapping.windowsAddress = &badAddr;
925+
VERIFY_ARE_EQUAL(WslcSetContainerSettingsPortMappings(&containerSettings5, &mapping, 1), E_INVALIDARG);
926+
}
828927
}
829928

830929
WSLC_TEST_METHOD(ContainerVolumeUnit)

0 commit comments

Comments
 (0)