• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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