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";