Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -2884,6 +2884,14 @@ On first run, creates the file with all settings commented out at their defaults
<value>Invalid {} value: network name cannot be empty or whitespace</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="WSLCCLI_NetworkDuplicateNameError" xml:space="preserve">
<value>Invalid {} value: network name can only be specified once</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="WSLCCLI_NetworkUnsupportedOptionError" xml:space="preserve">
<value>Invalid {} value: unsupported network option '{}'</value>
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
Comment thread
danegsta marked this conversation as resolved.
<data name="WSLCCLI_NetworkHostModeNotSupportedError" xml:space="preserve">
<value>host mode networking is not supported</value>
</data>
Expand Down
41 changes: 41 additions & 0 deletions src/shared/inc/stringshared.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ Module Name:
--*/

#pragma once
#include <algorithm>
#include <cctype>
#include <cwctype>
#include <set>
#include <vector>
#include <string>
#include <string_view>
#include <sstream>
#include <fstream>
#include <gsl/gsl>
#include <format>
#include <source_location>
#include <type_traits>

#ifndef WIN32
#include <string.h>
Expand Down Expand Up @@ -98,6 +103,27 @@ inline std::vector<std::basic_string<T>> Split(const std::basic_string<T>& Strin
return Output;
}

template <class T>
inline std::vector<std::basic_string_view<T>> SplitPreserveEmpty(const std::basic_string_view<T> String, T Separator)
{
std::vector<std::basic_string_view<T>> Output;
size_t Start = 0;
while (Start <= String.size())
{
const auto End = String.find(Separator, Start);
if (End == std::basic_string_view<T>::npos)
{
Output.emplace_back(String.substr(Start));
break;
}

Output.emplace_back(String.substr(Start, End - Start));
Start = End + 1;
}

return Output;
}

template <class T>
inline std::vector<std::basic_string<T>> SplitByMultipleSeparators(const std::basic_string<T>& String, const std::basic_string<T>& Separators)
{
Expand Down Expand Up @@ -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 <class T>
inline bool IsEmptyOrWhitespace(const std::basic_string_view<T> String)
{
return String.empty() || std::all_of(String.begin(), String.end(), [](T Ch) {
if constexpr (std::is_same_v<T, wchar_t>)
{
return std::iswspace(static_cast<wint_t>(Ch));
}
else
{
return std::isspace(static_cast<unsigned char>(Ch));
}
});
}

template <typename T>
inline std::optional<bool> ParseBool(const T* String)
{
Expand Down
32 changes: 25 additions & 7 deletions src/windows/common/WSLCContainerLauncher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,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<std::string>& Aliases)
{
m_additionalNetworks.push_back({.Name = Name, .Aliases = Aliases});
}

void wsl::windows::common::WSLCContainerLauncher::AddPrimaryNetworkAlias(const std::string& Alias)
Expand Down Expand Up @@ -379,24 +384,37 @@ std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::C
// Each additional network becomes an entry in NetworkingConfig.EndpointsConfig.
std::vector<WSLCNetworkConnection> connections;
connections.reserve(m_additionalNetworks.size());
std::vector<std::vector<KeyValuePair>> 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<ULONG>(settings.size()),
});
}

options.ContainerNetwork.Networks = connections.empty() ? nullptr : connections.data();
options.ContainerNetwork.NetworksCount = static_cast<ULONG>(connections.size());

// Aliases for the primary endpoint.
std::vector<KeyValuePair> aliasKvps;
aliasKvps.reserve(m_primaryNetworkAliases.size());
std::vector<KeyValuePair> 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<ULONG>(aliasKvps.size());
options.ContainerNetwork.Settings = primarySettings.empty() ? nullptr : primarySettings.data();
options.ContainerNetwork.SettingsCount = static_cast<ULONG>(primarySettings.size());

options.MemoryBytes = m_memoryBytes;
options.NanoCpus = m_nanoCpus;
Expand Down
9 changes: 8 additions & 1 deletion src/windows/common/WSLCContainerLauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>& Aliases);
void AddPrimaryNetworkAlias(const std::string& Alias);

std::pair<HRESULT, std::optional<RunningWSLCContainer>> CreateNoThrow(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
Expand Down Expand Up @@ -93,6 +94,12 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
using WSLCProcessLauncher::SetWorkingDirectory;

private:
struct NetworkConnection
{
std::string Name;
std::vector<std::string> Aliases;
};

std::string m_image;
std::string m_name;
std::vector<WSLCPortMapping> m_ports;
Expand All @@ -112,7 +119,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
std::vector<std::string> m_dnsServers;
std::vector<std::string> m_dnsSearchDomains;
std::vector<std::string> m_dnsOptions;
std::vector<std::string> m_additionalNetworks;
std::vector<NetworkConnection> m_additionalNetworks;
std::vector<std::string> m_primaryNetworkAliases;
std::vector<WSLCLabel> m_labels;
std::deque<std::string> m_labelKeys;
Expand Down
3 changes: 3 additions & 0 deletions src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,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;
Expand Down
100 changes: 87 additions & 13 deletions src/windows/wslc/arguments/ArgumentValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,7 @@ void Argument::Validate(const ArgMap& execArgs) const

case ArgType::Network:
{
for (const auto& value : execArgs.GetAll<ArgType::Network>())
{
if (value.empty() ||
std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
{
throw ArgumentException(Localization::WSLCCLI_NetworkEmptyError(m_name));
}

if (IsEqual(value, L"host", true))
{
throw ArgumentException(Localization::WSLCCLI_NetworkHostModeNotSupportedError());
}
}
validation::ValidateNetwork(execArgs.GetAll<ArgType::Network>(), m_name);
break;
}

Expand Down Expand Up @@ -192,6 +180,18 @@ void ValidateFilter(const std::vector<std::wstring>& values)
}
}

void ValidateNetwork(const std::vector<std::wstring>& 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)
{
Expand Down Expand Up @@ -494,6 +494,80 @@ std::tuple<std::string, int64_t, int64_t> 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")
Comment thread
danegsta marked this conversation as resolved.
{
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});
}
Comment thread
danegsta marked this conversation as resolved.
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));
}
Comment thread
danegsta marked this conversation as resolved.

return result;
}

std::pair<std::string, std::string> ParseLabel(const std::wstring& value)
{
std::pair<std::string, std::string> result{};
Expand Down
10 changes: 10 additions & 0 deletions src/windows/wslc/arguments/ArgumentValidation.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Module Name:
#include "ContainerModel.h"
#include "InspectModel.h"
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
#include <charconv>
Expand Down Expand Up @@ -82,9 +83,18 @@ InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstri
void ValidateGpus(const std::vector<std::wstring>& values, const std::wstring& argName);
void ValidateVolumeMount(const std::vector<std::wstring>& values);
void ValidateFilter(const std::vector<std::wstring>& values);
void ValidateNetwork(const std::vector<std::wstring>& values, const std::wstring& argName);

std::pair<std::string, std::string> ParseLabel(const std::wstring& value);
std::pair<std::string, std::string> ParseDriverOption(const std::wstring& value);
std::pair<std::string, std::string> ParseFilter(const std::wstring& value);

struct ParsedNetworkArgument
{
std::string Name;
std::vector<std::string> Aliases;
};

ParsedNetworkArgument ParseNetworkArgument(std::wstring_view value, const std::wstring& argName = {});

} // namespace wsl::windows::wslc::validation
10 changes: 9 additions & 1 deletion src/windows/wslc/services/ContainerModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ Module Name:

#include <wslservice.h>
#include <wslc.h>
#include <optional>
#include <string>
#include <vector>

namespace wsl::windows::wslc::models {

Expand All @@ -27,6 +29,12 @@ enum class FormatType
Json,
};

struct ContainerNetwork
{
std::string Name;
std::vector<std::string> Aliases;
};

struct ContainerOptions
{
std::vector<std::string> Arguments;
Expand All @@ -51,7 +59,7 @@ struct ContainerOptions
std::vector<std::string> DnsServers;
std::vector<std::string> DnsSearchDomains;
std::vector<std::string> DnsOptions;
std::vector<std::string> Networks;
std::vector<ContainerNetwork> Networks;
std::vector<std::string> NetworkAliases;
std::vector<std::string> Tmpfs;
std::vector<std::pair<std::string, std::string>> Labels;
Expand Down
Loading
Loading