From 094db1413b791b1edb780ba9cbcfece9762dc0e9 Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Thu, 14 May 2026 18:15:13 -0700 Subject: [PATCH 1/2] Support attaching and detaching containers to networks after creation --- src/windows/inc/docker_schema.h | 16 ++ src/windows/service/inc/wslc.idl | 2 + src/windows/wslcsession/DockerHTTPClient.cpp | 10 ++ src/windows/wslcsession/DockerHTTPClient.h | 2 + src/windows/wslcsession/WSLCContainer.h | 5 + src/windows/wslcsession/WSLCSession.cpp | 109 +++++++++++ src/windows/wslcsession/WSLCSession.h | 2 + test/windows/WSLCTests.cpp | 179 +++++++++++++++++++ 8 files changed, 325 insertions(+) diff --git a/src/windows/inc/docker_schema.h b/src/windows/inc/docker_schema.h index 916524b0e..9adab06f2 100644 --- a/src/windows/inc/docker_schema.h +++ b/src/windows/inc/docker_schema.h @@ -158,6 +158,22 @@ struct Network NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Network, Id, Name, Driver, Scope, Internal, IPAM, Labels); }; +struct ConnectNetworkRequest +{ + using TResponse = void; + std::string Container; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(ConnectNetworkRequest, Container); +}; + +struct DisconnectNetworkRequest +{ + using TResponse = void; + std::string Container; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(DisconnectNetworkRequest, Container); +}; + struct EmptyObject { }; diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 2ad3216f8..fca6b73b3 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -790,6 +790,8 @@ interface IWSLCSession : IUnknown HRESULT DeleteNetwork([in] LPCSTR Name); HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count); HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output); + HRESULT AttachContainerToNetwork([in] LPCSTR ContainerId, [in] const WSLCNetworkAttachment* Attachment); + HRESULT DetachContainerFromNetwork([in] LPCSTR ContainerId, [in] LPCSTR NetworkName); } // diff --git a/src/windows/wslcsession/DockerHTTPClient.cpp b/src/windows/wslcsession/DockerHTTPClient.cpp index 1900c7d76..d3e4daff6 100644 --- a/src/windows/wslcsession/DockerHTTPClient.cpp +++ b/src/windows/wslcsession/DockerHTTPClient.cpp @@ -469,6 +469,16 @@ void DockerHTTPClient::RemoveNetwork(const std::string& Name) Transaction(verb::delete_, URL::Create("/networks/{}", Name)); } +void DockerHTTPClient::ConnectContainerToNetwork(const std::string& NetworkName, const docker_schema::ConnectNetworkRequest& Request) +{ + Transaction(verb::post, URL::Create("/networks/{}/connect", NetworkName), Request); +} + +void DockerHTTPClient::DisconnectContainerFromNetwork(const std::string& NetworkName, const docker_schema::DisconnectNetworkRequest& Request) +{ + Transaction(verb::post, URL::Create("/networks/{}/disconnect", NetworkName), Request); +} + std::vector DockerHTTPClient::ListNetworks() { return Transaction>(verb::get, URL::Create("/networks")); diff --git a/src/windows/wslcsession/DockerHTTPClient.h b/src/windows/wslcsession/DockerHTTPClient.h index b9851f46c..54150487a 100644 --- a/src/windows/wslcsession/DockerHTTPClient.h +++ b/src/windows/wslcsession/DockerHTTPClient.h @@ -149,6 +149,8 @@ class DockerHTTPClient void RemoveNetwork(const std::string& Name); std::vector ListNetworks(); common::docker_schema::Network InspectNetwork(const std::string& Name); + void ConnectContainerToNetwork(const std::string& NetworkName, const common::docker_schema::ConnectNetworkRequest& Request); + void DisconnectContainerFromNetwork(const std::string& NetworkName, const common::docker_schema::DisconnectNetworkRequest& Request); // Image management. struct ListImagesFilters diff --git a/src/windows/wslcsession/WSLCContainer.h b/src/windows/wslcsession/WSLCContainer.h index 4ebfcd758..47be24269 100644 --- a/src/windows/wslcsession/WSLCContainer.h +++ b/src/windows/wslcsession/WSLCContainer.h @@ -125,6 +125,11 @@ class WSLCContainerImpl return m_containerFlags; } + WSLCContainerNetworkType NetworkMode() const noexcept + { + return m_networkingMode; + } + static std::unique_ptr Create( const WSLCContainerOptions& Options, const std::string& Name, diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index bc40f2925..7d4d2489c 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2295,6 +2295,115 @@ try } CATCH_RETURN(); +HRESULT WSLCSession::AttachContainerToNetwork(LPCSTR ContainerId, const WSLCNetworkAttachment* Attachment) +try +{ + COMServiceExecutionContext context; + + RETURN_HR_IF_NULL(E_POINTER, ContainerId); + RETURN_HR_IF_NULL(E_POINTER, Attachment); + + THROW_HR_WITH_USER_ERROR_IF(E_NOTIMPL, Localization::MessageWslcContainerIpAddressNotSupported(), Attachment->ContainerIpAddress != nullptr); + + THROW_HR_WITH_USER_ERROR_IF( + E_INVALIDARG, Localization::MessageWslcNetworkNameRequired(), !Attachment->NetworkName || strlen(Attachment->NetworkName) == 0); + + std::string containerId = ContainerId; + std::string networkName = Attachment->NetworkName; + ValidateName(containerId.c_str(), WSLC_MAX_CONTAINER_NAME_LENGTH); + ValidateName(networkName.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH); + + auto lock = m_lock.lock_shared(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine); + + std::scoped_lock locks(m_containersLock, m_networksLock); + std::erase_if(m_containers, [](const auto& entry) { return entry.second->State() == WslcContainerStateDeleted; }); + + auto containerIt = m_containers.find(containerId); + THROW_HR_WITH_USER_ERROR_IF( + WSLC_E_CONTAINER_NOT_FOUND, Localization::MessageWslcContainerNotFound(containerId), containerIt == m_containers.end()); + + auto networkIt = m_networks.find(networkName); + THROW_HR_WITH_USER_ERROR_IF( + WSLC_E_NETWORK_NOT_FOUND, Localization::MessageWslcNetworkNotFound(networkName), networkIt == m_networks.end()); + + const auto networkMode = containerIt->second->NetworkMode(); + THROW_HR_WITH_USER_ERROR_IF( + E_INVALIDARG, + Localization::MessageWslcAdditionalNetworksRequirePrimary(), + networkMode == WSLCContainerNetworkTypeHost || networkMode == WSLCContainerNetworkTypeNone); + + docker_schema::ConnectNetworkRequest request{}; + request.Container = containerId; + + try + { + m_dockerClient->ConnectContainerToNetwork(networkName, request); + } + catch (const DockerHTTPException& e) + { + THROW_DOCKER_USER_ERROR_MSG(e, "Failed to attach container '%hs' to network '%hs'", containerId.c_str(), networkName.c_str()); + } + + WSL_LOG( + "ContainerAttachedToNetwork", + TraceLoggingValue(containerId.c_str(), "ContainerId"), + TraceLoggingValue(networkName.c_str(), "NetworkName")); + + return S_OK; +} +CATCH_RETURN(); + +HRESULT WSLCSession::DetachContainerFromNetwork(LPCSTR ContainerId, LPCSTR NetworkName) +try +{ + COMServiceExecutionContext context; + + RETURN_HR_IF_NULL(E_POINTER, ContainerId); + RETURN_HR_IF_NULL(E_POINTER, NetworkName); + + std::string containerId = ContainerId; + std::string networkName = NetworkName; + ValidateName(containerId.c_str(), WSLC_MAX_CONTAINER_NAME_LENGTH); + ValidateName(networkName.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH); + + auto lock = m_lock.lock_shared(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine); + + std::scoped_lock locks(m_containersLock, m_networksLock); + std::erase_if(m_containers, [](const auto& entry) { return entry.second->State() == WslcContainerStateDeleted; }); + + auto containerIt = m_containers.find(containerId); + THROW_HR_WITH_USER_ERROR_IF( + WSLC_E_CONTAINER_NOT_FOUND, Localization::MessageWslcContainerNotFound(containerId), containerIt == m_containers.end()); + + auto networkIt = m_networks.find(networkName); + THROW_HR_WITH_USER_ERROR_IF( + WSLC_E_NETWORK_NOT_FOUND, Localization::MessageWslcNetworkNotFound(networkName), networkIt == m_networks.end()); + + docker_schema::DisconnectNetworkRequest request{}; + request.Container = containerId; + + try + { + m_dockerClient->DisconnectContainerFromNetwork(networkName, request); + } + catch (const DockerHTTPException& e) + { + THROW_DOCKER_USER_ERROR_MSG(e, "Failed to detach container '%hs' from network '%hs'", containerId.c_str(), networkName.c_str()); + } + + WSL_LOG( + "ContainerDetachedFromNetwork", + TraceLoggingValue(containerId.c_str(), "ContainerId"), + TraceLoggingValue(networkName.c_str(), "NetworkName")); + + return S_OK; +} +CATCH_RETURN(); + HRESULT WSLCSession::ListNetworks(WSLCNetworkInformation** Networks, ULONG* Count) try { diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index 572e721c8..a482b9b04 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -138,6 +138,8 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession IFACEMETHOD(DeleteNetwork)(_In_ LPCSTR Name) override; IFACEMETHOD(ListNetworks)(_Out_ WSLCNetworkInformation** Networks, _Out_ ULONG* Count) override; IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override; + IFACEMETHOD(AttachContainerToNetwork)(_In_ LPCSTR ContainerId, _In_ const WSLCNetworkAttachment* Attachment) override; + IFACEMETHOD(DetachContainerFromNetwork)(_In_ LPCSTR ContainerId, _In_ LPCSTR NetworkName) override; IFACEMETHOD(Terminate()) override; diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index cf7511e57..c914c484b 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -6248,6 +6248,185 @@ class WSLCTests ValidateCOMErrorMessage(L"Container network name is required for custom network type."); } + WSLC_TEST_METHOD(AttachDetachContainerNetworkRoundTripTest) + { + const std::string networkName = "test-attach-detach-net"; + const std::string containerName = "test-attach-detach-ctr"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCDriverOption opts[] = {{"Subnet", "172.50.0.0/16"}}; + WSLCNetworkOptions netOpts{}; + netOpts.Name = networkName.c_str(); + netOpts.Driver = "bridge"; + netOpts.DriverOpts = opts; + netOpts.DriverOptsCount = ARRAYSIZE(opts); + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts)); + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); }); + + WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}, WSLCContainerNetworkType::WSLCContainerNetworkTypeBridged); + auto container = launcher.Launch(*m_defaultSession); + auto containerId = container.Id(); + + WSLCNetworkAttachment attachment{}; + attachment.NetworkName = networkName.c_str(); + VERIFY_SUCCEEDED(m_defaultSession->AttachContainerToNetwork(containerId.c_str(), &attachment)); + + auto inspect = container.Inspect(); + VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName)); + VERIFY_IS_FALSE(inspect.NetworkSettings.Networks.at(networkName).IPAddress.empty()); + + VERIFY_SUCCEEDED(m_defaultSession->DetachContainerFromNetwork(containerId.c_str(), networkName.c_str())); + + auto inspectAfter = container.Inspect(); + VERIFY_IS_FALSE(inspectAfter.NetworkSettings.Networks.contains(networkName)); + } + + WSLC_TEST_METHOD(AttachContainerEmptyNetworkNameTest) + { + const std::string containerName = "test-attach-empty-net"; + + WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}); + auto container = launcher.Launch(*m_defaultSession); + auto containerId = container.Id(); + + WSLCNetworkAttachment attachment{}; + attachment.NetworkName = ""; + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->AttachContainerToNetwork(containerId.c_str(), &attachment)); + ValidateCOMErrorMessage(L"Network name cannot be empty."); + } + + WSLC_TEST_METHOD(AttachContainerNonexistentNetworkTest) + { + const std::string networkName = "nonexistent-attach-net"; + const std::string containerName = "test-attach-nonet"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}); + auto container = launcher.Launch(*m_defaultSession); + auto containerId = container.Id(); + + WSLCNetworkAttachment attachment{}; + attachment.NetworkName = networkName.c_str(); + VERIFY_ARE_EQUAL(WSLC_E_NETWORK_NOT_FOUND, m_defaultSession->AttachContainerToNetwork(containerId.c_str(), &attachment)); + ValidateCOMErrorMessage(std::format(L"Network not found: '{}'", std::wstring(networkName.begin(), networkName.end())).c_str()); + } + + WSLC_TEST_METHOD(AttachHostOrNoneModeContainerRejectedTest) + { + const std::string networkName = "test-attach-mode-net"; + const std::string hostContainerName = "test-attach-host-ctr"; + const std::string noneContainerName = "test-attach-none-ctr"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCDriverOption opts[] = {{"Subnet", "172.52.0.0/16"}}; + WSLCNetworkOptions netOpts{}; + netOpts.Name = networkName.c_str(); + netOpts.Driver = "bridge"; + netOpts.DriverOpts = opts; + netOpts.DriverOptsCount = ARRAYSIZE(opts); + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts)); + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); }); + + { + WSLCContainerLauncher launcher( + "debian:latest", hostContainerName, {"sleep", "99999"}, {}, WSLCContainerNetworkType::WSLCContainerNetworkTypeHost); + auto container = launcher.Launch(*m_defaultSession); + auto containerId = container.Id(); + + WSLCNetworkAttachment attachment{}; + attachment.NetworkName = networkName.c_str(); + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->AttachContainerToNetwork(containerId.c_str(), &attachment)); + ValidateCOMErrorMessage(L"Additional networks are not allowed when the primary network mode is 'host' or 'none'."); + } + + { + WSLCContainerLauncher launcher( + "debian:latest", noneContainerName, {"sleep", "99999"}, {}, WSLCContainerNetworkType::WSLCContainerNetworkTypeNone); + auto container = launcher.Launch(*m_defaultSession); + auto containerId = container.Id(); + + WSLCNetworkAttachment attachment{}; + attachment.NetworkName = networkName.c_str(); + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->AttachContainerToNetwork(containerId.c_str(), &attachment)); + ValidateCOMErrorMessage(L"Additional networks are not allowed when the primary network mode is 'host' or 'none'."); + } + } + + WSLC_TEST_METHOD(AttachContainerWithIpAddressRejectedTest) + { + const std::string containerName = "test-attach-ip-ctr"; + + WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}); + auto container = launcher.Launch(*m_defaultSession); + auto containerId = container.Id(); + + WSLCNetworkAttachment attachment{}; + attachment.NetworkName = "bridge"; + attachment.ContainerIpAddress = "10.0.0.5"; + VERIFY_ARE_EQUAL(E_NOTIMPL, m_defaultSession->AttachContainerToNetwork(containerId.c_str(), &attachment)); + ValidateCOMErrorMessage(L"ContainerIpAddress is not yet supported."); + } + + WSLC_TEST_METHOD(AttachNonexistentContainerToNetworkTest) + { + const std::string networkName = "test-attach-noctr-net"; + const std::string containerId = "nonexistent-container-id"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCDriverOption opts[] = {{"Subnet", "172.56.0.0/16"}}; + WSLCNetworkOptions netOpts{}; + netOpts.Name = networkName.c_str(); + netOpts.Driver = "bridge"; + netOpts.DriverOpts = opts; + netOpts.DriverOptsCount = ARRAYSIZE(opts); + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts)); + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); }); + + WSLCNetworkAttachment attachment{}; + attachment.NetworkName = networkName.c_str(); + VERIFY_ARE_EQUAL(WSLC_E_CONTAINER_NOT_FOUND, m_defaultSession->AttachContainerToNetwork(containerId.c_str(), &attachment)); + ValidateCOMErrorMessage(std::format(L"Container '{}' not found.", std::wstring(containerId.begin(), containerId.end())).c_str()); + } + + WSLC_TEST_METHOD(DetachContainerNonexistentNetworkTest) + { + const std::string networkName = "nonexistent-detach-net"; + const std::string containerName = "test-detach-nonet"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}); + auto container = launcher.Launch(*m_defaultSession); + auto containerId = container.Id(); + + VERIFY_ARE_EQUAL(WSLC_E_NETWORK_NOT_FOUND, m_defaultSession->DetachContainerFromNetwork(containerId.c_str(), networkName.c_str())); + ValidateCOMErrorMessage(std::format(L"Network not found: '{}'", std::wstring(networkName.begin(), networkName.end())).c_str()); + } + + WSLC_TEST_METHOD(DetachNonexistentContainerFromNetworkTest) + { + const std::string networkName = "test-detach-noctr-net"; + const std::string containerId = "nonexistent-container-id"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCDriverOption opts[] = {{"Subnet", "172.55.0.0/16"}}; + WSLCNetworkOptions netOpts{}; + netOpts.Name = networkName.c_str(); + netOpts.Driver = "bridge"; + netOpts.DriverOpts = opts; + netOpts.DriverOptsCount = ARRAYSIZE(opts); + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts)); + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); }); + + VERIFY_ARE_EQUAL(WSLC_E_CONTAINER_NOT_FOUND, m_defaultSession->DetachContainerFromNetwork(containerId.c_str(), networkName.c_str())); + ValidateCOMErrorMessage(std::format(L"Container '{}' not found.", std::wstring(containerId.begin(), containerId.end())).c_str()); + } + WSLC_TEST_METHOD(ContainerInspect) { // Helper to verify port mappings. From c7592e8688ea0ec2852332903578be4ba6e533c4 Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Mon, 18 May 2026 13:25:24 -0700 Subject: [PATCH 2/2] Move AttachToNetwork/DetachFromNetwork to IWSLCContainer --- src/windows/service/inc/wslc.idl | 4 +- src/windows/wslcsession/WSLCContainer.cpp | 78 ++++++++++++++++ src/windows/wslcsession/WSLCContainer.h | 4 + src/windows/wslcsession/WSLCSession.cpp | 109 ---------------------- src/windows/wslcsession/WSLCSession.h | 2 - test/windows/WSLCTests.cpp | 11 +++ 6 files changed, 95 insertions(+), 113 deletions(-) diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index fca6b73b3..9ec0e6663 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -536,6 +536,8 @@ interface IWSLCContainer : IUnknown HRESULT GetLabels([out, size_is(, *Count)] WSLCLabelInformation** Labels, [out] ULONG* Count); HRESULT Kill([in] WSLCSignal Signal); HRESULT Stats([out] LPSTR* Output); + HRESULT AttachToNetwork([in] const WSLCNetworkAttachment* Attachment); + HRESULT DetachFromNetwork([in] LPCSTR NetworkName); } typedef enum _WSLCDeletedImageType @@ -790,8 +792,6 @@ interface IWSLCSession : IUnknown HRESULT DeleteNetwork([in] LPCSTR Name); HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count); HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output); - HRESULT AttachContainerToNetwork([in] LPCSTR ContainerId, [in] const WSLCNetworkAttachment* Attachment); - HRESULT DetachContainerFromNetwork([in] LPCSTR ContainerId, [in] LPCSTR NetworkName); } // diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 3afa495dc..a054482ff 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -2348,6 +2348,66 @@ void WSLCContainerImpl::GetLabels(WSLCLabelInformation** Labels, ULONG* Count) c *Labels = labelsArray.release(); } +void WSLCContainerImpl::AttachToNetwork(const WSLCNetworkAttachment* Attachment) +{ + THROW_HR_WITH_USER_ERROR_IF(E_NOTIMPL, Localization::MessageWslcContainerIpAddressNotSupported(), Attachment->ContainerIpAddress != nullptr); + + THROW_HR_WITH_USER_ERROR_IF( + E_INVALIDARG, Localization::MessageWslcNetworkNameRequired(), !Attachment->NetworkName || strlen(Attachment->NetworkName) == 0); + + const std::string networkName = Attachment->NetworkName; + + auto lock = m_lock.lock_shared(); + + THROW_HR_WITH_USER_ERROR_IF( + E_INVALIDARG, + Localization::MessageWslcAdditionalNetworksRequirePrimary(), + m_networkingMode == WSLCContainerNetworkTypeHost || m_networkingMode == WSLCContainerNetworkTypeNone); + + common::docker_schema::ConnectNetworkRequest request{}; + request.Container = m_id; + + try + { + m_dockerClient.ConnectContainerToNetwork(networkName, request); + } + catch (const DockerHTTPException& e) + { + THROW_DOCKER_USER_ERROR_MSG(e, "Failed to attach container '%hs' to network '%hs'", m_id.c_str(), networkName.c_str()); + } + + WSL_LOG( + "ContainerAttachedToNetwork", + TraceLoggingValue(m_id.c_str(), "ContainerId"), + TraceLoggingValue(networkName.c_str(), "NetworkName")); +} + +void WSLCContainerImpl::DetachFromNetwork(LPCSTR NetworkName) +{ + THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcNetworkNameRequired(), strlen(NetworkName) == 0); + + const std::string networkName = NetworkName; + + auto lock = m_lock.lock_shared(); + + common::docker_schema::DisconnectNetworkRequest request{}; + request.Container = m_id; + + try + { + m_dockerClient.DisconnectContainerFromNetwork(networkName, request); + } + catch (const DockerHTTPException& e) + { + THROW_DOCKER_USER_ERROR_MSG(e, "Failed to detach container '%hs' from network '%hs'", m_id.c_str(), networkName.c_str()); + } + + WSL_LOG( + "ContainerDetachedFromNetwork", + TraceLoggingValue(m_id.c_str(), "ContainerId"), + TraceLoggingValue(networkName.c_str(), "NetworkName")); +} + HRESULT WSLCContainer::GetLabels(WSLCLabelInformation** Labels, ULONG* Count) try { @@ -2361,6 +2421,24 @@ try } CATCH_RETURN(); +HRESULT WSLCContainer::AttachToNetwork(const WSLCNetworkAttachment* Attachment) +try +{ + COMServiceExecutionContext context; + RETURN_HR_IF(E_POINTER, Attachment == nullptr); + return CallImpl(&WSLCContainerImpl::AttachToNetwork, Attachment); +} +CATCH_RETURN(); + +HRESULT WSLCContainer::DetachFromNetwork(LPCSTR NetworkName) +try +{ + COMServiceExecutionContext context; + RETURN_HR_IF(E_POINTER, NetworkName == nullptr); + return CallImpl(&WSLCContainerImpl::DetachFromNetwork, NetworkName); +} +CATCH_RETURN(); + HRESULT WSLCContainer::InterfaceSupportsErrorInfo(REFIID riid) { return riid == __uuidof(IWSLCContainer) ? S_OK : S_FALSE; diff --git a/src/windows/wslcsession/WSLCContainer.h b/src/windows/wslcsession/WSLCContainer.h index 47be24269..5b214777b 100644 --- a/src/windows/wslcsession/WSLCContainer.h +++ b/src/windows/wslcsession/WSLCContainer.h @@ -104,6 +104,8 @@ class WSLCContainerImpl void Logs(WSLCLogsFlags Flags, WSLCHandle* Stdout, WSLCHandle* Stderr, ULONGLONG Since, ULONGLONG Until, ULONGLONG Tail) const; void Stats(LPSTR* Output) const; void GetLabels(WSLCLabelInformation** Labels, ULONG* Count) const; + void AttachToNetwork(const WSLCNetworkAttachment* Attachment); + void DetachFromNetwork(LPCSTR NetworkName); void CopyTo(IWSLCContainer** Container) const; @@ -236,6 +238,8 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLCContainer IFACEMETHOD(GetName)(_Out_ LPSTR* Name) override; IFACEMETHOD(GetLabels)(_Out_ WSLCLabelInformation** Labels, _Out_ ULONG* Count) override; IFACEMETHOD(Stats)(_Out_ LPSTR* Output) override; + IFACEMETHOD(AttachToNetwork)(_In_ const WSLCNetworkAttachment* Attachment) override; + IFACEMETHOD(DetachFromNetwork)(_In_ LPCSTR NetworkName) override; IFACEMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 7d4d2489c..bc40f2925 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2295,115 +2295,6 @@ try } CATCH_RETURN(); -HRESULT WSLCSession::AttachContainerToNetwork(LPCSTR ContainerId, const WSLCNetworkAttachment* Attachment) -try -{ - COMServiceExecutionContext context; - - RETURN_HR_IF_NULL(E_POINTER, ContainerId); - RETURN_HR_IF_NULL(E_POINTER, Attachment); - - THROW_HR_WITH_USER_ERROR_IF(E_NOTIMPL, Localization::MessageWslcContainerIpAddressNotSupported(), Attachment->ContainerIpAddress != nullptr); - - THROW_HR_WITH_USER_ERROR_IF( - E_INVALIDARG, Localization::MessageWslcNetworkNameRequired(), !Attachment->NetworkName || strlen(Attachment->NetworkName) == 0); - - std::string containerId = ContainerId; - std::string networkName = Attachment->NetworkName; - ValidateName(containerId.c_str(), WSLC_MAX_CONTAINER_NAME_LENGTH); - ValidateName(networkName.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH); - - auto lock = m_lock.lock_shared(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine); - - std::scoped_lock locks(m_containersLock, m_networksLock); - std::erase_if(m_containers, [](const auto& entry) { return entry.second->State() == WslcContainerStateDeleted; }); - - auto containerIt = m_containers.find(containerId); - THROW_HR_WITH_USER_ERROR_IF( - WSLC_E_CONTAINER_NOT_FOUND, Localization::MessageWslcContainerNotFound(containerId), containerIt == m_containers.end()); - - auto networkIt = m_networks.find(networkName); - THROW_HR_WITH_USER_ERROR_IF( - WSLC_E_NETWORK_NOT_FOUND, Localization::MessageWslcNetworkNotFound(networkName), networkIt == m_networks.end()); - - const auto networkMode = containerIt->second->NetworkMode(); - THROW_HR_WITH_USER_ERROR_IF( - E_INVALIDARG, - Localization::MessageWslcAdditionalNetworksRequirePrimary(), - networkMode == WSLCContainerNetworkTypeHost || networkMode == WSLCContainerNetworkTypeNone); - - docker_schema::ConnectNetworkRequest request{}; - request.Container = containerId; - - try - { - m_dockerClient->ConnectContainerToNetwork(networkName, request); - } - catch (const DockerHTTPException& e) - { - THROW_DOCKER_USER_ERROR_MSG(e, "Failed to attach container '%hs' to network '%hs'", containerId.c_str(), networkName.c_str()); - } - - WSL_LOG( - "ContainerAttachedToNetwork", - TraceLoggingValue(containerId.c_str(), "ContainerId"), - TraceLoggingValue(networkName.c_str(), "NetworkName")); - - return S_OK; -} -CATCH_RETURN(); - -HRESULT WSLCSession::DetachContainerFromNetwork(LPCSTR ContainerId, LPCSTR NetworkName) -try -{ - COMServiceExecutionContext context; - - RETURN_HR_IF_NULL(E_POINTER, ContainerId); - RETURN_HR_IF_NULL(E_POINTER, NetworkName); - - std::string containerId = ContainerId; - std::string networkName = NetworkName; - ValidateName(containerId.c_str(), WSLC_MAX_CONTAINER_NAME_LENGTH); - ValidateName(networkName.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH); - - auto lock = m_lock.lock_shared(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine); - - std::scoped_lock locks(m_containersLock, m_networksLock); - std::erase_if(m_containers, [](const auto& entry) { return entry.second->State() == WslcContainerStateDeleted; }); - - auto containerIt = m_containers.find(containerId); - THROW_HR_WITH_USER_ERROR_IF( - WSLC_E_CONTAINER_NOT_FOUND, Localization::MessageWslcContainerNotFound(containerId), containerIt == m_containers.end()); - - auto networkIt = m_networks.find(networkName); - THROW_HR_WITH_USER_ERROR_IF( - WSLC_E_NETWORK_NOT_FOUND, Localization::MessageWslcNetworkNotFound(networkName), networkIt == m_networks.end()); - - docker_schema::DisconnectNetworkRequest request{}; - request.Container = containerId; - - try - { - m_dockerClient->DisconnectContainerFromNetwork(networkName, request); - } - catch (const DockerHTTPException& e) - { - THROW_DOCKER_USER_ERROR_MSG(e, "Failed to detach container '%hs' from network '%hs'", containerId.c_str(), networkName.c_str()); - } - - WSL_LOG( - "ContainerDetachedFromNetwork", - TraceLoggingValue(containerId.c_str(), "ContainerId"), - TraceLoggingValue(networkName.c_str(), "NetworkName")); - - return S_OK; -} -CATCH_RETURN(); - HRESULT WSLCSession::ListNetworks(WSLCNetworkInformation** Networks, ULONG* Count) try { diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index a482b9b04..572e721c8 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -138,8 +138,6 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession IFACEMETHOD(DeleteNetwork)(_In_ LPCSTR Name) override; IFACEMETHOD(ListNetworks)(_Out_ WSLCNetworkInformation** Networks, _Out_ ULONG* Count) override; IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override; - IFACEMETHOD(AttachContainerToNetwork)(_In_ LPCSTR ContainerId, _In_ const WSLCNetworkAttachment* Attachment) override; - IFACEMETHOD(DetachContainerFromNetwork)(_In_ LPCSTR ContainerId, _In_ LPCSTR NetworkName) override; IFACEMETHOD(Terminate()) override; diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 733248de9..d9324e5cf 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -6294,6 +6294,17 @@ class WSLCTests ValidateCOMErrorMessage(L"Network name cannot be empty."); } + WSLC_TEST_METHOD(DetachContainerEmptyNetworkNameTest) + { + const std::string containerName = "test-detach-empty-net"; + + WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}); + auto container = launcher.Launch(*m_defaultSession); + + VERIFY_ARE_EQUAL(E_INVALIDARG, container.Get().DetachFromNetwork("")); + ValidateCOMErrorMessage(L"Network name cannot be empty."); + } + WSLC_TEST_METHOD(AttachHostOrNoneModeContainerRejectedTest) { const std::string networkName = "test-attach-mode-net";