From 63dda12225ed15bc5eb21807cbf018035a64f6e6 Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 19 Jun 2026 14:27:19 -0700 Subject: [PATCH 1/4] Support arbitrary network driver options in wslc create/inspect/recovery --- src/windows/inc/docker_schema.h | 6 ++- src/windows/inc/wslc_schema.h | 3 +- src/windows/wslcsession/WSLCNetworkMetadata.h | 1 + src/windows/wslcsession/WSLCSession.cpp | 38 ++++++++++++-- test/windows/WSLCTests.cpp | 49 +++++++++++++++++-- 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/windows/inc/docker_schema.h b/src/windows/inc/docker_schema.h index a7e998bc6d..13c354a068 100644 --- a/src/windows/inc/docker_schema.h +++ b/src/windows/inc/docker_schema.h @@ -146,9 +146,10 @@ struct CreateNetwork std::string Driver; bool Internal{}; std::optional IPAM; + std::optional> Options; std::map Labels; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(CreateNetwork, Name, Driver, Internal, IPAM, Labels); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(CreateNetwork, Name, Driver, Internal, IPAM, Options, Labels); }; struct Network @@ -159,9 +160,10 @@ struct Network std::string Scope; bool Internal{}; IPAM IPAM; + std::optional> Options; std::map Labels; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Network, Id, Name, Driver, Scope, Internal, IPAM, Labels); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Network, Id, Name, Driver, Scope, Internal, IPAM, Options, Labels); }; struct ContainerNetworkRequest diff --git a/src/windows/inc/wslc_schema.h b/src/windows/inc/wslc_schema.h index 417efb4731..0913206c16 100644 --- a/src/windows/inc/wslc_schema.h +++ b/src/windows/inc/wslc_schema.h @@ -195,9 +195,10 @@ struct Network std::string Scope; bool Internal{}; IPAM IPAM; + std::optional> Options; std::map Labels; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Network, Id, Name, Driver, Scope, Internal, IPAM, Labels); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Network, Id, Name, Driver, Scope, Internal, IPAM, Options, Labels); }; } // namespace wsl::windows::common::wslc_schema diff --git a/src/windows/wslcsession/WSLCNetworkMetadata.h b/src/windows/wslcsession/WSLCNetworkMetadata.h index 005b0535c9..a2f7387636 100644 --- a/src/windows/wslcsession/WSLCNetworkMetadata.h +++ b/src/windows/wslcsession/WSLCNetworkMetadata.h @@ -45,6 +45,7 @@ struct NetworkEntry std::string Scope; bool Internal{false}; std::map Labels; + std::map Options; NetworkIPAM IPAM; }; diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 4c9a218fa9..45efec55bd 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2363,12 +2363,16 @@ try auto driverOpts = wslutil::ParseKeyValuePairs(Options->DriverOpts, Options->DriverOptsCount); auto labels = wslutil::ParseKeyValuePairs(Options->Labels, Options->LabelsCount, WSLCNetworkManagedLabel); - static constexpr std::array c_supportedDriverOpts{"Internal", "Subnet", "Gateway"}; + // Reject case-mismatches of reserved driver-option keys. + static constexpr std::array c_reservedDriverOpts{"Internal", "Subnet", "Gateway"}; for (const auto& [key, _] : driverOpts) { - const bool supported = std::any_of( - c_supportedDriverOpts.begin(), c_supportedDriverOpts.end(), [&](std::string_view opt) { return key == opt; }); - THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcInvalidNetworkDriverOption(key), !supported); + const bool caseMismatch = std::any_of(c_reservedDriverOpts.begin(), c_reservedDriverOpts.end(), [&](std::string_view opt) { + return key != opt && key.size() == opt.size() && std::equal(key.begin(), key.end(), opt.begin(), [](char a, char b) { + return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); + }); + }); + THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcInvalidNetworkDriverOption(key), caseMismatch); } THROW_HR_WITH_USER_ERROR_IF( @@ -2408,6 +2412,20 @@ try ipam.Config.emplace().push_back(std::move(ipamConfig)); } + // Forward any non-reserved driver options to Docker. Reserved keys (Internal, Subnet, Gateway) + // are translated into Docker's typed network fields above and not echoed back here. + for (const auto& [key, value] : driverOpts) + { + if (std::none_of(c_reservedDriverOpts.begin(), c_reservedDriverOpts.end(), [&](std::string_view opt) { return key == opt; })) + { + if (!request.Options.has_value()) + { + request.Options.emplace(); + } + (*request.Options)[key] = value; + } + } + docker_schema::CreateNetworkResponse createResult; try { @@ -2445,6 +2463,10 @@ try entry.Scope = full.Scope; entry.Internal = full.Internal; entry.Labels = full.Labels; + if (full.Options) + { + entry.Options = *full.Options; + } entry.IPAM.Driver = full.IPAM.Driver; if (full.IPAM.Config) { @@ -2569,6 +2591,10 @@ try result.Scope = entry.Scope; result.Internal = entry.Internal; result.Labels = entry.Labels; + if (!entry.Options.empty()) + { + result.Options = entry.Options; + } result.IPAM.Driver = entry.IPAM.Driver; if (entry.IPAM.Config) @@ -3390,6 +3416,10 @@ void WSLCSession::RecoverExistingNetworks() entry.Scope = network.Scope; entry.Internal = network.Internal; entry.Labels = network.Labels; + if (network.Options) + { + entry.Options = *network.Options; + } entry.IPAM.Driver = network.IPAM.Driver; if (network.IPAM.Config) { diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 19c7b26d86..f665616b14 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -5102,10 +5102,11 @@ class WSLCTests } } - // Invalid driver options (wrong case and unknown keys) + // Reserved driver-option keys must be spelled exactly; case-mismatches are rejected + // so users don't silently fall through to opaque pass-through. { options.Driver = "bridge"; - for (const char* key : {"internal", "subnet", "gateway", "foo"}) + for (const char* key : {"internal", "subnet", "gateway"}) { WSLCDriverOption opt{key, "true"}; options.DriverOpts = &opt; @@ -5250,17 +5251,49 @@ class WSLCTests VERIFY_ARE_EQUAL(std::string("172.31.0.1"), inspect.IPAM.Config->at(0).Gateway); } + WSLC_TEST_METHOD(NetworkCreateWithArbitraryDriverOptsTest) + { + const std::string networkName = "arbitrary-opts-test-net"; + + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + + WSLCDriverOption opts[] = {{"my.abc.key", "mygod"}, {"com.example.flag", "1"}}; + + WSLCNetworkOptions options{}; + options.Name = networkName.c_str(); + options.Driver = "bridge"; + options.DriverOpts = opts; + options.DriverOptsCount = ARRAYSIZE(opts); + + auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); }); + + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr)); + + wil::unique_cotaskmem_ansistring output; + VERIFY_SUCCEEDED(m_defaultSession->InspectNetwork(networkName.c_str(), &output)); + VERIFY_IS_NOT_NULL(output.get()); + + auto inspect = wsl::shared::FromJson(output.get()); + VERIFY_IS_TRUE(inspect.Options.has_value()); + VERIFY_IS_TRUE(inspect.Options->contains("my.abc.key")); + VERIFY_IS_TRUE(inspect.Options->contains("com.example.flag")); + VERIFY_ARE_EQUAL(std::string("mygod"), inspect.Options->at("my.abc.key")); + VERIFY_ARE_EQUAL(std::string("1"), inspect.Options->at("com.example.flag")); + } + WSLC_TEST_METHOD(NetworkSessionRecoveryTest) { const std::string networkName = "recovery-test-net"; LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); + WSLCDriverOption recoveryOpts[] = {{"recovery.test.key", "preserved"}}; + WSLCNetworkOptions options{}; options.Name = networkName.c_str(); options.Driver = "bridge"; - options.DriverOpts = nullptr; - options.DriverOptsCount = 0; + options.DriverOpts = recoveryOpts; + options.DriverOptsCount = ARRAYSIZE(recoveryOpts); VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr)); auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); }); @@ -5274,6 +5307,14 @@ class WSLCTests VERIFY_ARE_EQUAL(networkName, std::string(networks[0].Name)); VERIFY_ARE_EQUAL(std::string("bridge"), std::string(networks[0].Driver)); VERIFY_IS_TRUE(strlen(networks[0].Id) > 0); + + // Verify arbitrary driver options survive session recovery. + wil::unique_cotaskmem_ansistring output; + VERIFY_SUCCEEDED(m_defaultSession->InspectNetwork(networkName.c_str(), &output)); + auto inspect = wsl::shared::FromJson(output.get()); + VERIFY_IS_TRUE(inspect.Options.has_value()); + VERIFY_IS_TRUE(inspect.Options->contains("recovery.test.key")); + VERIFY_ARE_EQUAL(std::string("preserved"), inspect.Options->at("recovery.test.key")); } WSLC_TEST_METHOD(NetworkMultipleCreateListDeleteTest) From 6a736030ab82323e4892ec3a5a1bf01c69d5104e Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 19 Jun 2026 15:00:01 -0700 Subject: [PATCH 2/4] Address Copilot feedback --- localization/strings/en-US/Resources.resw | 4 ++-- src/windows/wslcsession/WSLCSession.cpp | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 3ca27cf4ad..80efc27428 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2327,8 +2327,8 @@ For privacy information about this product please visit https://aka.ms/privacy.< {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated - Unsupported network driver option: '{}' - {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + Network driver option '{}' is case-sensitive. Use the exact casing: Internal, Subnet, or Gateway. + {FixedPlaceholder="{}"}{Locked="Internal"}{Locked="Subnet"}{Locked="Gateway"}Command line arguments, file names and string inserts should not be translated Network driver option 'Gateway' requires 'Subnet' to also be specified. diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 45efec55bd..2213dbc282 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2368,9 +2368,7 @@ try for (const auto& [key, _] : driverOpts) { const bool caseMismatch = std::any_of(c_reservedDriverOpts.begin(), c_reservedDriverOpts.end(), [&](std::string_view opt) { - return key != opt && key.size() == opt.size() && std::equal(key.begin(), key.end(), opt.begin(), [](char a, char b) { - return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); - }); + return key != opt && wsl::shared::string::IsEqual(key, opt, true); }); THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcInvalidNetworkDriverOption(key), caseMismatch); } From 6907acec86b689c139d1f83e98398f06aa10902b Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 19 Jun 2026 15:19:24 -0700 Subject: [PATCH 3/4] Refine network driver option validation test assertions --- test/windows/WSLCTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index f665616b14..78c59f6a9e 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -5111,7 +5111,7 @@ class WSLCTests WSLCDriverOption opt{key, "true"}; options.DriverOpts = &opt; options.DriverOptsCount = 1; - verifyInvalid(wsl::shared::string::MultiByteToWide(key).c_str()); + verifyInvalid(L"case-sensitive"); } } From 4c8124323111e765550accac9d6aba06f58194ba Mon Sep 17 00:00:00 2001 From: Beena Chauhan Date: Fri, 19 Jun 2026 15:34:31 -0700 Subject: [PATCH 4/4] Fix localization comment format --- localization/strings/en-US/Resources.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 80efc27428..d245cf90b1 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2328,7 +2328,7 @@ For privacy information about this product please visit https://aka.ms/privacy.< Network driver option '{}' is case-sensitive. Use the exact casing: Internal, Subnet, or Gateway. - {FixedPlaceholder="{}"}{Locked="Internal"}{Locked="Subnet"}{Locked="Gateway"}Command line arguments, file names and string inserts should not be translated + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated{Locked="Internal"}{Locked="Subnet"}{Locked="Gateway"} Network driver option 'Gateway' requires 'Subnet' to also be specified.