ares_sysconfig_win.c (19944B)
1 /* MIT License 2 * 3 * Copyright (c) 1998 Massachusetts Institute of Technology 4 * Copyright (c) 2007 Daniel Stenberg 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice (including the next 14 * paragraph) shall be included in all copies or substantial portions of the 15 * Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 * SOFTWARE. 24 * 25 * SPDX-License-Identifier: MIT 26 */ 27 28 #include "ares_private.h" 29 30 #ifdef HAVE_SYS_PARAM_H 31 # include <sys/param.h> 32 #endif 33 34 #ifdef HAVE_NETINET_IN_H 35 # include <netinet/in.h> 36 #endif 37 38 #ifdef HAVE_NETDB_H 39 # include <netdb.h> 40 #endif 41 42 #ifdef HAVE_ARPA_INET_H 43 # include <arpa/inet.h> 44 #endif 45 46 #if defined(USE_WINSOCK) 47 # if defined(HAVE_IPHLPAPI_H) 48 # include <iphlpapi.h> 49 # endif 50 # if defined(HAVE_NETIOAPI_H) 51 # include <netioapi.h> 52 # endif 53 #endif 54 55 #include "ares_inet_net_pton.h" 56 57 #if defined(USE_WINSOCK) 58 /* 59 * get_REG_SZ() 60 * 61 * Given a 'hKey' handle to an open registry key and a 'leafKeyName' pointer 62 * to the name of the registry leaf key to be queried, fetch it's string 63 * value and return a pointer in *outptr to a newly allocated memory area 64 * holding it as a null-terminated string. 65 * 66 * Returns 0 and nullifies *outptr upon inability to return a string value. 67 * 68 * Returns 1 and sets *outptr when returning a dynamically allocated string. 69 * 70 * Supported on Windows NT 3.5 and newer. 71 */ 72 static ares_bool_t get_REG_SZ(HKEY hKey, const char *leafKeyName, char **outptr) 73 { 74 DWORD size = 0; 75 int res; 76 77 *outptr = NULL; 78 79 /* Find out size of string stored in registry */ 80 res = RegQueryValueExA(hKey, leafKeyName, 0, NULL, NULL, &size); 81 if ((res != ERROR_SUCCESS && res != ERROR_MORE_DATA) || !size) { 82 return ARES_FALSE; 83 } 84 85 /* Allocate buffer of indicated size plus one given that string 86 might have been stored without null termination */ 87 *outptr = ares_malloc(size + 1); 88 if (!*outptr) { 89 return ARES_FALSE; 90 } 91 92 /* Get the value for real */ 93 res = RegQueryValueExA(hKey, leafKeyName, 0, NULL, (unsigned char *)*outptr, 94 &size); 95 if ((res != ERROR_SUCCESS) || (size == 1)) { 96 ares_free(*outptr); 97 *outptr = NULL; 98 return ARES_FALSE; 99 } 100 101 /* Null terminate buffer always */ 102 *(*outptr + size) = '\0'; 103 104 return ARES_TRUE; 105 } 106 107 static void commanjoin(char **dst, const char * const src, const size_t len) 108 { 109 char *newbuf; 110 size_t newsize; 111 112 /* 1 for terminating 0 and 2 for , and terminating 0 */ 113 newsize = len + (*dst ? (ares_strlen(*dst) + 2) : 1); 114 newbuf = ares_realloc(*dst, newsize); 115 if (!newbuf) { 116 return; 117 } 118 if (*dst == NULL) { 119 *newbuf = '\0'; 120 } 121 *dst = newbuf; 122 if (ares_strlen(*dst) != 0) { 123 strcat(*dst, ","); 124 } 125 strncat(*dst, src, len); 126 } 127 128 /* 129 * commajoin() 130 * 131 * RTF code. 132 */ 133 static void commajoin(char **dst, const char *src) 134 { 135 commanjoin(dst, src, ares_strlen(src)); 136 } 137 138 /* A structure to hold the string form of IPv4 and IPv6 addresses so we can 139 * sort them by a metric. 140 */ 141 typedef struct { 142 /* The metric we sort them by. */ 143 ULONG metric; 144 145 /* Original index of the item, used as a secondary sort parameter to make 146 * qsort() stable if the metrics are equal */ 147 size_t orig_idx; 148 149 /* Room enough for the string form of any IPv4 or IPv6 address that 150 * ares_inet_ntop() will create. Based on the existing c-ares practice. 151 */ 152 char text[INET6_ADDRSTRLEN + 8 + 64]; /* [%s]:NNNNN%iface */ 153 } Address; 154 155 /* Sort Address values \a left and \a right by metric, returning the usual 156 * indicators for qsort(). 157 */ 158 static int compareAddresses(const void *arg1, const void *arg2) 159 { 160 const Address * const left = arg1; 161 const Address * const right = arg2; 162 /* Lower metric the more preferred */ 163 if (left->metric < right->metric) { 164 return -1; 165 } 166 if (left->metric > right->metric) { 167 return 1; 168 } 169 /* If metrics are equal, lower original index more preferred */ 170 if (left->orig_idx < right->orig_idx) { 171 return -1; 172 } 173 if (left->orig_idx > right->orig_idx) { 174 return 1; 175 } 176 return 0; 177 } 178 179 #if defined(HAVE_GETBESTROUTE2) && !defined(__WATCOMC__) 180 /* There can be multiple routes to "the Internet". And there can be different 181 * DNS servers associated with each of the interfaces that offer those routes. 182 * We have to assume that any DNS server can serve any request. But, some DNS 183 * servers may only respond if requested over their associated interface. But 184 * we also want to use "the preferred route to the Internet" whenever possible 185 * (and not use DNS servers on a non-preferred route even by forcing request 186 * to go out on the associated non-preferred interface). i.e. We want to use 187 * the DNS servers associated with the same interface that we would use to 188 * make a general request to anything else. 189 * 190 * But, Windows won't sort the DNS servers by the metrics associated with the 191 * routes and interfaces _even_ though it obviously sends IP packets based on 192 * those same routes and metrics. So, we must do it ourselves. 193 * 194 * So, we sort the DNS servers by the same metric values used to determine how 195 * an outgoing IP packet will go, thus effectively using the DNS servers 196 * associated with the interface that the DNS requests themselves will 197 * travel. This gives us optimal routing and avoids issues where DNS servers 198 * won't respond to requests that don't arrive via some specific subnetwork 199 * (and thus some specific interface). 200 * 201 * This function computes the metric we use to sort. On the interface 202 * identified by \a luid, it determines the best route to \a dest and combines 203 * that route's metric with \a interfaceMetric to compute a metric for the 204 * destination address on that interface. This metric can be used as a weight 205 * to sort the DNS server addresses associated with each interface (lower is 206 * better). 207 * 208 * Note that by restricting the route search to the specific interface with 209 * which the DNS servers are associated, this function asks the question "What 210 * is the metric for sending IP packets to this DNS server?" which allows us 211 * to sort the DNS servers correctly. 212 */ 213 static ULONG getBestRouteMetric(IF_LUID * const luid, /* Can't be const :( */ 214 const SOCKADDR_INET * const dest, 215 const ULONG interfaceMetric) 216 { 217 MIB_IPFORWARD_ROW2 row; 218 SOCKADDR_INET ignored; 219 if (GetBestRoute2(/* The interface to use. The index is ignored since we are 220 * passing a LUID. 221 */ 222 luid, 0, 223 /* No specific source address. */ 224 NULL, 225 /* Our destination address. */ 226 dest, 227 /* No options. */ 228 0, 229 /* The route row. */ 230 &row, 231 /* The best source address, which we don't need. */ 232 &ignored) != NO_ERROR 233 /* If the metric is "unused" (-1) or too large for us to add the two 234 * metrics, use the worst possible, thus sorting this last. 235 */ 236 || row.Metric == (ULONG)-1 || 237 row.Metric > ((ULONG)-1) - interfaceMetric) { 238 /* Return the worst possible metric. */ 239 return (ULONG)-1; 240 } 241 242 /* Return the metric value from that row, plus the interface metric. 243 * 244 * See 245 * http://msdn.microsoft.com/en-us/library/windows/desktop/aa814494(v=vs.85).aspx 246 * which describes the combination as a "sum". 247 */ 248 return row.Metric + interfaceMetric; 249 } 250 #endif 251 252 /* 253 * get_DNS_Windows() 254 * 255 * Locates DNS info using GetAdaptersAddresses() function from the Internet 256 * Protocol Helper (IP Helper) API. When located, this returns a pointer 257 * in *outptr to a newly allocated memory area holding a null-terminated 258 * string with a space or comma separated list of DNS IP addresses. 259 * 260 * Returns 0 and nullifies *outptr upon inability to return DNSes string. 261 * 262 * Returns 1 and sets *outptr when returning a dynamically allocated string. 263 * 264 * Implementation supports Windows XP and newer. 265 */ 266 # define IPAA_INITIAL_BUF_SZ 15 * 1024 267 # define IPAA_MAX_TRIES 3 268 269 static ares_bool_t get_DNS_Windows(char **outptr) 270 { 271 IP_ADAPTER_DNS_SERVER_ADDRESS *ipaDNSAddr; 272 IP_ADAPTER_ADDRESSES *ipaa; 273 IP_ADAPTER_ADDRESSES *newipaa; 274 IP_ADAPTER_ADDRESSES *ipaaEntry; 275 ULONG ReqBufsz = IPAA_INITIAL_BUF_SZ; 276 ULONG Bufsz = IPAA_INITIAL_BUF_SZ; 277 ULONG AddrFlags = 0; 278 int trying = IPAA_MAX_TRIES; 279 ULONG res; 280 281 /* The capacity of addresses, in elements. */ 282 size_t addressesSize; 283 /* The number of elements in addresses. */ 284 size_t addressesIndex = 0; 285 /* The addresses we will sort. */ 286 Address *addresses; 287 288 union { 289 struct sockaddr *sa; 290 struct sockaddr_in *sa4; 291 struct sockaddr_in6 *sa6; 292 } namesrvr; 293 294 *outptr = NULL; 295 296 ipaa = ares_malloc(Bufsz); 297 if (!ipaa) { 298 return ARES_FALSE; 299 } 300 301 /* Start with enough room for a few DNS server addresses and we'll grow it 302 * as we encounter more. 303 */ 304 addressesSize = 4; 305 addresses = (Address *)ares_malloc(sizeof(Address) * addressesSize); 306 if (addresses == NULL) { 307 /* We need room for at least some addresses to function. */ 308 ares_free(ipaa); 309 return ARES_FALSE; 310 } 311 312 /* Usually this call succeeds with initial buffer size */ 313 res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz); 314 if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS)) { 315 goto done; 316 } 317 318 while ((res == ERROR_BUFFER_OVERFLOW) && (--trying)) { 319 if (Bufsz < ReqBufsz) { 320 newipaa = ares_realloc(ipaa, ReqBufsz); 321 if (!newipaa) { 322 goto done; 323 } 324 Bufsz = ReqBufsz; 325 ipaa = newipaa; 326 } 327 res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz); 328 if (res == ERROR_SUCCESS) { 329 break; 330 } 331 } 332 if (res != ERROR_SUCCESS) { 333 goto done; 334 } 335 336 for (ipaaEntry = ipaa; ipaaEntry; ipaaEntry = ipaaEntry->Next) { 337 if (ipaaEntry->OperStatus != IfOperStatusUp) { 338 continue; 339 } 340 341 /* For each interface, find any associated DNS servers as IPv4 or IPv6 342 * addresses. For each found address, find the best route to that DNS 343 * server address _on_ _that_ _interface_ (at this moment in time) and 344 * compute the resulting total metric, just as Windows routing will do. 345 * Then, sort all the addresses found by the metric. 346 */ 347 for (ipaDNSAddr = ipaaEntry->FirstDnsServerAddress; ipaDNSAddr != NULL; 348 ipaDNSAddr = ipaDNSAddr->Next) { 349 char ipaddr[INET6_ADDRSTRLEN] = ""; 350 351 namesrvr.sa = ipaDNSAddr->Address.lpSockaddr; 352 353 if (namesrvr.sa->sa_family == AF_INET) { 354 if ((namesrvr.sa4->sin_addr.S_un.S_addr == INADDR_ANY) || 355 (namesrvr.sa4->sin_addr.S_un.S_addr == INADDR_NONE)) { 356 continue; 357 } 358 359 /* Allocate room for another address, if necessary, else skip. */ 360 if (addressesIndex == addressesSize) { 361 const size_t newSize = addressesSize + 4; 362 Address * const newMem = 363 (Address *)ares_realloc(addresses, sizeof(Address) * newSize); 364 if (newMem == NULL) { 365 continue; 366 } 367 addresses = newMem; 368 addressesSize = newSize; 369 } 370 371 # if defined(HAVE_GETBESTROUTE2) && !defined(__WATCOMC__) 372 /* OpenWatcom's builtin Windows SDK does not have a definition for 373 * MIB_IPFORWARD_ROW2, and also does not allow the usage of SOCKADDR_INET 374 * as a variable. Let's work around this by returning the worst possible 375 * metric, but only when using the OpenWatcom compiler. 376 * It may be worth investigating using a different version of the Windows 377 * SDK with OpenWatcom in the future, though this may be fixed in OpenWatcom 378 * 2.0. 379 */ 380 addresses[addressesIndex].metric = getBestRouteMetric( 381 &ipaaEntry->Luid, (SOCKADDR_INET *)((void *)(namesrvr.sa)), 382 ipaaEntry->Ipv4Metric); 383 # else 384 addresses[addressesIndex].metric = (ULONG)-1; 385 # endif 386 387 /* Record insertion index to make qsort stable */ 388 addresses[addressesIndex].orig_idx = addressesIndex; 389 390 if (!ares_inet_ntop(AF_INET, &namesrvr.sa4->sin_addr, ipaddr, 391 sizeof(ipaddr))) { 392 continue; 393 } 394 snprintf(addresses[addressesIndex].text, 395 sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr, 396 ntohs(namesrvr.sa4->sin_port)); 397 ++addressesIndex; 398 } else if (namesrvr.sa->sa_family == AF_INET6) { 399 unsigned int ll_scope = 0; 400 struct ares_addr addr; 401 402 if (memcmp(&namesrvr.sa6->sin6_addr, &ares_in6addr_any, 403 sizeof(namesrvr.sa6->sin6_addr)) == 0) { 404 continue; 405 } 406 407 /* Allocate room for another address, if necessary, else skip. */ 408 if (addressesIndex == addressesSize) { 409 const size_t newSize = addressesSize + 4; 410 Address * const newMem = 411 (Address *)ares_realloc(addresses, sizeof(Address) * newSize); 412 if (newMem == NULL) { 413 continue; 414 } 415 addresses = newMem; 416 addressesSize = newSize; 417 } 418 419 /* See if its link-local */ 420 memset(&addr, 0, sizeof(addr)); 421 addr.family = AF_INET6; 422 memcpy(&addr.addr.addr6, &namesrvr.sa6->sin6_addr, 16); 423 if (ares_addr_is_linklocal(&addr)) { 424 ll_scope = ipaaEntry->Ipv6IfIndex; 425 } 426 427 # if defined(HAVE_GETBESTROUTE2) && !defined(__WATCOMC__) 428 addresses[addressesIndex].metric = getBestRouteMetric( 429 &ipaaEntry->Luid, (SOCKADDR_INET *)((void *)(namesrvr.sa)), 430 ipaaEntry->Ipv6Metric); 431 # else 432 addresses[addressesIndex].metric = (ULONG)-1; 433 # endif 434 435 /* Record insertion index to make qsort stable */ 436 addresses[addressesIndex].orig_idx = addressesIndex; 437 438 if (!ares_inet_ntop(AF_INET6, &namesrvr.sa6->sin6_addr, ipaddr, 439 sizeof(ipaddr))) { 440 continue; 441 } 442 443 if (ll_scope) { 444 snprintf(addresses[addressesIndex].text, 445 sizeof(addresses[addressesIndex].text), "[%s]:%u%%%u", 446 ipaddr, ntohs(namesrvr.sa6->sin6_port), ll_scope); 447 } else { 448 snprintf(addresses[addressesIndex].text, 449 sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr, 450 ntohs(namesrvr.sa6->sin6_port)); 451 } 452 ++addressesIndex; 453 } else { 454 /* Skip non-IPv4/IPv6 addresses completely. */ 455 continue; 456 } 457 } 458 } 459 460 /* Sort all of the textual addresses by their metric (and original index if 461 * metrics are equal). */ 462 qsort(addresses, addressesIndex, sizeof(*addresses), compareAddresses); 463 464 /* Join them all into a single string, removing duplicates. */ 465 { 466 size_t i; 467 for (i = 0; i < addressesIndex; ++i) { 468 size_t j; 469 /* Look for this address text appearing previously in the results. */ 470 for (j = 0; j < i; ++j) { 471 if (strcmp(addresses[j].text, addresses[i].text) == 0) { 472 break; 473 } 474 } 475 /* Iff we didn't emit this address already, emit it now. */ 476 if (j == i) { 477 /* Add that to outptr (if we can). */ 478 commajoin(outptr, addresses[i].text); 479 } 480 } 481 } 482 483 done: 484 ares_free(addresses); 485 486 if (ipaa) { 487 ares_free(ipaa); 488 } 489 490 if (!*outptr) { 491 return ARES_FALSE; 492 } 493 494 return ARES_TRUE; 495 } 496 497 /* 498 * get_SuffixList_Windows() 499 * 500 * Reads the "DNS Suffix Search List" from registry and writes the list items 501 * whitespace separated to outptr. If the Search List is empty, the 502 * "Primary Dns Suffix" is written to outptr. 503 * 504 * Returns 0 and nullifies *outptr upon inability to return the suffix list. 505 * 506 * Returns 1 and sets *outptr when returning a dynamically allocated string. 507 * 508 * Implementation supports Windows Server 2003 and newer 509 */ 510 static ares_bool_t get_SuffixList_Windows(char **outptr) 511 { 512 HKEY hKey; 513 HKEY hKeyEnum; 514 char keyName[256]; 515 DWORD keyNameBuffSize; 516 DWORD keyIdx = 0; 517 char *p = NULL; 518 519 *outptr = NULL; 520 521 /* 1. Global DNS Suffix Search List */ 522 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ, &hKey) == 523 ERROR_SUCCESS) { 524 get_REG_SZ(hKey, SEARCHLIST_KEY, outptr); 525 if (get_REG_SZ(hKey, DOMAIN_KEY, &p)) { 526 commajoin(outptr, p); 527 ares_free(p); 528 p = NULL; 529 } 530 RegCloseKey(hKey); 531 } 532 533 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NT_DNSCLIENT, 0, KEY_READ, &hKey) == 534 ERROR_SUCCESS) { 535 if (get_REG_SZ(hKey, SEARCHLIST_KEY, &p)) { 536 commajoin(outptr, p); 537 ares_free(p); 538 p = NULL; 539 } 540 RegCloseKey(hKey); 541 } 542 543 /* 2. Connection Specific Search List composed of: 544 * a. Primary DNS Suffix */ 545 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_DNSCLIENT, 0, KEY_READ, &hKey) == 546 ERROR_SUCCESS) { 547 if (get_REG_SZ(hKey, PRIMARYDNSSUFFIX_KEY, &p)) { 548 commajoin(outptr, p); 549 ares_free(p); 550 p = NULL; 551 } 552 RegCloseKey(hKey); 553 } 554 555 /* b. Interface SearchList, Domain, DhcpDomain */ 556 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY "\\" INTERFACES_KEY, 0, 557 KEY_READ, &hKey) == ERROR_SUCCESS) { 558 for (;;) { 559 keyNameBuffSize = sizeof(keyName); 560 if (RegEnumKeyExA(hKey, keyIdx++, keyName, &keyNameBuffSize, 0, NULL, 561 NULL, NULL) != ERROR_SUCCESS) { 562 break; 563 } 564 if (RegOpenKeyExA(hKey, keyName, 0, KEY_QUERY_VALUE, &hKeyEnum) != 565 ERROR_SUCCESS) { 566 continue; 567 } 568 /* p can be comma separated (SearchList) */ 569 if (get_REG_SZ(hKeyEnum, SEARCHLIST_KEY, &p)) { 570 commajoin(outptr, p); 571 ares_free(p); 572 p = NULL; 573 } 574 if (get_REG_SZ(hKeyEnum, DOMAIN_KEY, &p)) { 575 commajoin(outptr, p); 576 ares_free(p); 577 p = NULL; 578 } 579 if (get_REG_SZ(hKeyEnum, DHCPDOMAIN_KEY, &p)) { 580 commajoin(outptr, p); 581 ares_free(p); 582 p = NULL; 583 } 584 RegCloseKey(hKeyEnum); 585 } 586 RegCloseKey(hKey); 587 } 588 589 return *outptr != NULL ? ARES_TRUE : ARES_FALSE; 590 } 591 592 ares_status_t ares_init_sysconfig_windows(const ares_channel_t *channel, 593 ares_sysconfig_t *sysconfig) 594 { 595 char *line = NULL; 596 ares_status_t status = ARES_SUCCESS; 597 598 if (get_DNS_Windows(&line)) { 599 status = ares_sconfig_append_fromstr(channel, &sysconfig->sconfig, line, 600 ARES_TRUE); 601 ares_free(line); 602 if (status != ARES_SUCCESS) { 603 goto done; 604 } 605 } 606 607 if (get_SuffixList_Windows(&line)) { 608 sysconfig->domains = ares_strsplit(line, ", ", &sysconfig->ndomains); 609 ares_free(line); 610 if (sysconfig->domains == NULL) { 611 status = ARES_EFILE; 612 } 613 if (status != ARES_SUCCESS) { 614 goto done; 615 } 616 } 617 618 done: 619 return status; 620 } 621 #endif