ares_sysconfig_mac.c (11989B)
1 /* MIT License 2 * 3 * Copyright (c) 2024 The c-ares project and its contributors 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to deal 7 * in the Software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice (including the next 13 * paragraph) shall be included in all copies or substantial portions of the 14 * Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 * 24 * SPDX-License-Identifier: MIT 25 */ 26 27 #ifdef __APPLE__ 28 29 /* The DNS configuration for apple is stored in the system configuration 30 * database. Apple does provide an emulated `/etc/resolv.conf` on MacOS (but 31 * not iOS), it cannot, however, represent the entirety of the DNS 32 * configuration. Alternatively, libresolv could be used to also retrieve some 33 * system configuration, but it too is not capable of retrieving the entirety 34 * of the DNS configuration. 35 * 36 * Attempts to use the preferred public API of `SCDynamicStoreCreate()` and 37 * friends yielded incomplete DNS information. Instead, that leaves some apple 38 * "internal" symbols from `configd` that we need to access in order to get the 39 * entire configuration. We can see that we're not the only ones to do this as 40 * Google Chrome also does: 41 * https://chromium.googlesource.com/chromium/src/+/HEAD/net/dns/dns_config_watcher_mac.cc 42 * These internal functions are what `libresolv` and `scutil` use to retrieve 43 * the dns configuration. Since these symbols are not publicly available, we 44 * will dynamically load the symbols from `libSystem` and import the `dnsinfo.h` 45 * private header extracted from: 46 * https://opensource.apple.com/source/configd/configd-1109.140.1/dnsinfo/dnsinfo.h 47 */ 48 49 /* The apple header uses anonymous unions which came with C11 */ 50 # if defined(__clang__) 51 # pragma GCC diagnostic push 52 # pragma GCC diagnostic ignored "-Wc11-extensions" 53 # endif 54 55 # include "ares_private.h" 56 # include <stdio.h> 57 # include <stdlib.h> 58 # include <string.h> 59 # include <dlfcn.h> 60 # include <arpa/inet.h> 61 # include "thirdparty/apple/dnsinfo.h" 62 # include <AvailabilityMacros.h> 63 # if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 /* MacOS 10.8 */ 64 # include <SystemConfiguration/SCNetworkConfiguration.h> 65 # endif 66 67 typedef struct { 68 void *handle; 69 dns_config_t *(*dns_configuration_copy)(void); 70 void (*dns_configuration_free)(dns_config_t *config); 71 } dnsinfo_t; 72 73 static void dnsinfo_destroy(dnsinfo_t *dnsinfo) 74 { 75 if (dnsinfo == NULL) { 76 return; 77 } 78 79 if (dnsinfo->handle) { 80 dlclose(dnsinfo->handle); 81 } 82 83 ares_free(dnsinfo); 84 } 85 86 static ares_status_t dnsinfo_init(dnsinfo_t **dnsinfo_out) 87 { 88 dnsinfo_t *dnsinfo = NULL; 89 ares_status_t status = ARES_SUCCESS; 90 size_t i; 91 const char *searchlibs[] = { 92 "/usr/lib/libSystem.dylib", 93 "/System/Library/Frameworks/SystemConfiguration.framework/" 94 "SystemConfiguration", 95 NULL 96 }; 97 98 if (dnsinfo_out == NULL) { 99 status = ARES_EFORMERR; 100 goto done; 101 } 102 103 *dnsinfo_out = NULL; 104 105 dnsinfo = ares_malloc_zero(sizeof(*dnsinfo)); 106 if (dnsinfo == NULL) { 107 status = ARES_ENOMEM; 108 goto done; 109 } 110 111 for (i = 0; searchlibs[i] != NULL; i++) { 112 dnsinfo->handle = dlopen(searchlibs[i], RTLD_LAZY /* | RTLD_NOLOAD */); 113 if (dnsinfo->handle == NULL) { 114 /* Fail, loop */ 115 continue; 116 } 117 118 dnsinfo->dns_configuration_copy = (dns_config_t * (*)(void)) 119 dlsym(dnsinfo->handle, "dns_configuration_copy"); 120 121 dnsinfo->dns_configuration_free = (void (*)(dns_config_t *))dlsym( 122 dnsinfo->handle, "dns_configuration_free"); 123 124 if (dnsinfo->dns_configuration_copy != NULL && 125 dnsinfo->dns_configuration_free != NULL) { 126 break; 127 } 128 129 /* Fail, loop */ 130 dlclose(dnsinfo->handle); 131 dnsinfo->handle = NULL; 132 } 133 134 135 if (dnsinfo->dns_configuration_copy == NULL || 136 dnsinfo->dns_configuration_free == NULL) { 137 status = ARES_ESERVFAIL; 138 goto done; 139 } 140 141 142 done: 143 if (status == ARES_SUCCESS) { 144 *dnsinfo_out = dnsinfo; 145 } else { 146 dnsinfo_destroy(dnsinfo); 147 } 148 149 return status; 150 } 151 152 static ares_bool_t search_is_duplicate(const ares_sysconfig_t *sysconfig, 153 const char *name) 154 { 155 size_t i; 156 for (i = 0; i < sysconfig->ndomains; i++) { 157 if (ares_strcaseeq(sysconfig->domains[i], name)) { 158 return ARES_TRUE; 159 } 160 } 161 return ARES_FALSE; 162 } 163 164 static ares_status_t read_resolver(const ares_channel_t *channel, 165 const dns_resolver_t *resolver, 166 ares_sysconfig_t *sysconfig) 167 { 168 int i; 169 unsigned short port = 0; 170 ares_status_t status = ARES_SUCCESS; 171 172 # if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 /* MacOS 10.8 */ 173 /* XXX: resolver->domain is for domain-specific servers. When we implement 174 * this support, we'll want to use this. But for now, we're going to 175 * skip any servers which set this since we can't properly route. 176 * MacOS used to use this setting for a different purpose in the 177 * past however, so on versions of MacOS < 10.8 just ignore this 178 * completely. */ 179 if (resolver->domain != NULL) { 180 return ARES_SUCCESS; 181 } 182 # endif 183 184 # if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 /* MacOS 10.8 */ 185 /* Check to see if DNS server should be used, base this on if the server is 186 * reachable or can be reachable automatically if we send traffic that 187 * direction. */ 188 if (!(resolver->reach_flags & 189 (kSCNetworkFlagsReachable | 190 kSCNetworkReachabilityFlagsConnectionOnTraffic))) { 191 return ARES_SUCCESS; 192 } 193 # endif 194 195 /* NOTE: it doesn't look like resolver->flags is relevant */ 196 197 /* If there's no nameservers, nothing to do */ 198 if (resolver->n_nameserver <= 0) { 199 return ARES_SUCCESS; 200 } 201 202 /* Default port */ 203 port = resolver->port; 204 205 /* Append search list */ 206 if (resolver->n_search > 0) { 207 char **new_domains = ares_realloc_zero( 208 sysconfig->domains, sizeof(*sysconfig->domains) * sysconfig->ndomains, 209 sizeof(*sysconfig->domains) * 210 (sysconfig->ndomains + (size_t)resolver->n_search)); 211 if (new_domains == NULL) { 212 return ARES_ENOMEM; 213 } 214 sysconfig->domains = new_domains; 215 216 for (i = 0; i < resolver->n_search; i++) { 217 const char *search; 218 /* UBSAN: copy pointer using memcpy due to misalignment */ 219 memcpy(&search, resolver->search + i, sizeof(search)); 220 221 /* Skip duplicates */ 222 if (search_is_duplicate(sysconfig, search)) { 223 continue; 224 } 225 sysconfig->domains[sysconfig->ndomains] = ares_strdup(search); 226 if (sysconfig->domains[sysconfig->ndomains] == NULL) { 227 return ARES_ENOMEM; 228 } 229 sysconfig->ndomains++; 230 } 231 } 232 233 /* NOTE: we're going to skip importing the sort addresses for now. Its 234 * likely not used, its not obvious how to even configure such a thing. 235 */ 236 # if 0 237 for (i=0; i<resolver->n_sortaddr; i++) { 238 char val[256]; 239 inet_ntop(AF_INET, &resolver->sortaddr[i]->address, val, sizeof(val)); 240 printf("\t\t%s/", val); 241 inet_ntop(AF_INET, &resolver->sortaddr[i]->mask, val, sizeof(val)); 242 printf("%s\n", val); 243 } 244 # endif 245 246 if (resolver->options != NULL) { 247 status = ares_sysconfig_set_options(sysconfig, resolver->options); 248 if (status != ARES_SUCCESS) { 249 return status; 250 } 251 } 252 253 /* NOTE: 254 * - resolver->timeout appears unused, always 0, so we ignore this 255 * - resolver->service_identifier doesn't appear relevant to us 256 * - resolver->cid also isn't relevant 257 * - resolver->if_name we won't use since it isn't available in MacOS 10.8 258 * or earlier, use resolver->if_index instead to then lookup the name. 259 */ 260 261 /* XXX: resolver->search_order appears like it might be relevant, we might 262 * need to sort the resulting list by this metric if we find in the future we 263 * need to. That said, due to the automatic re-sorting we do, I'm not sure it 264 * matters. Here's an article on this search order stuff: 265 * https://www.cnet.com/tech/computing/os-x-10-6-3-and-dns-server-priority-changes/ 266 */ 267 268 for (i = 0; i < resolver->n_nameserver; i++) { 269 struct ares_addr addr; 270 unsigned short addrport; 271 const struct sockaddr *sockaddr; 272 char if_name_str[256] = ""; 273 const char *if_name = NULL; 274 275 /* UBSAN alignment workaround to fetch memory address */ 276 memcpy(&sockaddr, resolver->nameserver + i, sizeof(sockaddr)); 277 278 if (!ares_sockaddr_to_ares_addr(&addr, &addrport, sockaddr)) { 279 continue; 280 } 281 282 if (addrport == 0) { 283 addrport = port; 284 } 285 286 if (channel->sock_funcs.aif_indextoname != NULL) { 287 if_name = channel->sock_funcs.aif_indextoname( 288 resolver->if_index, if_name_str, sizeof(if_name_str), 289 channel->sock_func_cb_data); 290 } 291 292 status = ares_sconfig_append(channel, &sysconfig->sconfig, &addr, addrport, 293 addrport, if_name); 294 if (status != ARES_SUCCESS) { 295 return status; 296 } 297 } 298 299 return status; 300 } 301 302 static ares_status_t read_resolvers(const ares_channel_t *channel, 303 dns_resolver_t **resolvers, int nresolvers, 304 ares_sysconfig_t *sysconfig) 305 { 306 ares_status_t status = ARES_SUCCESS; 307 int i; 308 309 for (i = 0; status == ARES_SUCCESS && i < nresolvers; i++) { 310 const dns_resolver_t *resolver_ptr; 311 312 /* UBSAN doesn't like that this is unaligned, lets use memcpy to get the 313 * address. Equivalent to: 314 * resolver = resolvers[i] 315 */ 316 memcpy(&resolver_ptr, resolvers + i, sizeof(resolver_ptr)); 317 318 status = read_resolver(channel, resolver_ptr, sysconfig); 319 } 320 321 return status; 322 } 323 324 ares_status_t ares_init_sysconfig_macos(const ares_channel_t *channel, 325 ares_sysconfig_t *sysconfig) 326 { 327 dnsinfo_t *dnsinfo = NULL; 328 dns_config_t *sc_dns = NULL; 329 ares_status_t status = ARES_SUCCESS; 330 331 status = dnsinfo_init(&dnsinfo); 332 333 if (status != ARES_SUCCESS) { 334 goto done; 335 } 336 337 sc_dns = dnsinfo->dns_configuration_copy(); 338 if (sc_dns == NULL) { 339 status = ARES_ESERVFAIL; 340 goto done; 341 } 342 343 /* There are `resolver`, `scoped_resolver`, and `service_specific_resolver` 344 * settings. The `scoped_resolver` settings appear to be already available via 345 * the `resolver` settings and likely are only relevant to link-local dns 346 * servers which we can already detect via the address itself, so we'll ignore 347 * the `scoped_resolver` section. It isn't clear what the 348 * `service_specific_resolver` is used for, I haven't personally seen it 349 * in use so we'll ignore this until at some point where we find we need it. 350 * Likely this wasn't available via `/etc/resolv.conf` nor `libresolv` anyhow 351 * so its not worse to prior configuration methods, worst case. */ 352 353 status = 354 read_resolvers(channel, sc_dns->resolver, sc_dns->n_resolver, sysconfig); 355 356 done: 357 if (dnsinfo) { 358 dnsinfo->dns_configuration_free(sc_dns); 359 dnsinfo_destroy(dnsinfo); 360 } 361 return status; 362 } 363 364 # if defined(__clang__) 365 # pragma GCC diagnostic pop 366 # endif 367 368 #else 369 370 /* Prevent compiler warnings due to empty translation unit */ 371 typedef int make_iso_compilers_happy; 372 373 #endif