Add --network-alias support to wslc container create/run (#40759)
beena352 committed
Jun 11, 2026 at 15:09 UTC
0e786326c8cf98cd1c7f27200613f75c147a7e4b
17 files changed
+410
-7
localization/strings/en-US/Resources.resw
+18
@@ -2346,6 +2346,17 @@ For privacy information about this product please visit https://aka.ms/privacy.<
2346
<value>Endpoint settings are not yet supported (network '{}').</value>
2347
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2348
</data>
2349
+ <data name="MessageWslcAliasEmpty" xml:space="preserve">
2350
+ <value>Network alias cannot be empty.</value>
2351
+ </data>
2352
+ <data name="MessageWslcAliasRequiresUserDefinedNetwork" xml:space="preserve">
2353
+ <value>Network aliases require a user-defined network. Use --network to specify one.</value>
2354
+ <comment>{Locked="--network "}Command line arguments, file names and string inserts should not be translated</comment>
2355
+ </data>
2356
+ <data name="MessageWslcAliasAmbiguousWithMultipleNetworks" xml:space="preserve">
2357
+ <value>Network aliases cannot be specified when multiple networks are requested. Use a single --network argument.</value>
2358
+ <comment>{Locked="--network "}Command line arguments, file names and string inserts should not be translated</comment>
2359
+ </data>
2360
<data name = "MessageWslcFailedToMountVolume" xml:space = "preserve" >
2361
<value>Failed to create volume '{}': {}</value>
2362
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
@@ -2813,6 +2824,13 @@ On first run, creates the file with all settings commented out at their defaults
2824
<data name="WSLCCLI_NetworkHostModeNotSupportedError" xml:space="preserve">
2825
<value>host mode networking is not supported</value>
2826
</data>
2827
+ <data name="WSLCCLI_NetworkAliasArgDescription" xml:space="preserve">
2828
+ <value>Add a network-scoped alias for the container</value>
2829
+ </data>
2830
+ <data name="WSLCCLI_NetworkAliasEmptyError" xml:space="preserve">
2831
+ <value>Invalid {} value: network alias cannot be empty or whitespace</value>
2832
+ <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2833
+ </data>
2834
<data name="WSLCCLI_NoCacheArgDescription" xml:space="preserve">
2835
<value>Do not use cache when building the image</value>
2836
</data>
src/windows/common/WSLCContainerLauncher.cpp
+16
@@ -250,6 +250,11 @@ void wsl::windows::common::WSLCContainerLauncher::AddAdditionalNetwork(const std
250
m_additionalNetworks.push_back(Name);
251
}
252
253
+void wsl::windows::common::WSLCContainerLauncher::AddPrimaryNetworkAlias(const std::string& Alias)
254
+{
255
+ m_primaryNetworkAliases.push_back(Alias);
256
+}
257
+
258
std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::LaunchNoThrow(
259
IWSLCSession& Session, WSLCContainerStartFlags Flags, IWarningCallback* WarningCallback)
260
{
@@ -371,6 +376,17 @@ std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::C
376
options.ContainerNetwork.Networks = connections.empty() ? nullptr : connections.data();
377
options.ContainerNetwork.NetworksCount = static_cast<ULONG>(connections.size());
378
379
+ // Aliases for the primary endpoint.
380
+ std::vector<KeyValuePair> aliasKvps;
381
+ aliasKvps.reserve(m_primaryNetworkAliases.size());
382
+ for (const auto& alias : m_primaryNetworkAliases)
383
+ {
384
+ aliasKvps.push_back({.Key = "Aliases", .Value = alias.c_str()});
385
+ }
386
+
387
+ options.ContainerNetwork.Settings = aliasKvps.empty() ? nullptr : aliasKvps.data();
388
+ options.ContainerNetwork.SettingsCount = static_cast<ULONG>(aliasKvps.size());
389
+
390
options.MemoryBytes = m_memoryBytes;
391
options.NanoCpus = m_nanoCpus;
392
options.UlimitsCount = static_cast<ULONG>(m_ulimits.size());
src/windows/common/WSLCContainerLauncher.h
+2
@@ -63,6 +63,7 @@ public:
63
void AddLabel(const std::string& Key, const std::string& Value);
64
void AddTmpfs(const std::string& ContainerPath, const std::string& Options);
65
void AddAdditionalNetwork(const std::string& Name);
66
+ void AddPrimaryNetworkAlias(const std::string& Alias);
67
68
std::pair<HRESULT, std::optional<RunningWSLCContainer>> CreateNoThrow(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
69
RunningWSLCContainer Create(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
@@ -110,6 +111,7 @@ private:
111
std::vector<std::string> m_dnsSearchDomains;
112
std::vector<std::string> m_dnsOptions;
113
std::vector<std::string> m_additionalNetworks;
114
+ std::vector<std::string> m_primaryNetworkAliases;
115
std::vector<WSLCLabel> m_labels;
116
std::deque<std::string> m_labelKeys;
117
std::deque<std::string> m_labelValues;
src/windows/inc/docker_schema.h
+17
-2
@@ -264,13 +264,28 @@ struct EndpointSettings
264
std::string Gateway;
265
std::string MacAddress;
266
int IPPrefixLen{};
267
+ std::optional<std::vector<std::string>> Aliases;
268
268
- NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(EndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen);
269
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(EndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen, Aliases);
270
};
271
272
+struct EndpointConfig
273
+{
274
+ std::optional<std::vector<std::string>> Aliases;
275
+};
276
+
277
+inline void to_json(nlohmann::json& j, const EndpointConfig& v)
278
+{
279
+ j = nlohmann::json::object();
280
+ if (v.Aliases.has_value() && !v.Aliases->empty())
281
+ {
282
+ j["Aliases"] = *v.Aliases;
283
+ }
284
+}
285
+
286
struct NetworkingConfig
287
{
273
- std::map<std::string, EmptyObject> EndpointsConfig;
288
+ std::map<std::string, EndpointConfig> EndpointsConfig;
289
290
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(NetworkingConfig, EndpointsConfig);
291
};
src/windows/inc/wslc_schema.h
+2
-1
@@ -85,8 +85,9 @@ struct InspectEndpointSettings
85
std::string Gateway;
86
std::string MacAddress;
87
int IPPrefixLen{};
88
+ std::vector<std::string> Aliases;
89
89
- NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(InspectEndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen);
90
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(InspectEndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen, Aliases);
91
};
92
93
struct InspectNetworkSettings
src/windows/service/inc/wslc.idl
+5
@@ -301,6 +301,11 @@ typedef struct _WSLCContainerNetwork
301
302
[unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks;
303
ULONG NetworksCount;
304
+
305
+ // Settings for the primary endpoint (the network identified by NetworkMode).
306
+ // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries).
307
+ [unique, size_is(SettingsCount)] const KeyValuePair* Settings;
308
+ ULONG SettingsCount;
309
} WSLCContainerNetwork;
310
311
typedef enum _WSLCContainerFlags
src/windows/wslc/arguments/ArgumentDefinitions.h
+1
@@ -76,6 +76,7 @@ _(Latest, "latest", L"l", Kind::Flag, L
76
_(Memory, "memory", L"m", Kind::Value, Localization::WSLCCLI_MemoryArgDescription()) \
77
_(Name, "name", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NameArgDescription()) \
78
_(Network, "network", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkArgDescription()) \
79
+_(NetworkAlias, "network-alias", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkAliasArgDescription()) \
80
_(NetworkName, "network-name", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_NetworkNameArgDescription()) \
81
/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/ \
82
_(NoCache, "no-cache", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoCacheArgDescription()) \
src/windows/wslc/arguments/ArgumentValidation.cpp
+13
@@ -124,6 +124,19 @@ void Argument::Validate(const ArgMap& execArgs) const
124
break;
125
}
126
127
+ case ArgType::NetworkAlias:
128
+ {
129
+ for (const auto& value : execArgs.GetAll<ArgType::NetworkAlias>())
130
+ {
131
+ if (value.empty() ||
132
+ std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
133
+ {
134
+ throw ArgumentException(Localization::WSLCCLI_NetworkAliasEmptyError(m_name));
135
+ }
136
+ }
137
+ break;
138
+ }
139
+
140
default:
141
break;
142
}
src/windows/wslc/commands/ContainerCreateCommand.cpp
+1
@@ -49,6 +49,7 @@ std::vector<Argument> ContainerCreateCommand::GetArguments() const
49
Argument::Create(ArgType::Memory),
50
Argument::Create(ArgType::Name),
51
Argument::Create(ArgType::Network, false, NO_LIMIT),
52
+ Argument::Create(ArgType::NetworkAlias, false, NO_LIMIT),
53
// Argument::Create(ArgType::NoDNS),
54
// Argument::Create(ArgType::Progress),
55
Argument::Create(ArgType::Publish, false, NO_LIMIT),
src/windows/wslc/commands/ContainerRunCommand.cpp
+1
@@ -49,6 +49,7 @@ std::vector<Argument> ContainerRunCommand::GetArguments() const
49
Argument::Create(ArgType::Memory),
50
Argument::Create(ArgType::Name),
51
Argument::Create(ArgType::Network, false, NO_LIMIT),
52
+ Argument::Create(ArgType::NetworkAlias, false, NO_LIMIT),
53
// Argument::Create(ArgType::NoDNS),
54
// Argument::Create(ArgType::Progress),
55
Argument::Create(ArgType::Publish, false, NO_LIMIT),
src/windows/wslc/services/ContainerModel.h
+1
@@ -51,6 +51,7 @@ struct ContainerOptions
51
std::vector<std::string> DnsSearchDomains;
52
std::vector<std::string> DnsOptions;
53
std::vector<std::string> Networks;
54
+ std::vector<std::string> NetworkAliases;
55
std::vector<std::string> Tmpfs;
56
std::vector<std::pair<std::string, std::string>> Labels;
57
std::optional<std::wstring> CidFile{};
src/windows/wslc/services/ContainerService.cpp
+18
@@ -62,6 +62,24 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(
62
containerLauncher.AddAdditionalNetwork(options.Networks[i]);
63
}
64
65
+ if (!options.NetworkAliases.empty())
66
+ {
67
+ THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcAliasRequiresUserDefinedNetwork(), options.Networks.empty());
68
+
69
+ THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcAliasAmbiguousWithMultipleNetworks(), options.Networks.size() > 1);
70
+
71
+ const auto& primary = options.Networks.front();
72
+ THROW_HR_WITH_USER_ERROR_IF(
73
+ E_INVALIDARG,
74
+ Localization::MessageWslcAliasRequiresUserDefinedNetwork(),
75
+ primary == "bridge" || primary == "host" || primary == "none" || primary.starts_with("container:"));
76
+
77
+ for (const auto& alias : options.NetworkAliases)
78
+ {
79
+ containerLauncher.AddPrimaryNetworkAlias(alias);
80
+ }
81
+ }
82
+
83
// Set port options if provided
84
for (const auto& port : options.Ports)
85
{
src/windows/wslc/tasks/ContainerTasks.cpp
+10
@@ -526,6 +526,16 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
526
}
527
}
528
529
+ if (context.Args.Contains(ArgType::NetworkAlias))
530
+ {
531
+ auto aliases = context.Args.GetAll<ArgType::NetworkAlias>();
532
+ options.NetworkAliases.reserve(aliases.size());
533
+ for (const auto& value : aliases)
534
+ {
535
+ options.NetworkAliases.emplace_back(WideToMultiByte(value));
536
+ }
537
+ }
538
+
539
if (context.Args.Contains(ArgType::User))
540
{
541
options.User = WideToMultiByte(context.Args.Get<ArgType::User>());
src/windows/wslcsession/WSLCContainer.cpp
+54
-4
@@ -220,10 +220,45 @@ std::string ResolveNetworkMode(LPCSTR networkMode, bool hasRequestedPorts, const
220
return std::string{mode};
221
}
222
223
-std::map<std::string, EmptyObject> ResolveEndpoints(
223
+// Parses the primary endpoint's Settings KVP. Today only "Aliases" is recognised; unknown
224
+// keys are rejected to avoid silently dropping caller data (mirrors the per-connection guard).
225
+EndpointConfig ResolvePrimaryEndpointConfig(const KeyValuePair* settings, ULONG count, std::string_view networkMode)
226
+{
227
+ EndpointConfig config{};
228
+ if (count == 0)
229
+ {
230
+ return config;
231
+ }
232
+
233
+ THROW_HR_IF_MSG(E_INVALIDARG, settings == nullptr, "Settings is null with SettingsCount=%lu", count);
234
+
235
+ auto parsed = ParseKeyMultiValuePairs(settings, count);
236
+
237
+ for (const auto& [key, _] : parsed)
238
+ {
239
+ THROW_HR_WITH_USER_ERROR_IF(
240
+ E_NOTIMPL, Localization::MessageWslcEndpointSettingsNotSupported(std::string{networkMode}), key != "Aliases");
241
+ }
242
+
243
+ if (auto it = parsed.find("Aliases"); it != parsed.end())
244
+ {
245
+ for (const auto& alias : it->second)
246
+ {
247
+ THROW_HR_WITH_USER_ERROR_IF(
248
+ E_INVALIDARG,
249
+ Localization::MessageWslcAliasEmpty(),
250
+ alias.empty() || std::all_of(alias.begin(), alias.end(), [](unsigned char ch) { return std::isspace(ch); }));
251
+ }
252
+ config.Aliases = std::move(it->second);
253
+ }
254
+
255
+ return config;
256
+}
257
+
258
+std::map<std::string, EndpointConfig> ResolveEndpoints(
259
const WSLCNetworkConnection* connections, ULONG count, std::string_view resolvedMode, const std::unordered_map<std::string, NetworkEntry>& sessionNetworks)
260
{
226
- std::map<std::string, EmptyObject> resolved;
261
+ std::map<std::string, EndpointConfig> resolved;
262
if (count == 0)
263
{
264
return resolved;
@@ -1314,6 +1349,7 @@ WslcInspectContainer WSLCContainerImpl::BuildInspectContainer(const DockerInspec
1349
wslcEndpoint.Gateway = endpoint.Gateway;
1350
wslcEndpoint.MacAddress = endpoint.MacAddress;
1351
wslcEndpoint.IPPrefixLen = endpoint.IPPrefixLen;
1352
+ wslcEndpoint.Aliases = endpoint.Aliases.value_or(std::vector<std::string>{});
1353
wslcInspect.NetworkSettings.Networks[name] = std::move(wslcEndpoint);
1354
}
1355
@@ -1586,6 +1622,15 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1622
auto endpoints = ResolveEndpoints(
1623
containerOptions.ContainerNetwork.Networks, containerOptions.ContainerNetwork.NetworksCount, networkMode, sessionNetworks);
1624
1625
+ auto primaryConfig =
1626
+ ResolvePrimaryEndpointConfig(containerOptions.ContainerNetwork.Settings, containerOptions.ContainerNetwork.SettingsCount, networkMode);
1627
+
1628
+ // Aliases require a user-defined endpoint. bridge/host/none/container: modes don't support them.
1629
+ THROW_HR_WITH_USER_ERROR_IF(
1630
+ E_INVALIDARG,
1631
+ Localization::MessageWslcAliasRequiresUserDefinedNetwork(),
1632
+ primaryConfig.Aliases.has_value() && (networkMode == "bridge" || !NetworkModeAllocatesVmPorts(networkMode)));
1633
+
1634
auto mappedPorts = BuildPortMappings(ports, networkMode, virtualMachine);
1635
1636
request.HostConfig.NetworkMode = networkMode;
@@ -1593,9 +1638,14 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1638
1639
// Docker API v1.44 (Docker 25.x) requires the primary network to be present in EndpointsConfig
1640
// when EndpointsConfig is non-empty. Insert it so Docker attaches all networks at create time.
1596
- if (!request.NetworkingConfig.EndpointsConfig.empty() && NetworkModeAllocatesVmPorts(networkMode))
1641
+ // Also insert when the caller supplied aliases for the primary endpoint.
1642
+ if (NetworkModeAllocatesVmPorts(networkMode) && (!request.NetworkingConfig.EndpointsConfig.empty() || primaryConfig.Aliases.has_value()))
1643
{
1598
- request.NetworkingConfig.EndpointsConfig.try_emplace(networkMode);
1644
+ auto [it, _] = request.NetworkingConfig.EndpointsConfig.try_emplace(networkMode);
1645
+ if (primaryConfig.Aliases.has_value())
1646
+ {
1647
+ it->second.Aliases = std::move(primaryConfig.Aliases);
1648
+ }
1649
}
1650
1651
for (const auto& e : mappedPorts)
test/windows/WSLCTests.cpp
+139
@@ -6830,6 +6830,145 @@ class WSLCTests
6830
}
6831
}
6832
6833
+ WSLC_TEST_METHOD(NetworkAliasCreateTest)
6834
+ {
6835
+ auto createNetwork = [&](const std::string& name, const char* subnet) {
6836
+ LOG_IF_FAILED(m_defaultSession->DeleteNetwork(name.c_str()));
6837
+ WSLCDriverOption opts[] = {{"Subnet", subnet}};
6838
+ WSLCNetworkOptions netOpts{};
6839
+ netOpts.Name = name.c_str();
6840
+ netOpts.Driver = "bridge";
6841
+ netOpts.DriverOpts = opts;
6842
+ netOpts.DriverOptsCount = ARRAYSIZE(opts);
6843
+ VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts, nullptr));
6844
+ };
6845
+
6846
+ auto launchWithAliases = [&](const std::string& containerName, const std::string& networkMode, const std::vector<std::string>& aliases) {
6847
+ WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}, networkMode);
6848
+ for (const auto& a : aliases)
6849
+ {
6850
+ launcher.AddPrimaryNetworkAlias(a);
6851
+ }
6852
+ return launcher.Launch(*m_defaultSession);
6853
+ };
6854
+
6855
+ auto expectError = [&](const std::string& containerName,
6856
+ const std::string& networkMode,
6857
+ const std::vector<std::string>& aliases,
6858
+ HRESULT expectedResult,
6859
+ const std::wstring& expectedErrorMessage) {
6860
+ auto result = wil::ResultFromException([&] { launchWithAliases(containerName, networkMode, aliases); });
6861
+ VERIFY_ARE_EQUAL(result, expectedResult);
6862
+ ValidateCOMErrorMessage(expectedErrorMessage);
6863
+ };
6864
+
6865
+ // Single user-defined network + single alias — success, round-trips via inspect.
6866
+ {
6867
+ const std::string networkName = "alias-net-single";
6868
+ createNetwork(networkName, "172.60.0.0/16");
6869
+ auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6870
+
6871
+ auto container = launchWithAliases("alias-ctr-single", networkName, {"db"});
6872
+
6873
+ auto inspect = container.Inspect();
6874
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
6875
+ const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
6876
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
6877
+ }
6878
+
6879
+ // Multiple aliases on a single network — all present.
6880
+ {
6881
+ const std::string networkName = "alias-net-multi";
6882
+ createNetwork(networkName, "172.61.0.0/16");
6883
+ auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6884
+
6885
+ auto container = launchWithAliases("alias-ctr-multi", networkName, {"db", "primary", "backup"});
6886
+
6887
+ auto inspect = container.Inspect();
6888
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
6889
+ const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
6890
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
6891
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "primary") != endpoint.Aliases.end());
6892
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "backup") != endpoint.Aliases.end());
6893
+ }
6894
+
6895
+ // Alias on 'host' mode — rejected at the IDL layer.
6896
+ {
6897
+ expectError(
6898
+ "alias-ctr-host",
6899
+ "host",
6900
+ {"db"},
6901
+ E_INVALIDARG,
6902
+ L"Network aliases require a user-defined network. Use --network to specify one.");
6903
+ }
6904
+
6905
+ // Alias on default 'bridge' mode — rejected (aliases require a user-defined network).
6906
+ {
6907
+ expectError(
6908
+ "alias-ctr-bridge",
6909
+ "bridge",
6910
+ {"db"},
6911
+ E_INVALIDARG,
6912
+ L"Network aliases require a user-defined network. Use --network to specify one.");
6913
+ }
6914
+
6915
+ // Alias on 'none' mode — rejected.
6916
+ {
6917
+ expectError(
6918
+ "alias-ctr-none",
6919
+ "none",
6920
+ {"db"},
6921
+ E_INVALIDARG,
6922
+ L"Network aliases require a user-defined network. Use --network to specify one.");
6923
+ }
6924
+
6925
+ // Alias on 'container:' mode — rejected.
6926
+ {
6927
+ WSLCContainerLauncher targetLauncher("debian:latest", "alias-ctr-target", {"sleep", "99999"}, {});
6928
+ auto target = targetLauncher.Launch(*m_defaultSession);
6929
+
6930
+ expectError(
6931
+ "alias-ctr-container",
6932
+ "container:alias-ctr-target",
6933
+ {"db"},
6934
+ E_INVALIDARG,
6935
+ L"Network aliases require a user-defined network. Use --network to specify one.");
6936
+ }
6937
+
6938
+ // Empty alias string — rejected.
6939
+ {
6940
+ const std::string networkName = "alias-net-empty";
6941
+ createNetwork(networkName, "172.62.0.0/16");
6942
+ auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6943
+
6944
+ expectError("alias-ctr-empty", networkName, {""}, E_INVALIDARG, L"Network alias cannot be empty.");
6945
+ }
6946
+
6947
+ // Unknown KVP key on primary settings — rejected with E_NOTIMPL.
6948
+ {
6949
+ const std::string networkName = "alias-net-unknown";
6950
+ createNetwork(networkName, "172.63.0.0/16");
6951
+ auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6952
+
6953
+ LPCSTR args[] = {"sleep", "99999"};
6954
+ const KeyValuePair settings[] = {{"IPAddress", "10.0.0.5"}};
6955
+ WSLCContainerOptions options{};
6956
+ options.Image = "debian:latest";
6957
+ options.Name = "alias-ctr-unknown";
6958
+ options.InitProcessOptions.CommandLine = {.Values = args, .Count = ARRAYSIZE(args)};
6959
+ options.ContainerNetwork.NetworkMode = networkName.c_str();
6960
+ options.ContainerNetwork.Settings = settings;
6961
+ options.ContainerNetwork.SettingsCount = ARRAYSIZE(settings);
6962
+
6963
+ wil::com_ptr<IWSLCContainer> container;
6964
+ VERIFY_ARE_EQUAL(E_NOTIMPL, m_defaultSession->CreateContainer(&options, nullptr, &container));
6965
+ ValidateCOMErrorMessage(std::format(
6966
+ L"Endpoint settings are not yet supported (network '{}').",
6967
+ std::wstring(networkName.begin(), networkName.end()))
6968
+ .c_str());
6969
+ }
6970
+ }
6971
+
6972
WSLC_TEST_METHOD(ContainerNetworkModeHappyPathTest)
6973
{
6974
// Start container A on the default (bridged) network, then start container B sharing A's
test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
+58
@@ -827,6 +827,63 @@ class WSLCE2EContainerCreateTests
827
VerifyContainerIsNotListed(WslcContainerName);
828
}
829
830
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_Success)
831
+ {
832
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
833
+ result.Verify({.Stderr = L"", .ExitCode = 0});
834
+ auto cleanupNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(TestNetworkName); });
835
+
836
+ result = RunWslc(std::format(
837
+ L"container create --name {} --network {} --network-alias db {} true", WslcContainerName, TestNetworkName, DebianImage.NameAndTag()));
838
+ result.Verify({.Stderr = L"", .ExitCode = 0});
839
+
840
+ const auto inspect = InspectContainer(WslcContainerName);
841
+ const auto networkName = wsl::shared::string::WideToMultiByte(TestNetworkName);
842
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
843
+ const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
844
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
845
+ }
846
+
847
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_NoNetwork_Rejected)
848
+ {
849
+ auto result =
850
+ RunWslc(std::format(L"container create --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
851
+ result.Verify(
852
+ {.Stderr =
853
+ L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
854
+ .ExitCode = 1});
855
+ VerifyContainerIsNotListed(WslcContainerName);
856
+ }
857
+
858
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_NoneMode_Rejected)
859
+ {
860
+ auto result = RunWslc(std::format(
861
+ L"container create --network none --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
862
+ result.Verify(
863
+ {.Stderr =
864
+ L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
865
+ .ExitCode = 1});
866
+ VerifyContainerIsNotListed(WslcContainerName);
867
+ }
868
+
869
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_MultipleNetworks_Rejected)
870
+ {
871
+ auto result = RunWslc(std::format(
872
+ L"container create --network bridge --network bridge --network-alias db --name {} {} true",
873
+ WslcContainerName,
874
+ DebianImage.NameAndTag()));
875
+ result.Verify({.Stderr = L"Network aliases cannot be specified when multiple networks are requested. Use a single --network argument.\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
876
+ VerifyContainerIsNotListed(WslcContainerName);
877
+ }
878
+
879
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_EmptyValue_Rejected)
880
+ {
881
+ auto result =
882
+ RunWslc(std::format(L"container create --network-alias \"\" --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
883
+ result.Verify({.Stderr = L"Invalid network-alias value: network alias cannot be empty or whitespace\r\n", .ExitCode = 1});
884
+ VerifyContainerIsNotListed(WslcContainerName);
885
+ }
886
+
887
WSLC_TEST_METHOD(WSLCE2E_Container_Create_Cpus)
888
{
889
auto result = RunWslc(std::format(L"container create --name {} --cpus 0.5 {} true", WslcContainerName, DebianImage.NameAndTag()));
@@ -1122,6 +1179,7 @@ private:
1179
<< L" -m,--memory Memory limit (e.g. 512M, 1G)\r\n"
1180
<< L" --name Name of the container\r\n"
1181
<< L" --network Connect a container to a network\r\n"
1182
+ << L" --network-alias Add a network-scoped alias for the container\r\n"
1183
<< L" -p,--publish Publish a port from a container to host\r\n"
1184
<< L" -P,--publish-all Publish all exposed ports to random host ports\r\n"
1185
<< L" --rm Remove the container after it stops\r\n"
test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
+54
@@ -713,6 +713,59 @@ class WSLCE2EContainerRunTests
713
result.Verify({.Stderr = L"Network not found: 'does-not-exist'\r\nError code: WSLC_E_NETWORK_NOT_FOUND\r\n", .ExitCode = 1});
714
}
715
716
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_Success)
717
+ {
718
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
719
+ result.Verify({.Stderr = L"", .ExitCode = 0});
720
+ auto cleanupNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(TestNetworkName); });
721
+
722
+ result = RunWslc(std::format(
723
+ L"container run --name {} --network {} --network-alias db {} true", WslcContainerName, TestNetworkName, DebianImage.NameAndTag()));
724
+ result.Verify({.Stderr = L"", .ExitCode = 0});
725
+
726
+ const auto inspect = InspectContainer(WslcContainerName);
727
+ const auto networkName = wsl::shared::string::WideToMultiByte(TestNetworkName);
728
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
729
+ const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
730
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
731
+ }
732
+
733
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_NoNetwork_Rejected)
734
+ {
735
+ auto result =
736
+ RunWslc(std::format(L"container run --rm --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
737
+ result.Verify(
738
+ {.Stderr =
739
+ L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
740
+ .ExitCode = 1});
741
+ }
742
+
743
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_NoneMode_Rejected)
744
+ {
745
+ auto result = RunWslc(std::format(
746
+ L"container run --rm --network none --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
747
+ result.Verify(
748
+ {.Stderr =
749
+ L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
750
+ .ExitCode = 1});
751
+ }
752
+
753
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_MultipleNetworks_Rejected)
754
+ {
755
+ auto result = RunWslc(std::format(
756
+ L"container run --rm --network bridge --network bridge --network-alias db --name {} {} true",
757
+ WslcContainerName,
758
+ DebianImage.NameAndTag()));
759
+ result.Verify({.Stderr = L"Network aliases cannot be specified when multiple networks are requested. Use a single --network argument.\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
760
+ }
761
+
762
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_EmptyValue_Rejected)
763
+ {
764
+ auto result = RunWslc(
765
+ std::format(L"container run --rm --network-alias \"\" --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
766
+ result.Verify({.Stderr = L"Invalid network-alias value: network alias cannot be empty or whitespace\r\n", .ExitCode = 1});
767
+ }
768
+
769
WSLC_TEST_METHOD(WSLCE2E_Container_Run_Volume_NamedVolume_Success)
770
{
771
// Create a named volume
@@ -973,6 +1026,7 @@ private:
1026
<< L" -m,--memory Memory limit (e.g. 512M, 1G)\r\n"
1027
<< L" --name Name of the container\r\n"
1028
<< L" --network Connect a container to a network\r\n"
1029
+ << L" --network-alias Add a network-scoped alias for the container\r\n"
1030
<< L" -p,--publish Publish a port from a container to host\r\n"
1031
<< L" -P,--publish-all Publish all exposed ports to random host ports\r\n"
1032
<< L" --rm Remove the container after it stops\r\n"