OpenVPN 3 Core Library
Loading...
Searching...
No Matches
dns.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2024- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12//
13
45#pragma once
46
47#include <array>
48
49#include <winternl.h>
50
53#include <openvpn/win/reg.hpp>
55
56namespace openvpn::TunWin {
57
64template <typename REG, typename NETAPI>
65class Dns
66{
73 static constexpr std::array<PCWSTR, 2> searchlist_subkeys{
74 LR"(SOFTWARE\Policies\Microsoft\WindowsNT\DNSClient)",
75 LR"(System\CurrentControlSet\Services\TCPIP\Parameters)"};
76
86 static std::pair<typename REG::Key, bool> open_searchlist_key()
87 {
88 for (const auto subkey : searchlist_subkeys)
89 {
90 typename REG::Key key(subkey);
91 if (key.defined())
92 {
93 auto [list, error] = REG::get_string(key, L"SearchList");
94 if (!error && !list.empty())
95 {
96 return {std::move(key), true};
97 }
98 else if (subkey == searchlist_subkeys.back())
99 {
100 // Return the local subkey (last in the list) as default
101 return {std::move(key), false};
102 }
103 }
104 }
105 return {typename REG::Key{}, false};
106 }
107
114 static bool initial_searchlist_exists(typename REG::Key &key)
115 {
116 auto [value, error] = REG::get_string(key, L"InitialSearchList");
117 return error ? false : true;
118 }
119
130 static bool set_initial_searchlist(typename REG::Key &key, const std::wstring &searchlist)
131 {
132 LSTATUS err;
133 err = REG::set_string(key, L"InitialSearchList", searchlist);
134 if (err)
135 {
136 return false;
137 }
138 err = REG::set_string(key, L"SearchList", searchlist);
139 if (err)
140 {
141 return false;
142 }
143 return true;
144 }
145
152 static bool set_initial_searchlist_from_existing(typename REG::Key &key)
153 {
154 auto [searchlist, error] = REG::get_string(key, L"SearchList");
155 if (error)
156 {
157 return false;
158 }
159
160 /* Store a copy of the original list */
161 LSTATUS err = REG::set_string(key, L"OriginalSearchList", searchlist);
162 if (err)
163 {
164 return false;
165 }
166
167 return set_initial_searchlist(key, searchlist);
168 }
169
176 static bool set_initial_searchlist_from_domains(typename REG::Key &key)
177 {
178 std::wstring list;
179
180 {
181 // Add primary domain to the list, if exists
182 typename REG::Key tcpip_params(LR"(SYSTEM\CurrentControlSet\Services\Tcpip\Parameters)");
183 auto [domain, error] = REG::get_string(tcpip_params, L"Domain");
184 if (!error && !domain.empty())
185 {
186 list.append(domain);
187 }
188 }
189
190 typename REG::Key itfs(REG::subkey_ipv4_itfs);
191 typename REG::KeyEnumerator itf_guids(itfs);
192 for (const auto &itf_guid : itf_guids)
193 {
194 // Ignore interfaces that are not connected or disabled
195 if (!NETAPI::interface_connected(itf_guid))
196 {
197 continue;
198 }
199
200 // Get the DNS domain for routing
201 std::wstring domain = interface_dns_domain<REG>(itf_guid);
202 if (domain.empty())
203 {
204 continue;
205 }
206
207 // FIXME: expand domain if "UseDomainNameDevolution" is set?
208 if (list.size())
209 {
210 list.append(L",");
211 }
212 list.append(domain);
213 }
214
215 return set_initial_searchlist(key, list);
216 }
217
226 static bool set_itf_domain_suffix(const std::string &itf_name, const std::wstring &domain)
227 {
228 const std::wstring iid = NETAPI::get_itf_id(itf_name);
229 if (iid.empty())
230 {
231 return false;
232 }
233
234 typename REG::Key itf_key(std::wstring(REG::subkey_ipv4_itfs) + L"\\" + iid);
235 PCWSTR name = dhcp_enabled_on_itf<REG>(itf_key) ? L"DhcpDomain" : L"Domain";
236 LSTATUS err = REG::set_string(itf_key, name, domain);
237 if (err)
238 {
239 return false;
240 }
241
242 return true;
243 }
244
253 static bool add_to_searchlist(typename REG::Key &key, const std::wstring &domains)
254 {
255 auto [list, error] = REG::get_string(key, L"SearchList");
256 if (error)
257 {
258 return false;
259 }
260 if (list.size())
261 {
262 list.append(L",");
263 }
264 list.append(domains);
265
266 LSTATUS err = REG::set_string(key, L"SearchList", list);
267 return err ? false : true;
268 }
269
270 public:
272
285 static void set_search_domains(const std::string &itf_name, const std::string &domains)
286 {
287 if (domains.empty())
288 {
289 return;
290 }
291
292 auto [list_key, list_exists] = open_searchlist_key();
293 bool initial_list_exists = initial_searchlist_exists(list_key);
294 bool single_domain = domains.find(',') == domains.npos;
295 if (!initial_list_exists)
296 {
297 if (list_exists)
298 {
300 {
301 return;
302 }
303 }
304 else if (!single_domain)
305 {
307 {
308 return;
309 }
310 }
311 }
312
313 std::wstring wide_domains = wstring::from_utf8(domains);
314 bool success_adding = single_domain && !list_exists
315 ? set_itf_domain_suffix(itf_name, wide_domains)
316 : add_to_searchlist(list_key, wide_domains);
317 if (!success_adding)
318 {
319 remove_search_domains(itf_name, domains);
320 }
321 }
322
331 static void reset_search_domains(typename REG::Key &list_key)
332 {
333 auto [originallist, error] = REG::get_string(list_key, L"OriginalSearchList");
334 if (!error || error == ERROR_FILE_NOT_FOUND)
335 {
336 // Restore the original search list or set a empty list
337 REG::set_string(list_key, L"SearchList", originallist);
338 }
339
340 REG::delete_value(list_key, L"InitialSearchList");
341 REG::delete_value(list_key, L"OriginalSearchList");
342 }
343
354 static void remove_search_domains(const std::string &itf_name, const std::string &domains)
355 {
356 if (domains.empty())
357 {
358 return;
359 }
360
361 set_itf_domain_suffix(itf_name, {});
362
363 auto [list_key, list_exists] = open_searchlist_key();
364 if (list_exists)
365 {
366 auto [searchlist, error] = REG::get_string(list_key, L"SearchList");
367 if (error)
368 {
369 return;
370 }
371
372 // Remove domains from list
373 const std::wstring wdomains = wstring::from_utf8(domains);
374 auto pos = searchlist.find(wdomains);
375 if (pos != searchlist.npos)
376 {
377 // Domains are in the search list, remove them
378 if (searchlist.size() == wdomains.size())
379 {
380 // No other domains in the list
381 searchlist.clear();
382 }
383 else if (pos == 0)
384 {
385 // Also remove trailing comma
386 searchlist.erase(pos, wdomains.size() + 1);
387 }
388 else
389 {
390 // Also remove leading comma
391 searchlist.erase(pos - 1, wdomains.size() + 1);
392 }
393 }
394
395 auto [initiallist, err] = REG::get_string(list_key, L"InitialSearchList");
396 if (err)
397 {
398 return;
399 }
400
401 // Compare shortened list with initial list
402 if (searchlist == initiallist)
403 {
404 // Reset everything to the original state
405 reset_search_domains(list_key);
406 }
407 else
408 {
409 // Store the shortened search list
410 REG::set_string(list_key, L"SearchList", searchlist);
411 }
412 }
413 }
414
415
416 class ActionCreate : public Action
417 {
418 public:
419 ActionCreate(const std::string &itf_name, const std::string &search_domains)
421 {
422 }
423
429 void execute(std::ostream &log) override
430 {
431 log << to_string() << std::endl;
433 }
434
440 std::string to_string() const override
441 {
442 std::ostringstream os;
443 os << "DNS::ActionCreate"
444 << " interface name=[" << itf_name_ << "]"
445 << " search domains=[" << search_domains_ << "]";
446 return os.str();
447 }
448
449 private:
450 const std::string itf_name_;
451 const std::string search_domains_;
452 };
453
454 class ActionDelete : public Action
455 {
456 public:
457 ActionDelete(const std::string &itf_name, const std::string &search_domains)
459 {
460 }
461
467 void execute(std::ostream &log) override
468 {
469 log << to_string() << std::endl;
471 }
472
478 std::string to_string() const override
479 {
480 std::ostringstream ss;
481 ss << "DNS::ActionDelete"
482 << " interface name=[" << itf_name_ << "]"
483 << " search domains=[" << search_domains_ << "]";
484 return ss.str();
485 }
486
487 private:
488 const std::string itf_name_;
489 const std::string search_domains_;
490 };
491
492 class ActionApply : public Action
493 {
494 public:
500 void execute(std::ostream &log) override
501 {
502 const char *gpol_status = "";
503 Reg::Key gpol_nrpt_key(Reg::gpol_nrpt_subkey);
504 if (gpol_nrpt_key.defined())
505 {
506 gpol_status = apply_gpol_nrtp_rules() ? " [gpol successful]" : " [gpol failed]";
507 }
508
509 const char *status = apply_dns_settings() ? "successful" : "failed";
510 log << to_string() << ": " << status << gpol_status << std::endl;
511 }
512
518 std::string to_string() const override
519 {
520 return "DNS::ActionApply";
521 }
522
523 private:
530 {
531 bool res = false;
532 SC_HANDLE scm = static_cast<SC_HANDLE>(INVALID_HANDLE_VALUE);
533 SC_HANDLE dnssvc = static_cast<SC_HANDLE>(INVALID_HANDLE_VALUE);
534
535 scm = ::OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
536 if (scm == nullptr)
537 {
538 goto out;
539 }
540
541 dnssvc = ::OpenServiceA(scm, "Dnscache", SERVICE_PAUSE_CONTINUE);
542 if (dnssvc == nullptr)
543 {
544 goto out;
545 }
546
547 SERVICE_STATUS status;
548 if (::ControlService(dnssvc, SERVICE_CONTROL_PARAMCHANGE, &status) == 0)
549 {
550 goto out;
551 }
552
553 res = true;
554
555 out:
556 if (dnssvc != INVALID_HANDLE_VALUE)
557 {
558 ::CloseServiceHandle(dnssvc);
559 }
560 if (scm != INVALID_HANDLE_VALUE)
561 {
562 ::CloseServiceHandle(scm);
563 }
564 return res;
565 }
566
573 {
574 SYSTEM_INFO si;
575 ::GetSystemInfo(&si);
576 const bool win_32bit = si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL;
577
578 return win_32bit ? apply_gpol_nrtp_rules_32() : apply_gpol_nrtp_rules_64();
579 }
580
588 {
589 using publish_fn_t = NTSTATUS(__stdcall *)(
590 DWORD StateNameLo,
591 DWORD StateNameHi,
592 DWORD TypeId,
593 DWORD Buffer,
594 DWORD Length,
595 DWORD ExplicitScope);
596 publish_fn_t RtlPublishWnfStateData;
597 constexpr DWORD WNF_GPOL_SYSTEM_CHANGES_HI = 0x0D891E2A;
598 constexpr DWORD WNF_GPOL_SYSTEM_CHANGES_LO = 0xA3BC0875;
599
600 HMODULE ntdll = ::LoadLibraryA("ntdll.dll");
601 if (ntdll == nullptr)
602 return false;
603
604 RtlPublishWnfStateData = reinterpret_cast<publish_fn_t>(::GetProcAddress(ntdll, "RtlPublishWnfStateData"));
605 if (RtlPublishWnfStateData == nullptr)
606 return false;
607
608 if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES_LO, WNF_GPOL_SYSTEM_CHANGES_HI, 0, 0, 0, 0) != ERROR_SUCCESS)
609 return false;
610
611 return true;
612 }
613
621 {
622 using publish_fn_t = NTSTATUS (*)(
623 __int64 StateName,
624 __int64 TypeId,
625 __int64 Buffer,
626 unsigned int Length,
627 __int64 ExplicitScope);
628 publish_fn_t RtlPublishWnfStateData;
629 constexpr INT64 WNF_GPOL_SYSTEM_CHANGES = 0x0D891E2AA3BC0875;
630
631 HMODULE ntdll = ::LoadLibraryA("ntdll.dll");
632 if (ntdll == nullptr)
633 return false;
634
635 RtlPublishWnfStateData = reinterpret_cast<publish_fn_t>(::GetProcAddress(ntdll, "RtlPublishWnfStateData"));
636 if (RtlPublishWnfStateData == nullptr)
637 return false;
638
639 if (RtlPublishWnfStateData(WNF_GPOL_SYSTEM_CHANGES, 0, 0, 0, 0) != ERROR_SUCCESS)
640 return false;
641
642 return true;
643 }
644 };
645};
646
648
649} // namespace openvpn::TunWin
bool apply_gpol_nrtp_rules_64()
Signal the DNS resolver (and others potentially) to reload the NRTP rules group policy settings on 64...
Definition dns.hpp:620
bool apply_gpol_nrtp_rules_32()
Signal the DNS resolver (and others potentially) to reload the NRTP rules group policy settings on 32...
Definition dns.hpp:587
std::string to_string() const override
Return the log message.
Definition dns.hpp:518
void execute(std::ostream &log) override
Apply any modification to the DNS settings by signaling the resolver.
Definition dns.hpp:500
bool apply_gpol_nrtp_rules()
Signal the DNS resolver (and others potentially) to reload the NRTP rules group policy settings.
Definition dns.hpp:572
bool apply_dns_settings()
Signal the DNS resolver to reload its settings.
Definition dns.hpp:529
std::string to_string() const override
Produce a textual representating of the DNS data.
Definition dns.hpp:440
ActionCreate(const std::string &itf_name, const std::string &search_domains)
Definition dns.hpp:419
void execute(std::ostream &log) override
Apply DNS data to the system.
Definition dns.hpp:429
const std::string search_domains_
Definition dns.hpp:451
const std::string itf_name_
Definition dns.hpp:450
void execute(std::ostream &log) override
Undo any modification to the DNS settings.
Definition dns.hpp:467
const std::string search_domains_
Definition dns.hpp:489
const std::string itf_name_
Definition dns.hpp:488
ActionDelete(const std::string &itf_name, const std::string &search_domains)
Definition dns.hpp:457
std::string to_string() const override
Return the log message.
Definition dns.hpp:478
Manage DNS search suffixes for Windows.
Definition dns.hpp:66
static bool add_to_searchlist(typename REG::Key &key, const std::wstring &domains)
Append domain suffixes to an existing search list.
Definition dns.hpp:253
static bool initial_searchlist_exists(typename REG::Key &key)
Check if a initial list had already been created.
Definition dns.hpp:114
static std::pair< typename REG::Key, bool > open_searchlist_key()
Return the Key for the DNS domain "SearchList" value.
Definition dns.hpp:86
static bool set_initial_searchlist(typename REG::Key &key, const std::wstring &searchlist)
Definition dns.hpp:130
static bool set_itf_domain_suffix(const std::string &itf_name, const std::wstring &domain)
Set interface specific domain suffix.
Definition dns.hpp:226
static constexpr std::array< PCWSTR, 2 > searchlist_subkeys
Definition dns.hpp:73
static bool set_initial_searchlist_from_existing(typename REG::Key &key)
Set the initial searchlist from the existing search list.
Definition dns.hpp:152
OPENVPN_EXCEPTION(dns_error)
static bool set_initial_searchlist_from_domains(typename REG::Key &key)
Definition dns.hpp:176
static void reset_search_domains(typename REG::Key &list_key)
Reset the DNS "SearchList" to its original value.
Definition dns.hpp:331
static void set_search_domains(const std::string &itf_name, const std::string &domains)
Add DNS search domain(s)
Definition dns.hpp:285
static void remove_search_domains(const std::string &itf_name, const std::string &domains)
Remove domain suffix(es) from the system.
Definition dns.hpp:354
DNS utilities for Windows.
BufferType< unsigned char > Buffer
Definition buffer.hpp:1895
dns_options search_domains
std::ostringstream os
static std::stringstream out
Definition test_path.cpp:10