VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/win/HostDnsServiceWin.cpp

Last change on this file was 98292, checked in by vboxsync, 15 months ago

Main/src-server: rc -> hrc/vrc. Enabled scm rc checks. bugref:10223

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.1 KB
Line 
1/* $Id: HostDnsServiceWin.cpp 98292 2023-01-25 01:14:53Z vboxsync $ */
2/** @file
3 * Host DNS listener for Windows.
4 */
5
6/*
7 * Copyright (C) 2014-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/*
29 * XXX: need <winsock2.h> to reveal IP_ADAPTER_ADDRESSES in
30 * <iptypes.h> and it must be included before <windows.h>, which is
31 * pulled in by IPRT headers.
32 */
33#include <iprt/win/winsock2.h>
34
35#include "../HostDnsService.h"
36
37#include <VBox/com/string.h>
38#include <VBox/com/ptr.h>
39
40#include <iprt/assert.h>
41#include <iprt/errcore.h>
42#include <VBox/log.h>
43
44#include <iprt/win/windows.h>
45#include <windns.h>
46#include <iptypes.h>
47#include <iprt/win/iphlpapi.h>
48
49#include <algorithm>
50#include <iprt/sanitized/sstream>
51#include <iprt/sanitized/string>
52#include <vector>
53
54
55DECLINLINE(int) registerNotification(const HKEY &hKey, HANDLE &hEvent)
56{
57 LONG lrc = RegNotifyChangeKeyValue(hKey,
58 TRUE,
59 REG_NOTIFY_CHANGE_LAST_SET,
60 hEvent,
61 TRUE);
62 AssertMsgReturn(lrc == ERROR_SUCCESS,
63 ("Failed to register event on the key. Please debug me!"),
64 VERR_INTERNAL_ERROR);
65
66 return VINF_SUCCESS;
67}
68
69static void appendTokenizedStrings(std::vector<std::string> &vecStrings, const std::string &strToAppend, char chDelim = ' ')
70{
71 if (strToAppend.empty())
72 return;
73
74 std::istringstream stream(strToAppend);
75 std::string substr;
76
77 while (std::getline(stream, substr, chDelim))
78 {
79 if (substr.empty())
80 continue;
81
82 if (std::find(vecStrings.cbegin(), vecStrings.cend(), substr) != vecStrings.cend())
83 continue;
84
85 vecStrings.push_back(substr);
86 }
87}
88
89
90struct HostDnsServiceWin::Data
91{
92 HKEY hKeyTcpipParameters;
93 bool fTimerArmed;
94
95#define DATA_SHUTDOWN_EVENT 0
96#define DATA_DNS_UPDATE_EVENT 1
97#define DATA_TIMER 2
98#define DATA_MAX_EVENT 3
99 HANDLE haDataEvent[DATA_MAX_EVENT];
100
101 Data()
102 {
103 hKeyTcpipParameters = NULL;
104 fTimerArmed = false;
105
106 for (size_t i = 0; i < DATA_MAX_EVENT; ++i)
107 haDataEvent[i] = NULL;
108 }
109
110 ~Data()
111 {
112 if (hKeyTcpipParameters != NULL)
113 RegCloseKey(hKeyTcpipParameters);
114
115 for (size_t i = 0; i < DATA_MAX_EVENT; ++i)
116 if (haDataEvent[i] != NULL)
117 CloseHandle(haDataEvent[i]);
118 }
119};
120
121
122HostDnsServiceWin::HostDnsServiceWin()
123 : HostDnsServiceBase(true)
124{
125 m = new Data();
126}
127
128HostDnsServiceWin::~HostDnsServiceWin()
129{
130 if (m != NULL)
131 delete m;
132}
133
134HRESULT HostDnsServiceWin::init(HostDnsMonitorProxy *pProxy)
135{
136 if (m == NULL)
137 return E_FAIL;
138
139 bool fRc = true;
140 LONG lRc = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
141 L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters",
142 0,
143 KEY_READ|KEY_NOTIFY,
144 &m->hKeyTcpipParameters);
145 if (lRc != ERROR_SUCCESS)
146 {
147 LogRel(("HostDnsServiceWin: failed to open key Tcpip\\Parameters (error %d)\n", lRc));
148 fRc = false;
149 }
150 else
151 {
152 for (size_t i = 0; i < DATA_MAX_EVENT; ++i)
153 {
154 HANDLE h;
155
156 if (i == DATA_TIMER)
157 h = CreateWaitableTimer(NULL, FALSE, NULL);
158 else
159 h = CreateEvent(NULL, TRUE, FALSE, NULL);
160
161 if (h == NULL)
162 {
163 LogRel(("HostDnsServiceWin: failed to create event (error %d)\n", GetLastError()));
164 fRc = false;
165 break;
166 }
167
168 m->haDataEvent[i] = h;
169 }
170 }
171
172 if (!fRc)
173 return E_FAIL;
174
175 HRESULT hrc = HostDnsServiceBase::init(pProxy);
176 if (FAILED(hrc))
177 return hrc;
178
179 return updateInfo();
180}
181
182void HostDnsServiceWin::uninit(void)
183{
184 HostDnsServiceBase::uninit();
185}
186
187int HostDnsServiceWin::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs)
188{
189 RT_NOREF(uTimeoutMs);
190
191 AssertPtr(m);
192 SetEvent(m->haDataEvent[DATA_SHUTDOWN_EVENT]);
193 /** @todo r=andy Wait for thread? Check vrc here. Timeouts? */
194
195 return VINF_SUCCESS;
196}
197
198int HostDnsServiceWin::monitorThreadProc(void)
199{
200 Assert(m != NULL);
201
202 registerNotification(m->hKeyTcpipParameters,
203 m->haDataEvent[DATA_DNS_UPDATE_EVENT]);
204
205 onMonitorThreadInitDone();
206
207 for (;;)
208 {
209 DWORD dwReady;
210
211 dwReady = WaitForMultipleObjects(DATA_MAX_EVENT, m->haDataEvent,
212 FALSE, INFINITE);
213
214 if (dwReady == WAIT_OBJECT_0 + DATA_SHUTDOWN_EVENT)
215 break;
216
217 if (dwReady == WAIT_OBJECT_0 + DATA_DNS_UPDATE_EVENT)
218 {
219 /*
220 * Registry updates for multiple values are not atomic, so
221 * wait a bit to avoid racing and reading partial update.
222 */
223 if (!m->fTimerArmed)
224 {
225 LARGE_INTEGER delay; /* in 100ns units */
226 delay.QuadPart = -2 * 1000 * 1000 * 10LL; /* relative: 2s */
227
228 BOOL ok = SetWaitableTimer(m->haDataEvent[DATA_TIMER], &delay,
229 0, NULL, NULL, FALSE);
230 if (ok)
231 {
232 m->fTimerArmed = true;
233 }
234 else
235 {
236 LogRel(("HostDnsServiceWin: failed to arm timer (error %d)\n", GetLastError()));
237 updateInfo();
238 }
239 }
240
241 ResetEvent(m->haDataEvent[DATA_DNS_UPDATE_EVENT]);
242 registerNotification(m->hKeyTcpipParameters,
243 m->haDataEvent[DATA_DNS_UPDATE_EVENT]);
244 }
245 else if (dwReady == WAIT_OBJECT_0 + DATA_TIMER)
246 {
247 m->fTimerArmed = false;
248 updateInfo();
249 }
250 else if (dwReady == WAIT_FAILED)
251 {
252 LogRel(("HostDnsServiceWin: WaitForMultipleObjects failed: error %d\n", GetLastError()));
253 return VERR_INTERNAL_ERROR;
254 }
255 else
256 {
257 LogRel(("HostDnsServiceWin: WaitForMultipleObjects unexpected return value %d\n", dwReady));
258 return VERR_INTERNAL_ERROR;
259 }
260 }
261
262 return VINF_SUCCESS;
263}
264
265HRESULT HostDnsServiceWin::updateInfo(void)
266{
267 HostDnsInformation info;
268
269 std::string strDomain;
270 std::string strSearchList; /* NB: comma separated, no spaces */
271
272 /*
273 * We ignore "DhcpDomain" key here since it's not stable. If
274 * there are two active interfaces that use DHCP (in particular
275 * when host uses OpenVPN) then DHCP ACKs will take turns updating
276 * that key. Instead we call GetAdaptersAddresses() below (which
277 * is what ipconfig.exe seems to do).
278 */
279 for (DWORD regIndex = 0; /**/; ++regIndex)
280 {
281 char keyName[256];
282 DWORD cbKeyName = sizeof(keyName);
283 DWORD keyType = 0;
284 char keyData[1024];
285 DWORD cbKeyData = sizeof(keyData);
286
287/** @todo use unicode API. This isn't UTF-8 clean! */
288 LSTATUS lrc = RegEnumValueA(m->hKeyTcpipParameters, regIndex,
289 keyName, &cbKeyName, 0,
290 &keyType, (LPBYTE)keyData, &cbKeyData);
291
292 if (lrc == ERROR_NO_MORE_ITEMS)
293 break;
294
295 if (lrc == ERROR_MORE_DATA) /* buffer too small; handle? */
296 continue;
297
298 if (lrc != ERROR_SUCCESS)
299 {
300 LogRel2(("HostDnsServiceWin: RegEnumValue error %d\n", (int)lrc));
301 return E_FAIL;
302 }
303
304 if (keyType != REG_SZ)
305 continue;
306
307 if (cbKeyData > 0 && keyData[cbKeyData - 1] == '\0')
308 --cbKeyData; /* don't count trailing NUL if present */
309
310 if (RTStrICmp("Domain", keyName) == 0)
311 {
312 strDomain.assign(keyData, cbKeyData);
313 LogRel2(("HostDnsServiceWin: Domain=\"%s\"\n", strDomain.c_str()));
314 }
315 else if (RTStrICmp("DhcpDomain", keyName) == 0)
316 {
317 std::string strDhcpDomain(keyData, cbKeyData);
318 LogRel2(("HostDnsServiceWin: DhcpDomain=\"%s\"\n", strDhcpDomain.c_str()));
319 }
320 else if (RTStrICmp("SearchList", keyName) == 0)
321 {
322 strSearchList.assign(keyData, cbKeyData);
323 LogRel2(("HostDnsServiceWin: SearchList=\"%s\"\n", strSearchList.c_str()));
324 }
325 }
326
327 /* statically configured domain name */
328 if (!strDomain.empty())
329 {
330 info.domain = strDomain;
331 info.searchList.push_back(strDomain);
332 }
333
334 /* statically configured search list */
335 if (!strSearchList.empty())
336 appendTokenizedStrings(info.searchList, strSearchList, ',');
337
338 /*
339 * When name servers are configured statically it seems that the
340 * value of Tcpip\Parameters\NameServer is NOT set, inly interface
341 * specific NameServer value is (which triggers notification for
342 * us to pick up the change). Fortunately, DnsApi seems to do the
343 * right thing there.
344 */
345 DNS_STATUS status;
346 PIP4_ARRAY pIp4Array = NULL;
347
348 // NB: must be set on input it seems, despite docs' claim to the contrary.
349 DWORD cbBuffer = sizeof(&pIp4Array);
350
351 status = DnsQueryConfig(DnsConfigDnsServerList,
352 DNS_CONFIG_FLAG_ALLOC, NULL, NULL,
353 &pIp4Array, &cbBuffer);
354
355 if (status == NO_ERROR && pIp4Array != NULL)
356 {
357 for (DWORD i = 0; i < pIp4Array->AddrCount; ++i)
358 {
359 char szAddrStr[16] = "";
360 RTStrPrintf(szAddrStr, sizeof(szAddrStr), "%RTnaipv4", pIp4Array->AddrArray[i]);
361
362 LogRel2(("HostDnsServiceWin: server %d: %s\n", i+1, szAddrStr));
363 info.servers.push_back(szAddrStr);
364 }
365
366 LocalFree(pIp4Array);
367 }
368
369
370 /**
371 * DnsQueryConfig(DnsConfigSearchList, ...) is not implemented.
372 * Call GetAdaptersAddresses() that orders the returned list
373 * appropriately and collect IP_ADAPTER_ADDRESSES::DnsSuffix.
374 */
375 do {
376 PIP_ADAPTER_ADDRESSES pAddrBuf = NULL;
377 ULONG cbAddrBuf = 8 * 1024;
378 bool fReallocated = false;
379 ULONG err;
380
381 pAddrBuf = (PIP_ADAPTER_ADDRESSES) malloc(cbAddrBuf);
382 if (pAddrBuf == NULL)
383 {
384 LogRel2(("HostDnsServiceWin: failed to allocate %zu bytes"
385 " of GetAdaptersAddresses buffer\n",
386 (size_t)cbAddrBuf));
387 break;
388 }
389
390 while (pAddrBuf != NULL)
391 {
392 ULONG cbAddrBufProvided = cbAddrBuf;
393
394 err = GetAdaptersAddresses(AF_UNSPEC,
395 GAA_FLAG_SKIP_ANYCAST
396 | GAA_FLAG_SKIP_MULTICAST,
397 NULL,
398 pAddrBuf, &cbAddrBuf);
399 if (err == NO_ERROR)
400 {
401 break;
402 }
403 else if (err == ERROR_BUFFER_OVERFLOW)
404 {
405 LogRel2(("HostDnsServiceWin: provided GetAdaptersAddresses with %zu"
406 " but asked again for %zu bytes\n",
407 (size_t)cbAddrBufProvided, (size_t)cbAddrBuf));
408
409 if (RT_UNLIKELY(fReallocated)) /* what? again?! */
410 {
411 LogRel2(("HostDnsServiceWin: ... not going to realloc again\n"));
412 free(pAddrBuf);
413 pAddrBuf = NULL;
414 break;
415 }
416
417 PIP_ADAPTER_ADDRESSES pNewBuf = (PIP_ADAPTER_ADDRESSES) realloc(pAddrBuf, cbAddrBuf);
418 if (pNewBuf == NULL)
419 {
420 LogRel2(("HostDnsServiceWin: failed to reallocate %zu bytes\n", (size_t)cbAddrBuf));
421 free(pAddrBuf);
422 pAddrBuf = NULL;
423 break;
424 }
425
426 /* try again */
427 pAddrBuf = pNewBuf; /* cbAddrBuf already updated */
428 fReallocated = true;
429 }
430 else
431 {
432 LogRel2(("HostDnsServiceWin: GetAdaptersAddresses error %d\n", err));
433 free(pAddrBuf);
434 pAddrBuf = NULL;
435 break;
436 }
437 }
438
439 if (pAddrBuf == NULL)
440 break;
441
442 for (PIP_ADAPTER_ADDRESSES pAdp = pAddrBuf; pAdp != NULL; pAdp = pAdp->Next)
443 {
444 LogRel2(("HostDnsServiceWin: %ls (status %u) ...\n",
445 pAdp->FriendlyName ? pAdp->FriendlyName : L"(null)", pAdp->OperStatus));
446
447 if (pAdp->OperStatus != IfOperStatusUp)
448 continue;
449
450 if (pAdp->DnsSuffix == NULL || *pAdp->DnsSuffix == L'\0')
451 continue;
452
453 char *pszDnsSuffix = NULL;
454 int vrc = RTUtf16ToUtf8Ex(pAdp->DnsSuffix, RTSTR_MAX, &pszDnsSuffix, 0, /* allocate */ NULL);
455 if (RT_FAILURE(vrc))
456 {
457 LogRel2(("HostDnsServiceWin: failed to convert DNS suffix \"%ls\": %Rrc\n", pAdp->DnsSuffix, vrc));
458 continue;
459 }
460
461 AssertContinue(pszDnsSuffix != NULL);
462 AssertContinue(*pszDnsSuffix != '\0');
463 LogRel2(("HostDnsServiceWin: ... suffix = \"%s\"\n", pszDnsSuffix));
464
465 appendTokenizedStrings(info.searchList, pszDnsSuffix);
466 RTStrFree(pszDnsSuffix);
467 }
468
469 free(pAddrBuf);
470 } while (0);
471
472
473 if (info.domain.empty() && !info.searchList.empty())
474 info.domain = info.searchList[0];
475
476 if (info.searchList.size() == 1)
477 info.searchList.clear();
478
479 HostDnsServiceBase::setInfo(info);
480
481 return S_OK;
482}
483
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use