diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw
index aa0f5bf72c..b9a2aa6c03 100644
--- a/localization/strings/en-US/Resources.resw
+++ b/localization/strings/en-US/Resources.resw
@@ -2986,6 +2986,14 @@ On first run, creates the file with all settings commented out at their defaults
Invalid {} value: network name cannot be empty or whitespace
{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+ Invalid {} value: network name can only be specified once
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Invalid {} value: unsupported network option '{}'
+ {FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
host mode networking is not supported
diff --git a/src/shared/inc/stringshared.h b/src/shared/inc/stringshared.h
index 978bd9cbd7..48160c9426 100644
--- a/src/shared/inc/stringshared.h
+++ b/src/shared/inc/stringshared.h
@@ -13,14 +13,19 @@ Module Name:
--*/
#pragma once
+#include
+#include
+#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
#ifndef WIN32
#include
@@ -98,6 +103,27 @@ inline std::vector> Split(const std::basic_string& Strin
return Output;
}
+template
+inline std::vector> SplitPreserveEmpty(const std::basic_string_view String, T Separator)
+{
+ std::vector> Output;
+ size_t Start = 0;
+ while (Start <= String.size())
+ {
+ const auto End = String.find(Separator, Start);
+ if (End == std::basic_string_view::npos)
+ {
+ Output.emplace_back(String.substr(Start));
+ break;
+ }
+
+ Output.emplace_back(String.substr(Start, End - Start));
+ Start = End + 1;
+ }
+
+ return Output;
+}
+
template
inline std::vector> SplitByMultipleSeparators(const std::basic_string& String, const std::basic_string& Separators)
{
@@ -311,6 +337,21 @@ inline bool IsEqual(const std::wstring_view String1, const std::wstring_view Str
return (Compare(String1, String2, CaseInsensitive) == String1.size());
}
+template
+inline bool IsEmptyOrWhitespace(const std::basic_string_view String)
+{
+ return String.empty() || std::all_of(String.begin(), String.end(), [](T Ch) {
+ if constexpr (std::is_same_v)
+ {
+ return std::iswspace(static_cast(Ch));
+ }
+ else
+ {
+ return std::isspace(static_cast(Ch));
+ }
+ });
+}
+
template
inline std::optional ParseBool(const T* String)
{
diff --git a/src/windows/common/WSLCContainerLauncher.cpp b/src/windows/common/WSLCContainerLauncher.cpp
index 77b98cdd46..57a96e3d91 100644
--- a/src/windows/common/WSLCContainerLauncher.cpp
+++ b/src/windows/common/WSLCContainerLauncher.cpp
@@ -282,7 +282,12 @@ void wsl::windows::common::WSLCContainerLauncher::AddTmpfs(const std::string& Co
void wsl::windows::common::WSLCContainerLauncher::AddAdditionalNetwork(const std::string& Name)
{
- m_additionalNetworks.push_back(Name);
+ AddAdditionalNetwork(Name, {});
+}
+
+void wsl::windows::common::WSLCContainerLauncher::AddAdditionalNetwork(const std::string& Name, const std::vector& Aliases)
+{
+ m_additionalNetworks.push_back({.Name = Name, .Aliases = Aliases});
}
void wsl::windows::common::WSLCContainerLauncher::AddPrimaryNetworkAlias(const std::string& Alias)
@@ -424,24 +429,37 @@ std::pair> WSLCContainerLauncher::C
// Each additional network becomes an entry in NetworkingConfig.EndpointsConfig.
std::vector connections;
connections.reserve(m_additionalNetworks.size());
+ std::vector> connectionSettings;
+ connectionSettings.reserve(m_additionalNetworks.size());
for (const auto& e : m_additionalNetworks)
{
- connections.push_back({.NetworkName = e.c_str()});
+ auto& settings = connectionSettings.emplace_back();
+ settings.reserve(e.Aliases.size());
+ for (const auto& alias : e.Aliases)
+ {
+ settings.push_back({.Key = "Aliases", .Value = alias.c_str()});
+ }
+
+ connections.push_back({
+ .NetworkName = e.Name.c_str(),
+ .Settings = settings.empty() ? nullptr : settings.data(),
+ .SettingsCount = static_cast(settings.size()),
+ });
}
options.ContainerNetwork.Networks = connections.empty() ? nullptr : connections.data();
options.ContainerNetwork.NetworksCount = static_cast(connections.size());
// Aliases for the primary endpoint.
- std::vector aliasKvps;
- aliasKvps.reserve(m_primaryNetworkAliases.size());
+ std::vector primarySettings;
+ primarySettings.reserve(m_primaryNetworkAliases.size());
for (const auto& alias : m_primaryNetworkAliases)
{
- aliasKvps.push_back({.Key = "Aliases", .Value = alias.c_str()});
+ primarySettings.push_back({.Key = "Aliases", .Value = alias.c_str()});
}
- options.ContainerNetwork.Settings = aliasKvps.empty() ? nullptr : aliasKvps.data();
- options.ContainerNetwork.SettingsCount = static_cast(aliasKvps.size());
+ options.ContainerNetwork.Settings = primarySettings.empty() ? nullptr : primarySettings.data();
+ options.ContainerNetwork.SettingsCount = static_cast(primarySettings.size());
options.MemoryBytes = m_memoryBytes;
options.NanoCpus = m_nanoCpus;
diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h
index 0e9a63f42b..0ad00956cd 100644
--- a/src/windows/common/WSLCContainerLauncher.h
+++ b/src/windows/common/WSLCContainerLauncher.h
@@ -63,6 +63,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
void AddLabel(const std::string& Key, const std::string& Value);
void AddTmpfs(const std::string& ContainerPath, const std::string& Options);
void AddAdditionalNetwork(const std::string& Name);
+ void AddAdditionalNetwork(const std::string& Name, const std::vector& Aliases);
void AddPrimaryNetworkAlias(const std::string& Alias);
std::pair> CreateNoThrow(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
@@ -99,6 +100,12 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
using WSLCProcessLauncher::SetWorkingDirectory;
private:
+ struct NetworkConnection
+ {
+ std::string Name;
+ std::vector Aliases;
+ };
+
std::string m_image;
std::string m_name;
std::vector m_ports;
@@ -123,7 +130,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
std::vector m_dnsServers;
std::vector m_dnsSearchDomains;
std::vector m_dnsOptions;
- std::vector m_additionalNetworks;
+ std::vector m_additionalNetworks;
std::vector m_primaryNetworkAliases;
std::vector m_labels;
std::deque m_labelKeys;
diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl
index 2c9abc1aa3..f12c5a265e 100644
--- a/src/windows/service/inc/wslc.idl
+++ b/src/windows/service/inc/wslc.idl
@@ -224,6 +224,9 @@ typedef struct _WSLCUlimit
typedef struct _WSLCNetworkConnection
{
[string] LPCSTR NetworkName;
+
+ // Settings for this endpoint.
+ // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries).
[unique, size_is(SettingsCount)] const KeyValuePair* Settings;
ULONG SettingsCount;
} WSLCNetworkConnection;
diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp
index 4b2995e8ea..1fca5926db 100644
--- a/src/windows/wslc/arguments/ArgumentValidation.cpp
+++ b/src/windows/wslc/arguments/ArgumentValidation.cpp
@@ -141,19 +141,7 @@ void Argument::Validate(const ArgMap& execArgs) const
case ArgType::Network:
{
- for (const auto& value : execArgs.GetAll())
- {
- if (value.empty() ||
- std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast(c)); }))
- {
- throw ArgumentException(Localization::WSLCCLI_NetworkEmptyError(m_name));
- }
-
- if (IsEqual(value, L"host", true))
- {
- throw ArgumentException(Localization::WSLCCLI_NetworkHostModeNotSupportedError());
- }
- }
+ validation::ValidateNetwork(execArgs.GetAll(), m_name);
break;
}
@@ -219,6 +207,18 @@ void ValidateFilter(const std::vector& values)
}
}
+void ValidateNetwork(const std::vector& values, const std::wstring& argName)
+{
+ for (const auto& value : values)
+ {
+ const auto parsed = ParseNetworkArgument(value, argName);
+ if (IsEqual(parsed.Name, "host", true))
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkHostModeNotSupportedError());
+ }
+ }
+}
+
// Convert string to WSLCSignal enum - accepts either signal name (e.g., "SIGKILL") or number (e.g., "9")
WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName)
{
@@ -659,6 +659,80 @@ std::tuple ParseUlimit(const std::wstring& input,
return {WideToMultiByte(input.substr(0, equalsPos)), soft, hard};
}
+ParsedNetworkArgument ParseNetworkArgument(std::wstring_view value, const std::wstring& argName)
+{
+ ParsedNetworkArgument result;
+
+ auto parseOptions = [&](std::wstring_view options, bool requireName) {
+ bool parsedName = false;
+ for (const auto part : SplitPreserveEmpty(options, L','))
+ {
+ const auto separator = part.find(L'=');
+ if (separator == std::wstring_view::npos || separator == 0)
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkUnsupportedOptionError(argName, std::wstring{part}));
+ }
+
+ const auto key = part.substr(0, separator);
+ const auto optionValue = part.substr(separator + 1);
+ if (key == L"name")
+ {
+ if (IsEmptyOrWhitespace(optionValue))
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkEmptyError(argName));
+ }
+
+ if (parsedName)
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkDuplicateNameError(argName));
+ }
+
+ parsedName = true;
+ result.Name = WideToMultiByte(std::wstring{optionValue});
+ }
+ else if (key == L"alias")
+ {
+ if (IsEmptyOrWhitespace(optionValue))
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkAliasEmptyError(argName));
+ }
+
+ result.Aliases.emplace_back(WideToMultiByte(std::wstring{optionValue}));
+ }
+ else
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkUnsupportedOptionError(argName, std::wstring{key}));
+ }
+ }
+
+ if (requireName && !parsedName)
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkEmptyError(argName));
+ }
+ };
+
+ if (value.find(L'=') != std::wstring_view::npos)
+ {
+ parseOptions(value, true);
+ }
+ else
+ {
+ if (IsEmptyOrWhitespace(value))
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkEmptyError(argName));
+ }
+
+ result.Name = WideToMultiByte(std::wstring{value});
+ }
+
+ if (result.Name.empty())
+ {
+ throw ArgumentException(Localization::WSLCCLI_NetworkEmptyError(argName));
+ }
+
+ return result;
+}
+
std::pair ParseLabel(const std::wstring& value)
{
std::pair result{};
diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h
index b25a807e03..f384d1afb0 100644
--- a/src/windows/wslc/arguments/ArgumentValidation.h
+++ b/src/windows/wslc/arguments/ArgumentValidation.h
@@ -17,6 +17,7 @@ Module Name:
#include "ContainerModel.h"
#include "InspectModel.h"
#include
+#include
#include
#include
#include
@@ -85,9 +86,18 @@ InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstri
void ValidateGpus(const std::vector& values, const std::wstring& argName);
void ValidateVolumeMount(const std::vector& values);
void ValidateFilter(const std::vector& values);
+void ValidateNetwork(const std::vector& values, const std::wstring& argName);
std::pair ParseLabel(const std::wstring& value);
std::pair ParseDriverOption(const std::wstring& value);
std::pair ParseFilter(const std::wstring& value);
+struct ParsedNetworkArgument
+{
+ std::string Name;
+ std::vector Aliases;
+};
+
+ParsedNetworkArgument ParseNetworkArgument(std::wstring_view value, const std::wstring& argName = {});
+
} // namespace wsl::windows::wslc::validation
diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h
index 4d8ff2cc59..3d57c07f21 100644
--- a/src/windows/wslc/services/ContainerModel.h
+++ b/src/windows/wslc/services/ContainerModel.h
@@ -16,7 +16,9 @@ Module Name:
#include
#include
+#include
#include
+#include
namespace wsl::windows::wslc::models {
@@ -27,6 +29,12 @@ enum class FormatType
Json,
};
+struct ContainerNetwork
+{
+ std::string Name;
+ std::vector Aliases;
+};
+
struct ContainerOptions
{
std::vector Arguments;
@@ -57,7 +65,7 @@ struct ContainerOptions
std::vector DnsServers;
std::vector DnsSearchDomains;
std::vector DnsOptions;
- std::vector Networks;
+ std::vector Networks;
std::vector NetworkAliases;
std::vector Tmpfs;
std::vector> Labels;
diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp
index b1854faa57..2fb869cf43 100644
--- a/src/windows/wslc/services/ContainerService.cpp
+++ b/src/windows/wslc/services/ContainerService.cpp
@@ -41,6 +41,12 @@ static void SetContainerArguments(WSLCProcessOptions& options, std::vector(argsStorage.size())};
}
+static bool SupportsNetworkAliases(std::string_view network)
+{
+ // Aliases are only supported for user-defined networks, not built-in or container-sourced network modes.
+ return network != "bridge" && network != "host" && network != "none" && !network.starts_with("container:");
+}
+
static wsl::windows::common::RunningWSLCContainer CreateInternal(Reporter& reporter, Session& session, const std::string& image, const ContainerOptions& options)
{
WarningCallback warningCallback(reporter);
@@ -54,14 +60,20 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Reporter& repor
WI_SetFlagIf(containerFlags, WSLCContainerFlagsPublishAll, options.PublishAll);
WI_SetFlagIf(containerFlags, WSLCContainerFlagsGpu, options.Gpu);
- std::string networkMode = options.Networks.empty() ? std::string("bridge") : options.Networks.front();
+ std::string networkMode = options.Networks.empty() ? std::string("bridge") : options.Networks.front().Name;
wsl::windows::common::WSLCContainerLauncher containerLauncher(
image, options.Name, options.Arguments, options.EnvironmentVariables, std::move(networkMode), processFlags);
for (size_t i = 1; i < options.Networks.size(); ++i)
{
- containerLauncher.AddAdditionalNetwork(options.Networks[i]);
+ const auto& network = options.Networks[i];
+ THROW_HR_WITH_USER_ERROR_IF(
+ E_INVALIDARG,
+ Localization::MessageWslcAliasRequiresUserDefinedNetwork(),
+ !network.Aliases.empty() && !SupportsNetworkAliases(network.Name));
+
+ containerLauncher.AddAdditionalNetwork(network.Name, network.Aliases);
}
if (!options.NetworkAliases.empty())
@@ -70,13 +82,24 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Reporter& repor
THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcAliasAmbiguousWithMultipleNetworks(), options.Networks.size() > 1);
+ const auto& primary = options.Networks.front().Name;
+ THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcAliasRequiresUserDefinedNetwork(), !SupportsNetworkAliases(primary));
+
+ for (const auto& alias : options.NetworkAliases)
+ {
+ containerLauncher.AddPrimaryNetworkAlias(alias);
+ }
+ }
+
+ if (!options.Networks.empty())
+ {
const auto& primary = options.Networks.front();
THROW_HR_WITH_USER_ERROR_IF(
E_INVALIDARG,
Localization::MessageWslcAliasRequiresUserDefinedNetwork(),
- primary == "bridge" || primary == "host" || primary == "none" || primary.starts_with("container:"));
+ !primary.Aliases.empty() && !SupportsNetworkAliases(primary.Name));
- for (const auto& alias : options.NetworkAliases)
+ for (const auto& alias : primary.Aliases)
{
containerLauncher.AddPrimaryNetworkAlias(alias);
}
diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp
index 52cc96f28a..7e566bbe40 100644
--- a/src/windows/wslc/tasks/ContainerTasks.cpp
+++ b/src/windows/wslc/tasks/ContainerTasks.cpp
@@ -848,11 +848,15 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
if (context.Args.Contains(ArgType::Network))
{
+ const auto networkArgName = Argument::Create(ArgType::Network).Name();
auto networks = context.Args.GetAll();
options.Networks.reserve(options.Networks.size() + networks.size());
for (const auto& value : networks)
{
- options.Networks.emplace_back(WideToMultiByte(value));
+ auto parsed = validation::ParseNetworkArgument(value, networkArgName);
+ auto& network = options.Networks.emplace_back();
+ network.Name = parsed.Name;
+ network.Aliases = std::move(parsed.Aliases);
}
}
diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp
index 24c7399932..2fb69a89e6 100644
--- a/src/windows/wslcsession/WSLCContainer.cpp
+++ b/src/windows/wslcsession/WSLCContainer.cpp
@@ -169,6 +169,11 @@ bool NetworkModeAllocatesVmPorts(std::string_view mode) noexcept
return mode != "host" && mode != "none" && !mode.starts_with(c_containerNetworkPrefix);
}
+bool NetworkSupportsAliases(std::string_view mode) noexcept
+{
+ return mode != "bridge" && NetworkModeAllocatesVmPorts(mode);
+}
+
// Reject `:` strings whose prefix isn't `container:`. Docker treats colon-prefixed
// modes (`service:`, `ns:`, ...) as special, but WSLC only supports `container:`. Surface the
// rejection here so both Create() and Open() recovery paths share the same gate.
@@ -231,9 +236,9 @@ std::string ResolveNetworkMode(LPCSTR networkMode, bool hasRequestedPorts, const
return std::string{mode};
}
-// Parses the primary endpoint's Settings KVP. Today only "Aliases" is recognised; unknown
-// keys are rejected to avoid silently dropping caller data (mirrors the per-connection guard).
-EndpointConfig ResolvePrimaryEndpointConfig(const KeyValuePair* settings, ULONG count, std::string_view networkMode)
+// Parses an endpoint's Settings KVP. Today only "Aliases" is recognised; unknown keys are rejected
+// to avoid silently dropping caller data.
+EndpointConfig ResolveEndpointConfig(const KeyValuePair* settings, ULONG count, std::string_view networkName)
{
EndpointConfig config{};
if (count == 0)
@@ -248,7 +253,7 @@ EndpointConfig ResolvePrimaryEndpointConfig(const KeyValuePair* settings, ULONG
for (const auto& [key, _] : parsed)
{
THROW_HR_WITH_USER_ERROR_IF(
- E_NOTIMPL, Localization::MessageWslcEndpointSettingsNotSupported(std::string{networkMode}), key != "Aliases");
+ E_NOTIMPL, Localization::MessageWslcEndpointSettingsNotSupported(std::string{networkName}), key != "Aliases");
}
if (auto it = parsed.find("Aliases"); it != parsed.end())
@@ -256,10 +261,9 @@ EndpointConfig ResolvePrimaryEndpointConfig(const KeyValuePair* settings, ULONG
for (const auto& alias : it->second)
{
THROW_HR_WITH_USER_ERROR_IF(
- E_INVALIDARG,
- Localization::MessageWslcAliasEmpty(),
- alias.empty() || std::all_of(alias.begin(), alias.end(), [](unsigned char ch) { return std::isspace(ch); }));
+ E_INVALIDARG, Localization::MessageWslcAliasEmpty(), wsl::shared::string::IsEmptyOrWhitespace(std::string_view{alias}));
}
+
config.Aliases = std::move(it->second);
}
@@ -288,15 +292,17 @@ std::map ResolveEndpoints(
auto [it, inserted] = resolved.try_emplace(name);
THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcDuplicateNetwork(name), !inserted);
+ auto config = ResolveEndpointConfig(connections[i].Settings, connections[i].SettingsCount, name);
+ THROW_HR_WITH_USER_ERROR_IF(
+ E_INVALIDARG, Localization::MessageWslcAliasRequiresUserDefinedNetwork(), config.Aliases.has_value() && !NetworkSupportsAliases(name));
+
if (name != "bridge")
{
THROW_HR_WITH_USER_ERROR_IF(
WSLC_E_NETWORK_NOT_FOUND, Localization::MessageWslcNetworkNotFound(name), !sessionNetworks.contains(name));
}
- // Per-endpoint Settings are reserved for future use (IPAddress, Aliases, ...). No keys
- // are interpreted today, so reject any non-empty payload rather than silently dropping it.
- THROW_HR_WITH_USER_ERROR_IF(E_NOTIMPL, Localization::MessageWslcEndpointSettingsNotSupported(name), connections[i].SettingsCount > 0);
+ it->second = std::move(config);
}
return resolved;
}
@@ -1846,13 +1852,13 @@ std::shared_ptr WSLCContainerImpl::Create(
containerOptions.ContainerNetwork.Networks, containerOptions.ContainerNetwork.NetworksCount, networkMode, sessionNetworks);
auto primaryConfig =
- ResolvePrimaryEndpointConfig(containerOptions.ContainerNetwork.Settings, containerOptions.ContainerNetwork.SettingsCount, networkMode);
+ ResolveEndpointConfig(containerOptions.ContainerNetwork.Settings, containerOptions.ContainerNetwork.SettingsCount, networkMode);
// Aliases require a user-defined endpoint. bridge/host/none/container: modes don't support them.
THROW_HR_WITH_USER_ERROR_IF(
E_INVALIDARG,
Localization::MessageWslcAliasRequiresUserDefinedNetwork(),
- primaryConfig.Aliases.has_value() && (networkMode == "bridge" || !NetworkModeAllocatesVmPorts(networkMode)));
+ primaryConfig.Aliases.has_value() && !NetworkSupportsAliases(networkMode));
auto mappedPorts = BuildPortMappings(ports, networkMode, virtualMachine);
diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp
index edc13644b6..3e435f6455 100644
--- a/test/windows/WSLCTests.cpp
+++ b/test/windows/WSLCTests.cpp
@@ -7161,9 +7161,8 @@ class WSLCTests
WSLC_TEST_METHOD(ContainerNetworkEndpointSettingsNotImplementedTest)
{
- // Per-endpoint Settings (Aliases, IPAMConfig, etc.) are reserved for a future PR.
- // Until that lands, any non-empty Settings payload must be rejected with E_NOTIMPL
- // so callers don't silently lose data.
+ // Unsupported per-endpoint settings must be rejected with E_NOTIMPL so callers
+ // don't silently lose data.
const std::string networkName = "custom-net-settings";
LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str()));
@@ -7174,7 +7173,7 @@ class WSLCTests
auto networkCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
LPCSTR args[] = {"sleep", "99999"};
- KeyValuePair settings[] = {{"Aliases", "my-alias"}};
+ KeyValuePair settings[] = {{"IPAddress", "10.0.0.5"}};
WSLCNetworkConnection connection{};
connection.NetworkName = networkName.c_str();
connection.Settings = settings;
@@ -7677,6 +7676,53 @@ class WSLCTests
VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "backup") != endpoint.Aliases.end());
}
+ // Aliases on primary and additional user-defined networks — all present.
+ {
+ const std::string primaryNetworkName = "alias-net-primary";
+ const std::string additionalNetworkName = "alias-net-additional";
+ createNetwork(primaryNetworkName, "172.64.0.0/16");
+ createNetwork(additionalNetworkName, "172.65.0.0/16");
+ auto primaryNetCleanup =
+ wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(primaryNetworkName.c_str())); });
+ auto additionalNetCleanup =
+ wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(additionalNetworkName.c_str())); });
+
+ WSLCContainerLauncher launcher("debian:latest", "alias-ctr-additional", {"sleep", "99999"}, {}, primaryNetworkName);
+ launcher.AddPrimaryNetworkAlias("db");
+ launcher.AddAdditionalNetwork(additionalNetworkName, {"cache", "replica"});
+ auto container = launcher.Launch(*m_defaultSession);
+
+ auto inspect = container.Inspect();
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(primaryNetworkName));
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(additionalNetworkName));
+ const auto& primaryEndpoint = inspect.NetworkSettings.Networks.at(primaryNetworkName);
+ const auto& additionalEndpoint = inspect.NetworkSettings.Networks.at(additionalNetworkName);
+ VERIFY_IS_TRUE(std::ranges::find(primaryEndpoint.Aliases, "db") != primaryEndpoint.Aliases.end());
+ VERIFY_IS_TRUE(std::ranges::find(additionalEndpoint.Aliases, "cache") != additionalEndpoint.Aliases.end());
+ VERIFY_IS_TRUE(std::ranges::find(additionalEndpoint.Aliases, "replica") != additionalEndpoint.Aliases.end());
+ }
+
+ // Aliases on additional built-in/non-user-defined networks — rejected before network lookup.
+ {
+ const std::string primaryNetworkName = "alias-net-invalid-additional";
+ createNetwork(primaryNetworkName, "172.66.0.0/16");
+ auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(primaryNetworkName.c_str())); });
+
+ auto expectAdditionalNetworkAliasError = [&](const std::string& containerName, const std::string& additionalNetworkName) {
+ WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}, primaryNetworkName);
+ launcher.AddAdditionalNetwork(additionalNetworkName, {"db"});
+
+ auto result = wil::ResultFromException([&] { launcher.Launch(*m_defaultSession); });
+ VERIFY_ARE_EQUAL(E_INVALIDARG, result);
+ ValidateCOMErrorMessage(L"Network aliases require a user-defined network. Use --network to specify one.");
+ };
+
+ expectAdditionalNetworkAliasError("alias-ctr-additional-bridge", "bridge");
+ expectAdditionalNetworkAliasError("alias-ctr-additional-host", "host");
+ expectAdditionalNetworkAliasError("alias-ctr-additional-none", "none");
+ expectAdditionalNetworkAliasError("alias-ctr-additional-container", "container:alias-ctr-target");
+ }
+
// Alias on 'host' mode — rejected at the IDL layer.
{
expectError(
diff --git a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
index 9a5ca6218c..ddbe8bd4c0 100644
--- a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
+++ b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
@@ -21,6 +21,7 @@ Module Name:
#include "AsyncExecution.h"
#include "Command.h"
#include "RootCommand.h"
+#include "ArgumentValidation.h"
#include "ContainerCommand.h"
#include "ContainerTasks.h"
@@ -368,7 +369,7 @@ class WSLCCLIExecutionUnitTests
const auto& options = context.Data.Get();
VERIFY_ARE_EQUAL(1u, options.Networks.size());
- VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0]);
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
}
TEST_METHOD(RunCommand_ParseNetworkMultipleValues_PreservesOrder)
@@ -384,8 +385,50 @@ class WSLCCLIExecutionUnitTests
const auto& options = context.Data.Get();
VERIFY_ARE_EQUAL(2u, options.Networks.size());
- VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0]);
- VERIFY_ARE_EQUAL(std::string("net2"), options.Networks[1]);
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
+ VERIFY_ARE_EQUAL(std::string("net2"), options.Networks[1].Name);
+ }
+
+ TEST_METHOD(RunCommand_ParseDockerNetworkAliases_SetsPerNetworkAliases)
+ {
+ auto invocation =
+ CreateInvocationFromCommandLine(L"wslc --network name=net1,alias=a,alias=b --network name=net2,alias=c ubuntu sh");
+
+ ContainerRunCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+ command.ValidateArguments(context.Args);
+
+ wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);
+
+ const auto& options = context.Data.Get();
+ VERIFY_ARE_EQUAL(2u, options.Networks.size());
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
+ VERIFY_ARE_EQUAL(2u, options.Networks[0].Aliases.size());
+ VERIFY_ARE_EQUAL(std::string("a"), options.Networks[0].Aliases[0]);
+ VERIFY_ARE_EQUAL(std::string("b"), options.Networks[0].Aliases[1]);
+ VERIFY_ARE_EQUAL(std::string("net2"), options.Networks[1].Name);
+ VERIFY_ARE_EQUAL(1u, options.Networks[1].Aliases.size());
+ VERIFY_ARE_EQUAL(std::string("c"), options.Networks[1].Aliases[0]);
+ }
+
+ TEST_METHOD(RunCommand_ParseDockerNetworkAliasesWithNameAfterAlias_SetsPerNetworkAliases)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network alias=a,name=net1,alias=b ubuntu sh");
+
+ ContainerRunCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+ command.ValidateArguments(context.Args);
+
+ wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);
+
+ const auto& options = context.Data.Get();
+ VERIFY_ARE_EQUAL(1u, options.Networks.size());
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
+ VERIFY_ARE_EQUAL(2u, options.Networks[0].Aliases.size());
+ VERIFY_ARE_EQUAL(std::string("a"), options.Networks[0].Aliases[0]);
+ VERIFY_ARE_EQUAL(std::string("b"), options.Networks[0].Aliases[1]);
}
TEST_METHOD(RunCommand_ParseNetworkEmptyValue_ThrowsArgumentException)
@@ -413,7 +456,7 @@ class WSLCCLIExecutionUnitTests
const auto& options = context.Data.Get();
VERIFY_ARE_EQUAL(1u, options.Networks.size());
- VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0]);
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
}
TEST_METHOD(CreateCommand_ParseNetworkMultipleValues_PreservesOrder)
@@ -429,8 +472,160 @@ class WSLCCLIExecutionUnitTests
const auto& options = context.Data.Get();
VERIFY_ARE_EQUAL(2u, options.Networks.size());
- VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0]);
- VERIFY_ARE_EQUAL(std::string("net2"), options.Networks[1]);
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
+ VERIFY_ARE_EQUAL(std::string("net2"), options.Networks[1].Name);
+ }
+
+ TEST_METHOD(CreateCommand_ParseDockerNetworkAliases_SetsPerNetworkAliases)
+ {
+ auto invocation =
+ CreateInvocationFromCommandLine(L"wslc --network name=net1,alias=a,alias=b --network name=net2,alias=c ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+ command.ValidateArguments(context.Args);
+
+ wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);
+
+ const auto& options = context.Data.Get();
+ VERIFY_ARE_EQUAL(2u, options.Networks.size());
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
+ VERIFY_ARE_EQUAL(2u, options.Networks[0].Aliases.size());
+ VERIFY_ARE_EQUAL(std::string("a"), options.Networks[0].Aliases[0]);
+ VERIFY_ARE_EQUAL(std::string("b"), options.Networks[0].Aliases[1]);
+ VERIFY_ARE_EQUAL(std::string("net2"), options.Networks[1].Name);
+ VERIFY_ARE_EQUAL(1u, options.Networks[1].Aliases.size());
+ VERIFY_ARE_EQUAL(std::string("c"), options.Networks[1].Aliases[0]);
+ }
+
+ TEST_METHOD(CreateCommand_ParseDockerNetworkAliasesWithNameAfterAlias_SetsPerNetworkAliases)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network alias=a,name=net1,alias=b ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+ command.ValidateArguments(context.Args);
+
+ wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);
+
+ const auto& options = context.Data.Get();
+ VERIFY_ARE_EQUAL(1u, options.Networks.size());
+ VERIFY_ARE_EQUAL(std::string("net1"), options.Networks[0].Name);
+ VERIFY_ARE_EQUAL(2u, options.Networks[0].Aliases.size());
+ VERIFY_ARE_EQUAL(std::string("a"), options.Networks[0].Aliases[0]);
+ VERIFY_ARE_EQUAL(std::string("b"), options.Networks[0].Aliases[1]);
+ }
+
+ TEST_METHOD(CreateCommand_ParseNetworkDuplicateNameOption_ThrowsArgumentException)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network name=net1,name=net2 ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkDuplicateNameError(L"network");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
+ TEST_METHOD(CreateCommand_ParseNetworkUnsupportedOption_ThrowsArgumentException)
+ {
+ auto invocation = CreateInvocationFromCommandLine(
+ L"wslc --network name=net1,driver-opt=com.docker.network.endpoint.sysctls="
+ L"net.ipv4.conf.IFNAME.log_martians=1 ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage =
+ wsl::shared::Localization::WSLCCLI_NetworkUnsupportedOptionError(L"network", L"driver-opt");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
+ TEST_METHOD(CreateCommand_ParseNetworkUnknownOption_ThrowsArgumentException)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network name=net1,aliases=a ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkUnsupportedOptionError(L"network", L"aliases");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
+ TEST_METHOD(CreateCommand_ParseNetworkBackendAliasesOption_ThrowsArgumentException)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network name=net1,Aliases=a ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkUnsupportedOptionError(L"network", L"Aliases");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
+ TEST_METHOD(CreateCommand_ParseNetworkAliasWithoutName_ThrowsArgumentException)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network alias=a ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkEmptyError(L"network");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
+ TEST_METHOD(CreateCommand_ParseNetworkNameWhitespaceValue_ThrowsArgumentException)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network \"name= \" ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkEmptyError(L"network");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
+ TEST_METHOD(ParseNetworkArgument_NameUnicodeWhitespaceValue_ThrowsArgumentException)
+ {
+ VERIFY_THROWS_SPECIFIC(
+ validation::ParseNetworkArgument(L"name=\u3000", L"network"), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkEmptyError(L"network");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
+ TEST_METHOD(CreateCommand_ParseNetworkAliasEmptyValue_ThrowsArgumentException)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network name=net1,alias= ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkAliasEmptyError(L"network");
+ return exception.Message() == expectedMessage;
+ });
}
TEST_METHOD(CreateCommand_ParseNetworkEmptyValue_ThrowsArgumentException)
@@ -445,6 +640,21 @@ class WSLCCLIExecutionUnitTests
command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto&) { return true; });
}
+ TEST_METHOD(CreateCommand_SetContainerOptionsInvalidNetwork_ThrowsArgumentExceptionWithArgumentName)
+ {
+ auto invocation = CreateInvocationFromCommandLine(L"wslc --network name=net1,name=net2 ubuntu sh");
+
+ ContainerCreateCommand command{L""};
+ CLIExecutionContext context;
+ command.ParseArguments(invocation, context.Args);
+
+ VERIFY_THROWS_SPECIFIC(
+ wsl::windows::wslc::task::SetContainerOptionsFromArgs(context), wsl::windows::wslc::ArgumentException, [](const auto& exception) {
+ const auto expectedMessage = wsl::shared::Localization::WSLCCLI_NetworkDuplicateNameError(L"network");
+ return exception.Message() == expectedMessage;
+ });
+ }
+
// Test: Command Line test parsing all cases defined in CommandLineTestCases.h
// This test verifies the command line parsing logic used by the CLI and executes the same
// code as the CLI up to the point of command execution, including parsing and argument validtion.
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
index 39fd029997..78346c50d0 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
@@ -1079,6 +1079,39 @@ class WSLCE2EContainerCreateTests
VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
}
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_DockerStyleMultiNetwork_Success)
+ {
+ const auto secondNetworkName = TestNetworkName + L"-2";
+ EnsureNetworkDoesNotExist(secondNetworkName);
+
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto cleanupNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(TestNetworkName); });
+
+ result = RunWslc(std::format(L"network create --driver bridge {}", secondNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto cleanupSecondNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(secondNetworkName); });
+
+ result = RunWslc(std::format(
+ L"container create --name {} --network name={},alias=db,alias=primary --network name={},alias=cache {} true",
+ WslcContainerName,
+ TestNetworkName,
+ secondNetworkName,
+ DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ const auto networkName = wsl::shared::string::WideToMultiByte(TestNetworkName);
+ const auto secondNetwork = wsl::shared::string::WideToMultiByte(secondNetworkName);
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(secondNetwork));
+ const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
+ const auto& secondEndpoint = inspect.NetworkSettings.Networks.at(secondNetwork);
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "primary") != endpoint.Aliases.end());
+ VERIFY_IS_TRUE(std::ranges::find(secondEndpoint.Aliases, "cache") != secondEndpoint.Aliases.end());
+ }
+
WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_NoNetwork_Rejected)
{
auto result =
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
index 74ac853a64..9903a5d032 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
@@ -927,6 +927,41 @@ class WSLCE2EContainerRunTests
VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
}
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_DockerStyleMultiNetwork_Success)
+ {
+ const auto secondNetworkName = TestNetworkName + L"-2";
+ EnsureNetworkDoesNotExist(secondNetworkName);
+
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto cleanupNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(TestNetworkName); });
+
+ result = RunWslc(std::format(L"network create --driver bridge {}", secondNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto cleanupSecondNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(secondNetworkName); });
+
+ result = RunWslc(std::format(
+ L"container run --name {} --network name={},alias=db,alias=primary "
+ L"--network name={},alias=cache,alias=replica {} true",
+ WslcContainerName,
+ TestNetworkName,
+ secondNetworkName,
+ DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ const auto networkName = wsl::shared::string::WideToMultiByte(TestNetworkName);
+ const auto secondNetwork = wsl::shared::string::WideToMultiByte(secondNetworkName);
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
+ VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(secondNetwork));
+ const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
+ const auto& secondEndpoint = inspect.NetworkSettings.Networks.at(secondNetwork);
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
+ VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "primary") != endpoint.Aliases.end());
+ VERIFY_IS_TRUE(std::ranges::find(secondEndpoint.Aliases, "cache") != secondEndpoint.Aliases.end());
+ VERIFY_IS_TRUE(std::ranges::find(secondEndpoint.Aliases, "replica") != secondEndpoint.Aliases.end());
+ }
+
WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_NoNetwork_Rejected)
{
auto result =