Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions src/platform/backends/hyperv_api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ if(WIN32)
hcn/hyperv_hcn_api.cpp
hcn/hyperv_hcn_route.cpp
hcn/hyperv_hcn_subnet.cpp
hcn/hyperv_hcn_dns.cpp
hcn/hyperv_hcn_ipam.cpp
hcn/hyperv_hcn_network_policy.cpp
hcn/hyperv_hcn_create_endpoint_params.cpp
hcn/hyperv_hcn_create_network_params.cpp
hcn/hyperv_hcn_endpoint_query.cpp
hcn/hyperv_hcn_network_info.cpp
dns/nrpt_rule.cpp
hcs/hyperv_hcs_wrapper.cpp
hcs/hyperv_hcs_event_type.cpp
hcs/hyperv_hcs_scsi_device.cpp
Expand All @@ -77,5 +79,7 @@ if(WIN32)
computecore.lib
computenetwork.lib
virtdisk.lib
advapi32.lib
rpcrt4.lib
)
endif()
193 changes: 193 additions & 0 deletions src/platform/backends/hyperv_api/dns/nrpt_rule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#include <hyperv_api/dns/nrpt_rule.h>

// clang-format off
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <rpc.h>
// clang-format on

#include <fmt/format.h>

#include <stdexcept>

namespace multipass::hyperv::dns
{

namespace
{
// Local (non-group-policy) NRPT registry location, as used by Add-DnsClientNrptRule.
constexpr auto nrpt_base = LR"(SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig)";

// ConfigOptions bitmask value meaning "use the provided override DNS resolvers".
constexpr DWORD nrpt_override_dns = 0x8;

std::wstring widen(const std::string& s)
{
if (s.empty())
return {};
const int size =
::MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0);
std::wstring out(static_cast<std::size_t>(size), L'\0');
::MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), out.data(), size);
return out;
}

std::string generate_rule_guid()
{
UUID uuid{};
if (::UuidCreate(&uuid) != RPC_S_OK)
throw std::runtime_error{"NRPT: failed to generate rule GUID"};

RPC_CSTR str = nullptr;
if (::UuidToStringA(&uuid, &str) != RPC_S_OK)
throw std::runtime_error{"NRPT: failed to format rule GUID"};

std::string result = fmt::format("{{{}}}", reinterpret_cast<char*>(str));
::RpcStringFreeA(&str);
return result;
}

std::wstring rule_key_path(const std::string& guid)
{
return std::wstring{nrpt_base} + L"\\" + widen(guid);
}
} // namespace

NrptRule::NrptRule(const std::string& dns_namespace, const std::vector<std::string>& name_servers)
: rule_guid{generate_rule_guid()}
{
HKEY key = nullptr;
const auto key_path = rule_key_path(rule_guid);
if (const auto status = ::RegCreateKeyExW(HKEY_LOCAL_MACHINE,
key_path.c_str(),
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE,
nullptr,
&key,
nullptr);
status != ERROR_SUCCESS)
{
rule_guid.clear();
throw std::runtime_error{
fmt::format("NRPT: failed to create registry key (error {})", status)};
}

const auto fail = [&](const char* what, LONG status) {
::RegCloseKey(key);
::RegDeleteKeyW(HKEY_LOCAL_MACHINE, key_path.c_str());
rule_guid.clear();
throw std::runtime_error{fmt::format("NRPT: failed to set {} (error {})", what, status)};
};

// Version (DWORD): NRPT rule schema version.
const DWORD version = 1;
if (const auto s = ::RegSetValueExW(key,
L"Version",
0,
REG_DWORD,
reinterpret_cast<const BYTE*>(&version),
sizeof(version));
s != ERROR_SUCCESS)
fail("Version", s);

// Name (REG_MULTI_SZ): the matched namespace(s), each with a leading dot.
{
std::wstring multi = widen(dns_namespace);
multi.push_back(L'\0'); // terminate the single entry
multi.push_back(L'\0'); // terminate the list
if (const auto s = ::RegSetValueExW(
key,
L"Name",
0,
REG_MULTI_SZ,
reinterpret_cast<const BYTE*>(multi.data()),
static_cast<DWORD>(multi.size() * sizeof(wchar_t)));
s != ERROR_SUCCESS)
fail("Name", s);
}

// GenericDNSServers (REG_SZ): the override resolvers, ';'-separated.
{
std::string joined;
for (std::size_t i = 0; i < name_servers.size(); ++i)
{
if (i != 0)
joined += ';';
joined += name_servers[i];
}
const std::wstring servers = widen(joined);
if (const auto s = ::RegSetValueExW(
key,
L"GenericDNSServers",
0,
REG_SZ,
reinterpret_cast<const BYTE*>(servers.c_str()),
static_cast<DWORD>((servers.size() + 1) * sizeof(wchar_t)));
s != ERROR_SUCCESS)
fail("GenericDNSServers", s);
}

// ConfigOptions (DWORD): direct matching queries to the override resolvers.
if (const auto s = ::RegSetValueExW(key,
L"ConfigOptions",
0,
REG_DWORD,
reinterpret_cast<const BYTE*>(&nrpt_override_dns),
sizeof(nrpt_override_dns));
s != ERROR_SUCCESS)
fail("ConfigOptions", s);

::RegCloseKey(key);
}

NrptRule::~NrptRule()
{
remove();
}

NrptRule::NrptRule(NrptRule&& other) noexcept : rule_guid{std::move(other.rule_guid)}
{
other.rule_guid.clear();
}

NrptRule& NrptRule::operator=(NrptRule&& other) noexcept
{
if (this != &other)
{
remove();
rule_guid = std::move(other.rule_guid);
other.rule_guid.clear();
}
return *this;
}

void NrptRule::remove() noexcept
{
if (rule_guid.empty())
return;
::RegDeleteKeyW(HKEY_LOCAL_MACHINE, rule_key_path(rule_guid).c_str());
rule_guid.clear();
}

} // namespace multipass::hyperv::dns
78 changes: 78 additions & 0 deletions src/platform/backends/hyperv_api/dns/nrpt_rule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#pragma once

#include <string>
#include <vector>

namespace multipass::hyperv::dns
{

/**
* RAII wrapper around a single local Windows Name Resolution Policy Table (NRPT) rule.
*
* An NRPT rule routes DNS queries for a namespace (e.g. ".multipass.test") to a
* specific set of DNS servers, bypassing the default resolver path. This is the
* host-side glue that points a custom suffix at the Multipass ICS DNS shim.
*
* The rule is written directly to the registry under
* HKLM\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\{GUID}
* which is exactly what `Add-DnsClientNrptRule` does, so no PowerShell dependency is
* required. The rule is removed on destruction; it is safe to call @ref remove early.
*
* Creating a rule requires administrative privileges (write access to HKLM).
*/
class NrptRule
{
public:
/**
* Create and apply an NRPT rule.
*
* @param dns_namespace The namespace to match, including the leading dot
* (e.g. ".multipass.test").
* @param name_servers The DNS server IPs to route the namespace to.
* @throws std::runtime_error if the rule could not be written to the registry.
*/
NrptRule(const std::string& dns_namespace, const std::vector<std::string>& name_servers);

~NrptRule();

NrptRule(NrptRule&& other) noexcept;
NrptRule& operator=(NrptRule&& other) noexcept;

NrptRule(const NrptRule&) = delete;
NrptRule& operator=(const NrptRule&) = delete;

/**
* Remove the rule from the registry. Idempotent; safe to call more than once.
*/
void remove() noexcept;

/**
* The registry rule identifier, formatted as "{GUID}".
*/
const std::string& guid() const noexcept
{
return rule_guid;
}

private:
std::string rule_guid{};
};

} // namespace multipass::hyperv::dns
7 changes: 7 additions & 0 deletions src/platform/backends/hyperv_api/hcn/hyperv_hcn_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ HRESULT HCNAPI::HcnQueryNetworkProperties(HCN_NETWORK Network,
{
return ::HcnQueryNetworkProperties(Network, Query, Properties, ErrorRecord);
}
HRESULT HCNAPI::HcnQueryEndpointProperties(HCN_ENDPOINT Endpoint,
PCWSTR Query,
PWSTR* Properties,
PWSTR* ErrorRecord) const
{
return ::HcnQueryEndpointProperties(Endpoint, Query, Properties, ErrorRecord);
}
void HCNAPI::CoTaskMemFree(LPVOID pv) const
{
::CoTaskMemFree(pv);
Expand Down
4 changes: 4 additions & 0 deletions src/platform/backends/hyperv_api/hcn/hyperv_hcn_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ struct HCNAPI : public Singleton<HCNAPI>
PCWSTR Query,
PWSTR* Properties,
PWSTR* ErrorRecord) const;
[[nodiscard]] virtual HRESULT HcnQueryEndpointProperties(HCN_ENDPOINT Endpoint,
PCWSTR Query,
PWSTR* Properties,
PWSTR* ErrorRecord) const;

virtual void CoTaskMemFree(LPVOID pv) const;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,27 @@ auto fmt::formatter<CreateNetworkParameters, Char>::format(const CreateNetworkPa
"Flags": {3},
"Policies": [
{4}
]
]{5}
}}
)json");

// The "Dns" object is optional, so it is only emitted when specified. When
// absent the fragment is empty, leaving the rest of the document untouched.
std::basic_string<Char> dns_fragment{};
if (params.dns.has_value())
{
static constexpr auto dns_template = string_literal<Char>(R"json(,
"Dns": {0})json");
dns_fragment = dns_template.format(params.dns.value());
}

return json_template.format_to(ctx,
params.name,
params.type,
fmt::join(params.ipams, string_literal<Char>(",")),
fmt::underlying(params.flags),
fmt::join(params.policies, string_literal<Char>(",")));
fmt::join(params.policies, string_literal<Char>(",")),
dns_fragment);
}

template auto fmt::formatter<CreateNetworkParameters, char>::format<fmt::format_context>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@

#pragma once

#include <hyperv_api/hcn/hyperv_hcn_dns.h>
#include <hyperv_api/hcn/hyperv_hcn_ipam.h>
#include <hyperv_api/hcn/hyperv_hcn_network_flags.h>
#include <hyperv_api/hcn/hyperv_hcn_network_policy.h>
#include <hyperv_api/hcn/hyperv_hcn_network_type.h>

#include <fmt/format.h>

#include <optional>
#include <vector>

namespace multipass::hyperv::hcn
Expand Down Expand Up @@ -63,6 +65,14 @@ struct CreateNetworkParameters
* Network policies
*/
std::vector<HcnNetworkPolicy> policies;

/**
* DNS settings for the network (optional).
*
* When set, allows specifying the DNS suffix (domain), suffix search
* list and DNS servers for the network.
*/
std::optional<HcnDns> dns{};
};

} // namespace multipass::hyperv::hcn
Expand Down
Loading
Loading