@samitouri / QOSAMI-WSL / commits / 052b4b1f

Add --ip-range option to network create (#41138)

beena352 committed Jul 27, 2026 at 12:36 UTC 052b4b1f799d7efb0b58f2d773bc75b864387a0d
13 files changed +156 -5
localization/strings/en-US/Resources.resw
+8
@@ -2363,6 +2363,10 @@ For privacy information about this product please visit https://aka.ms/privacy.<
2363 <value>The '--gateway' option requires '--subnet' to also be specified.</value>
2364 <comment>{Locked="--gateway'"}{Locked="--subnet'"}Command line arguments, file names and string inserts should not be translated</comment>
2365 </data>
2366 + <data name="MessageWslcIpRangeRequiresSubnet" xml:space="preserve">
2367 + <value>The '--ip-range' option requires '--subnet' to also be specified.</value>
2368 + <comment>{Locked="--ip-range'"}{Locked="--subnet'"}Command line arguments, file names and string inserts should not be translated</comment>
2369 + </data>
2370 <data name = "MessageWslcNetworkInUse" xml:space = "preserve" >
2371 <value>Network '{}' has active endpoints.</value>
2372 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
@@ -3285,6 +3289,10 @@ On first run, creates the file with all settings commented out at their defaults
3289 <value>IPv4 or IPv6 gateway for the subnet</value>
3290 <comment>{Locked="IPv4"}{Locked="IPv6"}Command line arguments, file names and string inserts should not be translated</comment>
3291 </data>
3292 + <data name="WSLCCLI_NetworkIpRangeArgDescription" xml:space="preserve">
3293 + <value>Allocate container IP addresses from a sub-range in CIDR format</value>
3294 + <comment>{Locked="CIDR"}Command line arguments, file names and string inserts should not be translated</comment>
3295 + </data>
3296 <data name="WSLCCLI_NetworkRemoveDesc" xml:space="preserve">
3297 <value>Remove one or more networks.</value>
3298 </data>
src/windows/inc/docker_schema.h
+2 -1
@@ -118,8 +118,9 @@ struct IPAMConfig
118 {
119 std::string Subnet;
120 std::string Gateway;
121 + std::string IPRange;
122
122 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(IPAMConfig, Subnet, Gateway);
123 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(IPAMConfig, Subnet, Gateway, IPRange);
124 };
125
126 struct IPAM
src/windows/inc/wslc_schema.h
+2 -1
@@ -218,8 +218,9 @@ struct IPAMConfig
218 {
219 std::string Subnet;
220 std::string Gateway;
221 + std::string IPRange;
222
222 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(IPAMConfig, Subnet, Gateway);
223 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(IPAMConfig, Subnet, Gateway, IPRange);
224 };
225
226 struct IPAM
src/windows/service/inc/wslc.idl
+1
@@ -564,6 +564,7 @@ typedef struct _WSLCNetworkOptions
564 BOOL Internal;
565 [unique] LPCSTR Subnet;
566 [unique] LPCSTR Gateway;
567 + [unique] LPCSTR IpRange;
568 } WSLCNetworkOptions;
569
570 typedef char WSLCNetworkName[WSLC_MAX_NETWORK_NAME_LENGTH + 1];
src/windows/wslc/arguments/ArgumentDefinitions.h
+1
@@ -80,6 +80,7 @@ _(Input, "input", L"i", Kind::Value, L
80 _(Interactive, "interactive", L"i", Kind::Flag, Localization::WSLCCLI_InteractiveArgDescription()) \
81 _(Internal, "internal", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NetworkInternalArgDescription()) \
82 _(IpAddress, "ip", NO_ALIAS, Kind::Value, Localization::WSLCCLI_IpAddressArgDescription()) \
83 +_(IpRange, "ip-range", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkIpRangeArgDescription()) \
84 _(Label, "label", L"l", Kind::Value, Localization::WSLCCLI_LabelArgDescription()) \
85 _(Last, "last", L"n", Kind::Value, Localization::WSLCCLI_LastArgDescription()) \
86 _(Latest, "latest", L"l", Kind::Flag, Localization::WSLCCLI_LatestArgDescription()) \
src/windows/wslc/commands/NetworkCreateCommand.cpp
+1
@@ -33,6 +33,7 @@ std::vector<Argument> NetworkCreateCommand::GetArguments() const
33 Argument::Create(ArgType::Label, false, Limit::Unlimited, Localization::WSLCCLI_NetworkLabelArgDescription()),
34 Argument::Create(ArgType::Gateway),
35 Argument::Create(ArgType::Internal),
36 + Argument::Create(ArgType::IpRange),
37 Argument::Create(ArgType::Subnet),
38 };
39 }
src/windows/wslc/services/NetworkModel.h
+1
@@ -28,6 +28,7 @@ struct CreateNetworkOptions
28 bool Internal{false};
29 std::optional<std::string> Subnet;
30 std::optional<std::string> Gateway;
31 + std::optional<std::string> IpRange;
32 };
33
34 struct NetworkEndpointOptions
src/windows/wslc/services/NetworkService.cpp
+5
@@ -62,6 +62,11 @@ void NetworkService::Create(Reporter& reporter, models::Session& session, const
62 options.Gateway = createOptions.Gateway->c_str();
63 }
64
65 + if (createOptions.IpRange.has_value())
66 + {
67 + options.IpRange = createOptions.IpRange->c_str();
68 + }
69 +
70 THROW_IF_FAILED(session.Get()->CreateNetwork(&options, &warningCallback));
71 }
72
src/windows/wslc/tasks/NetworkTasks.cpp
+5
@@ -107,6 +107,11 @@ void CreateNetwork(CLIExecutionContext& context)
107 options.Gateway = WideToMultiByte(context.Args.Get<ArgType::Gateway>());
108 }
109
110 + if (context.Args.Contains(ArgType::IpRange))
111 + {
112 + options.IpRange = WideToMultiByte(context.Args.Get<ArgType::IpRange>());
113 + }
114 +
115 NetworkService::Create(context.Reporter, context.Data.Get<Data::Session>(), options);
116 context.Reporter.Output(L"{}\n", MultiByteToWide(options.Name));
117 }
src/windows/wslcsession/WSLCNetworkMetadata.h
+1
@@ -30,6 +30,7 @@ struct NetworkIPAMConfig
30 {
31 std::string Subnet;
32 std::string Gateway;
33 + std::string IPRange;
34 };
35
36 struct NetworkIPAM
src/windows/wslcsession/WSLCSession.cpp
+11 -2
@@ -2513,6 +2513,9 @@ try
2513 THROW_HR_WITH_USER_ERROR_IF(
2514 E_INVALIDARG, Localization::MessageWslcGatewayRequiresSubnet(), Options->Gateway != nullptr && Options->Subnet == nullptr);
2515
2516 + THROW_HR_WITH_USER_ERROR_IF(
2517 + E_INVALIDARG, Localization::MessageWslcIpRangeRequiresSubnet(), Options->IpRange != nullptr && Options->Subnet == nullptr);
2518 +
2519 if (Options->Subnet != nullptr)
2520 {
2521 docker_schema::IPAMConfig ipamConfig;
@@ -2523,6 +2526,11 @@ try
2526 ipamConfig.Gateway = Options->Gateway;
2527 }
2528
2529 + if (Options->IpRange != nullptr)
2530 + {
2531 + ipamConfig.IPRange = Options->IpRange;
2532 + }
2533 +
2534 auto& ipam = request.IPAM.emplace();
2535 ipam.Driver = "default";
2536 ipam.Config.emplace().push_back(std::move(ipamConfig));
@@ -2580,7 +2588,7 @@ try
2588 auto& cfgs = entry.IPAM.Config.emplace();
2589 for (const auto& c : *full.IPAM.Config)
2590 {
2583 - cfgs.push_back({c.Subnet, c.Gateway});
2591 + cfgs.push_back({c.Subnet, c.Gateway, c.IPRange});
2592 }
2593 }
2594
@@ -2712,6 +2720,7 @@ try
2720 wslc_schema::IPAMConfig inspectCfg;
2721 inspectCfg.Subnet = cfg.Subnet;
2722 inspectCfg.Gateway = cfg.Gateway;
2723 + inspectCfg.IPRange = cfg.IPRange;
2724 configs.push_back(std::move(inspectCfg));
2725 }
2726 }
@@ -3537,7 +3546,7 @@ void WSLCSession::RecoverExistingNetworks()
3546 auto& cfgs = entry.IPAM.Config.emplace();
3547 for (const auto& c : *network.IPAM.Config)
3548 {
3540 - cfgs.push_back({c.Subnet, c.Gateway});
3549 + cfgs.push_back({c.Subnet, c.Gateway, c.IPRange});
3550 }
3551 }
3552
test/windows/WSLCTests.cpp
+90 -1
@@ -5610,7 +5610,16 @@ class WSLCTests
5610 options.Driver = "bridge";
5611 options.Subnet = nullptr;
5612 options.Gateway = "172.44.0.1";
5613 - verifyInvalid(L"--subnet");
5613 + verifyInvalid(L"--gateway");
5614 + }
5615 +
5616 + // IpRange specified without Subnet
5617 + {
5618 + options.Driver = "bridge";
5619 + options.Subnet = nullptr;
5620 + options.Gateway = nullptr;
5621 + options.IpRange = "172.44.10.0/24";
5622 + verifyInvalid(L"--ip-range");
5623 }
5624 }
5625
@@ -5737,6 +5746,86 @@ class WSLCTests
5746 VERIFY_ARE_EQUAL(gateway, inspect.IPAM.Config->at(0).Gateway);
5747 }
5748
5749 + WSLC_TEST_METHOD(NetworkCreateWithIpRangeTest)
5750 + {
5751 + const std::string networkName = "ip-range-test-net";
5752 + const std::string subnet = "172.32.0.0/16";
5753 + const std::string ipRange = "172.32.10.0/24";
5754 +
5755 + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str()));
5756 +
5757 + WSLCNetworkOptions options{};
5758 + options.Name = networkName.c_str();
5759 + options.Driver = "bridge";
5760 + options.Subnet = subnet.c_str();
5761 + options.IpRange = ipRange.c_str();
5762 +
5763 + auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
5764 +
5765 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
5766 +
5767 + wil::unique_cotaskmem_ansistring output;
5768 + VERIFY_SUCCEEDED(m_defaultSession->InspectNetwork(networkName.c_str(), &output));
5769 + VERIFY_IS_NOT_NULL(output.get());
5770 +
5771 + auto inspect = wsl::shared::FromJson<wsl::windows::common::wslc_schema::Network>(output.get());
5772 + VERIFY_IS_TRUE(inspect.IPAM.Config.has_value());
5773 + VERIFY_ARE_EQUAL(1u, inspect.IPAM.Config->size());
5774 + VERIFY_ARE_EQUAL(subnet, inspect.IPAM.Config->at(0).Subnet);
5775 + VERIFY_ARE_EQUAL(ipRange, inspect.IPAM.Config->at(0).IPRange);
5776 + }
5777 +
5778 + WSLC_TEST_METHOD(NetworkCreateInvalidIpRangeTest)
5779 + {
5780 + const std::string networkName = "bad-ip-range-net";
5781 + const std::string subnet = "172.33.0.0/16";
5782 +
5783 + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str()));
5784 + auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
5785 +
5786 + WSLCNetworkOptions options{};
5787 + options.Name = networkName.c_str();
5788 + options.Driver = "bridge";
5789 + options.Subnet = subnet.c_str();
5790 + options.IpRange = "10.0.0.0/24";
5791 +
5792 + VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateNetwork(&options, nullptr));
5793 +
5794 + wil::unique_cotaskmem_ansistring output;
5795 + VERIFY_ARE_EQUAL(WSLC_E_NETWORK_NOT_FOUND, m_defaultSession->InspectNetwork(networkName.c_str(), &output));
5796 + }
5797 +
5798 + WSLC_TEST_METHOD(NetworkSessionRecoveryWithIpRangeTest)
5799 + {
5800 + const std::string networkName = "recovery-ip-range-net";
5801 + const std::string subnet = "172.34.0.0/16";
5802 + const std::string ipRange = "172.34.20.0/24";
5803 +
5804 + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str()));
5805 +
5806 + WSLCNetworkOptions options{};
5807 + options.Name = networkName.c_str();
5808 + options.Driver = "bridge";
5809 + options.Subnet = subnet.c_str();
5810 + options.IpRange = ipRange.c_str();
5811 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
5812 +
5813 + auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
5814 +
5815 + // Reset the session (simulates session restart).
5816 + ResetTestSession();
5817 +
5818 + wil::unique_cotaskmem_ansistring output;
5819 + VERIFY_SUCCEEDED(m_defaultSession->InspectNetwork(networkName.c_str(), &output));
5820 + VERIFY_IS_NOT_NULL(output.get());
5821 +
5822 + auto inspect = wsl::shared::FromJson<wsl::windows::common::wslc_schema::Network>(output.get());
5823 + VERIFY_IS_TRUE(inspect.IPAM.Config.has_value());
5824 + VERIFY_ARE_EQUAL(1u, inspect.IPAM.Config->size());
5825 + VERIFY_ARE_EQUAL(subnet, inspect.IPAM.Config->at(0).Subnet);
5826 + VERIFY_ARE_EQUAL(ipRange, inspect.IPAM.Config->at(0).IPRange);
5827 + }
5828 +
5829 WSLC_TEST_METHOD(NetworkCreateWithArbitraryDriverOptsTest)
5830 {
5831 const std::string networkName = "arbitrary-opts-test-net";
test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp
+28
@@ -168,6 +168,34 @@ class WSLCE2ENetworkCreateTests
168 VerifyNetworkIsNotListed(TestNetworkName);
169 }
170
171 + WSLC_TEST_METHOD(WSLCE2E_Network_Create_SubnetAndIpRange_Success)
172 + {
173 + const std::wstring subnet = L"172.51.0.0/16";
174 + const std::wstring ipRange = L"172.51.10.0/24";
175 + auto result = RunWslc(std::format(L"network create --subnet {} --ip-range {} {}", subnet, ipRange, TestNetworkName));
176 + result.Verify({.Stderr = L"", .ExitCode = 0});
177 + VERIFY_ARE_EQUAL(TestNetworkName, result.GetStdoutOneLine());
178 +
179 + VerifyNetworkIsListed(TestNetworkName);
180 + auto inspect = InspectNetwork(TestNetworkName);
181 + VERIFY_ARE_EQUAL("bridge", inspect.Driver);
182 + VERIFY_IS_TRUE(inspect.IPAM.Config.has_value());
183 + VERIFY_ARE_EQUAL(1u, inspect.IPAM.Config->size());
184 + VERIFY_ARE_EQUAL(wsl::shared::string::WideToMultiByte(subnet), (*inspect.IPAM.Config)[0].Subnet);
185 + VERIFY_ARE_EQUAL(wsl::shared::string::WideToMultiByte(ipRange), (*inspect.IPAM.Config)[0].IPRange);
186 + }
187 +
188 + WSLC_TEST_METHOD(WSLCE2E_Network_Create_IpRangeWithoutSubnet_Fail)
189 + {
190 + auto result = RunWslc(std::format(L"network create --ip-range 172.52.10.0/24 {}", TestNetworkName));
191 + result.Verify(
192 + {.Stdout = L"",
193 + .Stderr = L"The '--ip-range' option requires '--subnet' to also be specified.\r\nError code: E_INVALIDARG\r\n",
194 + .ExitCode = 1});
195 +
196 + VerifyNetworkIsNotListed(TestNetworkName);
197 + }
198 +
199 WSLC_TEST_METHOD(WSLCE2E_Network_Create_WithOpt_Success)
200 {
201 constexpr auto c_opts = L"--opt com.docker.network.bridge.enable_icc=true --opt com.docker.network.driver.mtu=1450";