diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 462c7b36a..dffad7fe8 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2505,4 +2505,8 @@ On first run, creates the file with all settings commented out at their defaults Requested load but no input provided. + + Cannot use --no-dns with --dns, --dns-domain, --dns-option, or --dns-search + {Locked="--no-dns"}{Locked="--dns"}{Locked="--dns-domain"}{Locked="--dns-option"}{Locked="--dns-search"}Command line arguments should not be translated + diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index 3748a95cd..ed4f064db 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -41,10 +41,10 @@ _(Command, "command", NO_ALIAS, Kind::Positional, L _(ContainerId, "container-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ContainerIdArgDescription()) \ _(Force, "force", L"f", Kind::Flag, Localization::WSLCCLI_ForceArgDescription()) \ _(Detach, "detach", L"d", Kind::Flag, Localization::WSLCCLI_DetachArgDescription()) \ -/*_(DNS, "dns", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSArgDescription())*/ \ -/*_(DNSDomain, "dns-domain", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSDomainArgDescription())*/ \ -/*_(DNSOption, "dns-option", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSOptionArgDescription())*/ \ -/*_(DNSSearch, "dns-search", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSSearchArgDescription())*/ \ +_(DNS, "dns", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSArgDescription()) \ +_(DNSDomain, "dns-domain", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSDomainArgDescription()) \ +_(DNSOption, "dns-option", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSOptionArgDescription()) \ +_(DNSSearch, "dns-search", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSSearchArgDescription()) \ _(Entrypoint, "entrypoint", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EntrypointArgDescription()) \ _(Env, "env", L"e", Kind::Value, Localization::WSLCCLI_EnvArgDescription()) \ _(EnvFile, "env-file", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EnvFileArgDescription()) \ @@ -59,7 +59,7 @@ _(ImageId, "image", NO_ALIAS, Kind::Positional, L _(Input, "input", L"i", Kind::Value, Localization::WSLCCLI_InputArgDescription()) \ _(Interactive, "interactive", L"i", Kind::Flag, Localization::WSLCCLI_InteractiveArgDescription()) \ _(Name, "name", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NameArgDescription()) \ -/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/ \ +_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription()) \ _(NoPrune, "no-prune", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoPruneArgDescription()) \ _(NoTrunc, "no-trunc", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoTruncArgDescription()) \ _(Output, "output", L"o", Kind::Value, Localization::WSLCCLI_OutputArgDescription()) \ diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index ea47a5f55..7e5303a28 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -47,6 +47,16 @@ void Argument::Validate(const ArgMap& execArgs) const validation::ValidateVolumeMount(execArgs.GetAll()); break; + case ArgType::NoDNS: + { + if (execArgs.Contains(ArgType::DNS) || execArgs.Contains(ArgType::DNSDomain) || + execArgs.Contains(ArgType::DNSOption) || execArgs.Contains(ArgType::DNSSearch)) + { + throw ArgumentException(Localization::WSLCCLI_NoDNSConflictError()); + } + break; + } + case ArgType::WorkDir: { const auto& value = execArgs.Get(); diff --git a/src/windows/wslc/commands/ContainerCreateCommand.cpp b/src/windows/wslc/commands/ContainerCreateCommand.cpp index 4150d0af5..d1d7c574b 100644 --- a/src/windows/wslc/commands/ContainerCreateCommand.cpp +++ b/src/windows/wslc/commands/ContainerCreateCommand.cpp @@ -32,17 +32,17 @@ std::vector ContainerCreateCommand::GetArguments() const Argument::Create(ArgType::Command), Argument::Create(ArgType::ForwardArgs), // Argument::Create(ArgType::CIDFile), - // Argument::Create(ArgType::DNS), - // Argument::Create(ArgType::DNSDomain), - // Argument::Create(ArgType::DNSOption), - // Argument::Create(ArgType::DNSSearch), + Argument::Create(ArgType::DNS, false, NO_LIMIT), + Argument::Create(ArgType::DNSDomain), + Argument::Create(ArgType::DNSOption, false, NO_LIMIT), + Argument::Create(ArgType::DNSSearch, false, NO_LIMIT), Argument::Create(ArgType::Entrypoint), Argument::Create(ArgType::Env, false, NO_LIMIT), Argument::Create(ArgType::EnvFile, false, NO_LIMIT), // Argument::Create(ArgType::GroupId), Argument::Create(ArgType::Interactive), Argument::Create(ArgType::Name), - // Argument::Create(ArgType::NoDNS), + Argument::Create(ArgType::NoDNS), // Argument::Create(ArgType::Progress), Argument::Create(ArgType::Publish, false, NO_LIMIT), Argument::Create(ArgType::Remove), diff --git a/src/windows/wslc/commands/ContainerRunCommand.cpp b/src/windows/wslc/commands/ContainerRunCommand.cpp index e2810cd5f..0c1a7c926 100644 --- a/src/windows/wslc/commands/ContainerRunCommand.cpp +++ b/src/windows/wslc/commands/ContainerRunCommand.cpp @@ -33,16 +33,16 @@ std::vector ContainerRunCommand::GetArguments() const Argument::Create(ArgType::ForwardArgs), // Argument::Create(ArgType::CIDFile), Argument::Create(ArgType::Detach), - // Argument::Create(ArgType::DNS), - // Argument::Create(ArgType::DNSDomain), - // Argument::Create(ArgType::DNSOption), - // Argument::Create(ArgType::DNSSearch), + Argument::Create(ArgType::DNS, false, NO_LIMIT), + Argument::Create(ArgType::DNSDomain), + Argument::Create(ArgType::DNSOption, false, NO_LIMIT), + Argument::Create(ArgType::DNSSearch, false, NO_LIMIT), Argument::Create(ArgType::Entrypoint), Argument::Create(ArgType::Env, false, NO_LIMIT), Argument::Create(ArgType::EnvFile, false, NO_LIMIT), Argument::Create(ArgType::Interactive), Argument::Create(ArgType::Name), - // Argument::Create(ArgType::NoDNS), + Argument::Create(ArgType::NoDNS), // Argument::Create(ArgType::Progress), Argument::Create(ArgType::Publish, false, NO_LIMIT), // Argument::Create(ArgType::Pull), diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index c4da81fab..9b459abe5 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -42,6 +42,11 @@ struct ContainerOptions std::vector Entrypoint; std::optional User{}; std::vector Tmpfs; + std::vector DnsServers; + std::string DnsDomain; + std::vector DnsOptions; + std::vector DnsSearchDomains; + bool NoDns = false; }; struct CreateContainerResult diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 05a5cc295..59de12430 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -107,6 +107,30 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& sessio containerLauncher.AddTmpfs(tmpfsMount.ContainerPath(), tmpfsMount.Options()); } + if (!options.DnsServers.empty()) + { + auto dnsServers = options.DnsServers; + containerLauncher.SetDnsServers(std::move(dnsServers)); + } + + if (!options.DnsDomain.empty()) + { + auto domainname = options.DnsDomain; + containerLauncher.SetDomainname(std::move(domainname)); + } + + if (!options.DnsOptions.empty()) + { + auto dnsOptions = options.DnsOptions; + containerLauncher.SetDnsOptions(std::move(dnsOptions)); + } + + if (!options.DnsSearchDomains.empty()) + { + auto dnsSearchDomains = options.DnsSearchDomains; + containerLauncher.SetDnsSearchDomains(std::move(dnsSearchDomains)); + } + auto [result, runningContainer] = containerLauncher.CreateNoThrow(*session.Get()); if (result == WSLC_E_IMAGE_NOT_FOUND) { diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 55e19e478..72a20a1cc 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -293,6 +293,46 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context) } } + if (context.Args.Contains(ArgType::DNS)) + { + auto dnsServers = context.Args.GetAll(); + options.DnsServers.reserve(options.DnsServers.size() + dnsServers.size()); + for (const auto& value : dnsServers) + { + options.DnsServers.emplace_back(WideToMultiByte(value)); + } + } + + if (context.Args.Contains(ArgType::DNSDomain)) + { + options.DnsDomain = WideToMultiByte(context.Args.Get()); + } + + if (context.Args.Contains(ArgType::DNSOption)) + { + auto dnsOptions = context.Args.GetAll(); + options.DnsOptions.reserve(options.DnsOptions.size() + dnsOptions.size()); + for (const auto& value : dnsOptions) + { + options.DnsOptions.emplace_back(WideToMultiByte(value)); + } + } + + if (context.Args.Contains(ArgType::DNSSearch)) + { + auto dnsSearchDomains = context.Args.GetAll(); + options.DnsSearchDomains.reserve(options.DnsSearchDomains.size() + dnsSearchDomains.size()); + for (const auto& value : dnsSearchDomains) + { + options.DnsSearchDomains.emplace_back(WideToMultiByte(value)); + } + } + + if (context.Args.Contains(ArgType::NoDNS)) + { + options.NoDns = true; + } + if (context.Args.Contains(ArgType::ForwardArgs)) { auto const& forwardArgs = context.Args.Get(); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index cda1f24b1..4bad19f6c 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -1081,6 +1081,82 @@ class WSLCE2EContainerCreateTests result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_SingleServer) + { + + auto result = RunWslc(std::format( + L"container create --name {} --dns 8.8.8.8 {} cat /etc/resolv.conf", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_MultipleServers) + { + + auto result = RunWslc(std::format( + L"container create --name {} --dns 8.8.8.8 --dns 8.8.4.4 {} cat /etc/resolv.conf", + WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.4.4") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_Search) + { + + auto result = RunWslc(std::format( + L"container create --name {} --dns-search example.com {} cat /etc/resolv.conf", + WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"search example.com") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_Option) + { + + auto result = RunWslc(std::format( + L"container create --name {} --dns-option ndots:5 {} cat /etc/resolv.conf", + WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:5") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_AllOptions) + { + + auto result = RunWslc(std::format( + L"container create --name {} --dns 1.1.1.1 --dns-search test.local --dns-option ndots:3 {} cat /etc/resolv.conf", + WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 1.1.1.1") != std::wstring::npos); + VERIFY_IS_TRUE(result.Stdout->find(L"search test.local") != std::wstring::npos); + VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:3") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NoDNS_ConflictWithDNS_Fails) + { + + auto result = RunWslc(std::format( + L"container create --name {} --no-dns --dns 8.8.8.8 {} echo test", + WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Cannot use --no-dns with --dns, --dns-domain, --dns-option, or --dns-search\r\n", .ExitCode = 1}); + } + private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; @@ -1146,11 +1222,16 @@ class WSLCE2EContainerCreateTests { std::wstringstream options; options << L"The following options are available:\r\n" // + << L" --dns IP address of the DNS nameserver in resolv.conf\r\n" + << L" --dns-domain Set the default DNS Domain\r\n" + << L" --dns-option Set DNS options\r\n" + << L" --dns-search Set DNS search domains\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n" << L" --env-file File containing key=value pairs of env variables\r\n" << L" -i,--interactive Attach to stdin and keep it open\r\n" << L" --name Name of the container\r\n" + << L" --no-dns No configuration of DNS in the container\r\n" << L" -p,--publish Publish a port from a container to host\r\n" << L" --rm Remove the container after it stops\r\n" << L" --session Specify the session to use\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 85f166890..f2201fda1 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -170,6 +170,63 @@ class WSLCE2EContainerRunTests result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_SingleServer) + { + + auto result = RunWslc(std::format( + L"container run --rm --dns 8.8.8.8 {} cat /etc/resolv.conf", DebianImage.NameAndTag())); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_MultipleServers) + { + + auto result = RunWslc(std::format( + L"container run --rm --dns 8.8.8.8 --dns 8.8.4.4 {} cat /etc/resolv.conf", DebianImage.NameAndTag())); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.4.4") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_Search) + { + + auto result = RunWslc(std::format( + L"container run --rm --dns-search example.com {} cat /etc/resolv.conf", DebianImage.NameAndTag())); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"search example.com") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_Option) + { + + auto result = RunWslc(std::format( + L"container run --rm --dns-option ndots:5 {} cat /etc/resolv.conf", DebianImage.NameAndTag())); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:5") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_AllOptions) + { + + auto result = RunWslc(std::format( + L"container run --rm --dns 1.1.1.1 --dns-search test.local --dns-option ndots:3 {} cat /etc/resolv.conf", + DebianImage.NameAndTag())); + result.Verify({.ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 1.1.1.1") != std::wstring::npos); + VERIFY_IS_TRUE(result.Stdout->find(L"search test.local") != std::wstring::npos); + VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:3") != std::wstring::npos); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NoDNS_ConflictWithDNS_Fails) + { + + auto result = RunWslc(std::format( + L"container run --rm --no-dns --dns 8.8.8.8 {} echo test", DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Cannot use --no-dns with --dns, --dns-domain, --dns-option, or --dns-search\r\n", .ExitCode = 1}); + } + private: const std::wstring WslcContainerName = L"wslc-test-container"; const TestImage& DebianImage = DebianTestImage(); @@ -212,11 +269,16 @@ class WSLCE2EContainerRunTests std::wstringstream options; options << L"The following options are available:\r\n" << L" -d,--detach Run container in detached mode\r\n" + << L" --dns IP address of the DNS nameserver in resolv.conf\r\n" + << L" --dns-domain Set the default DNS Domain\r\n" + << L" --dns-option Set DNS options\r\n" + << L" --dns-search Set DNS search domains\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n" << L" --env-file File containing key=value pairs of env variables\r\n" << L" -i,--interactive Attach to stdin and keep it open\r\n" << L" --name Name of the container\r\n" + << L" --no-dns No configuration of DNS in the container\r\n" << L" -p,--publish Publish a port from a container to host\r\n" << L" --rm Remove the container after it stops\r\n" << L" --session Specify the session to use\r\n"