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
dnsinfo_destroy(dnsinfo_t * dnsinfo)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
dnsinfo_init(dnsinfo_t ** dnsinfo_out)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
search_is_duplicate(const ares_sysconfig_t * sysconfig,const char * name)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
read_resolver(const ares_channel_t * channel,const dns_resolver_t * resolver,ares_sysconfig_t * sysconfig)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
read_resolvers(const ares_channel_t * channel,dns_resolver_t ** resolvers,int nresolvers,ares_sysconfig_t * sysconfig)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
ares_init_sysconfig_macos(const ares_channel_t * channel,ares_sysconfig_t * sysconfig)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
374