1 | /* Test parsing of /etc/resolv.conf. Genric version. |
2 | Copyright (C) 2017-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* Before including this file, TEST_THREAD has to be defined to 0 or |
20 | 1, depending on whether the threading tests should be compiled |
21 | in. */ |
22 | |
23 | #include <arpa/inet.h> |
24 | #include <errno.h> |
25 | #include <gnu/lib-names.h> |
26 | #include <netdb.h> |
27 | #include <resolv/resolv_context.h> |
28 | #include <stdio.h> |
29 | #include <stdlib.h> |
30 | #include <support/capture_subprocess.h> |
31 | #include <support/check.h> |
32 | #include <support/namespace.h> |
33 | #include <support/run_diff.h> |
34 | #include <support/support.h> |
35 | #include <support/temp_file.h> |
36 | #include <support/test-driver.h> |
37 | #include <support/xsocket.h> |
38 | #include <support/xstdio.h> |
39 | #include <support/xunistd.h> |
40 | |
41 | #if TEST_THREAD |
42 | # include <support/xthread.h> |
43 | #endif |
44 | |
45 | /* This is the host name used to ensure predictable behavior of |
46 | res_init. */ |
47 | static const char *const test_hostname = "www.example.com" ; |
48 | |
49 | struct support_chroot *chroot_env; |
50 | |
51 | static void |
52 | prepare (int argc, char **argv) |
53 | { |
54 | chroot_env = support_chroot_create |
55 | ((struct support_chroot_configuration) |
56 | { |
57 | .resolv_conf = "" , |
58 | }); |
59 | } |
60 | |
61 | /* Verify that the chroot environment has been set up. */ |
62 | static void |
63 | check_chroot_working (void *closure) |
64 | { |
65 | xchroot (path: chroot_env->path_chroot); |
66 | FILE *fp = xfopen (_PATH_RESCONF, mode: "r" ); |
67 | xfclose (fp); |
68 | |
69 | TEST_VERIFY_EXIT (res_init () == 0); |
70 | TEST_VERIFY (_res.options & RES_INIT); |
71 | |
72 | char buf[100]; |
73 | if (gethostname (name: buf, len: sizeof (buf)) < 0) |
74 | FAIL_EXIT1 ("gethostname: %m" ); |
75 | if (strcmp (s1: buf, s2: test_hostname) != 0) |
76 | FAIL_EXIT1 ("unexpected host name: %s" , buf); |
77 | } |
78 | |
79 | /* If FLAG is set in *OPTIONS, write NAME to FP, and clear it in |
80 | *OPTIONS. */ |
81 | static void |
82 | print_option_flag (FILE *fp, int *options, int flag, const char *name) |
83 | { |
84 | if (*options & flag) |
85 | { |
86 | fprintf (stream: fp, format: " %s" , name); |
87 | *options &= ~flag; |
88 | } |
89 | } |
90 | |
91 | /* Write a decoded version of the resolver configuration *RESP to the |
92 | stream FP. */ |
93 | static void |
94 | print_resp (FILE *fp, res_state resp) |
95 | { |
96 | struct resolv_context *ctx = __resolv_context_get_override (resp); |
97 | TEST_VERIFY_EXIT (ctx != NULL); |
98 | if (ctx->conf == NULL) |
99 | fprintf (stream: fp, format: "; extended resolver state missing\n" ); |
100 | |
101 | /* The options directive. */ |
102 | { |
103 | /* RES_INIT is used internally for tracking initialization. */ |
104 | TEST_VERIFY (resp->options & RES_INIT); |
105 | /* Also mask out other default flags which cannot be set through |
106 | the options directive. */ |
107 | int options |
108 | = resp->options & ~(RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH); |
109 | if (options != 0 |
110 | || resp->ndots != 1 |
111 | || resp->retrans != RES_TIMEOUT |
112 | || resp->retry != RES_DFLRETRY) |
113 | { |
114 | fputs (s: "options" , stream: fp); |
115 | if (resp->ndots != 1) |
116 | fprintf (stream: fp, format: " ndots:%d" , resp->ndots); |
117 | if (resp->retrans != RES_TIMEOUT) |
118 | fprintf (stream: fp, format: " timeout:%d" , resp->retrans); |
119 | if (resp->retry != RES_DFLRETRY) |
120 | fprintf (stream: fp, format: " attempts:%d" , resp->retry); |
121 | print_option_flag (fp, options: &options, RES_USEVC, name: "use-vc" ); |
122 | print_option_flag (fp, options: &options, RES_ROTATE, name: "rotate" ); |
123 | print_option_flag (fp, options: &options, RES_USE_EDNS0, name: "edns0" ); |
124 | print_option_flag (fp, options: &options, RES_SNGLKUP, |
125 | name: "single-request" ); |
126 | print_option_flag (fp, options: &options, RES_SNGLKUPREOP, |
127 | name: "single-request-reopen" ); |
128 | print_option_flag (fp, options: &options, RES_NOTLDQUERY, name: "no-tld-query" ); |
129 | print_option_flag (fp, options: &options, RES_NORELOAD, name: "no-reload" ); |
130 | print_option_flag (fp, options: &options, RES_TRUSTAD, name: "trust-ad" ); |
131 | fputc (c: '\n', stream: fp); |
132 | if (options != 0) |
133 | fprintf (stream: fp, format: "; error: unresolved option bits: 0x%x\n" , options); |
134 | } |
135 | } |
136 | |
137 | /* The search and domain directives. */ |
138 | if (resp->dnsrch[0] != NULL) |
139 | { |
140 | fputs (s: "search" , stream: fp); |
141 | for (int i = 0; i < MAXDNSRCH && resp->dnsrch[i] != NULL; ++i) |
142 | { |
143 | fputc (c: ' ', stream: fp); |
144 | fputs (s: resp->dnsrch[i], stream: fp); |
145 | } |
146 | fputc (c: '\n', stream: fp); |
147 | } |
148 | else if (resp->defdname[0] != '\0') |
149 | fprintf (stream: fp, format: "domain %s\n" , resp->defdname); |
150 | |
151 | /* The extended search path. */ |
152 | { |
153 | size_t i = 0; |
154 | while (true) |
155 | { |
156 | const char *name = __resolv_context_search_list (ctx, index: i); |
157 | if (name == NULL) |
158 | break; |
159 | fprintf (stream: fp, format: "; search[%zu]: %s\n" , i, name); |
160 | ++i; |
161 | } |
162 | } |
163 | |
164 | /* The sortlist directive. */ |
165 | if (resp->nsort > 0) |
166 | { |
167 | fputs (s: "sortlist" , stream: fp); |
168 | for (int i = 0; i < resp->nsort && i < MAXRESOLVSORT; ++i) |
169 | { |
170 | char net[20]; |
171 | if (inet_ntop (AF_INET, cp: &resp->sort_list[i].addr, |
172 | buf: net, len: sizeof (net)) == NULL) |
173 | FAIL_EXIT1 ("inet_ntop: %m\n" ); |
174 | char mask[20]; |
175 | if (inet_ntop (AF_INET, cp: &resp->sort_list[i].mask, |
176 | buf: mask, len: sizeof (mask)) == NULL) |
177 | FAIL_EXIT1 ("inet_ntop: %m\n" ); |
178 | fprintf (stream: fp, format: " %s/%s" , net, mask); |
179 | } |
180 | fputc (c: '\n', stream: fp); |
181 | } |
182 | |
183 | /* The nameserver directives. */ |
184 | for (size_t i = 0; i < resp->nscount; ++i) |
185 | { |
186 | char host[NI_MAXHOST]; |
187 | char service[NI_MAXSERV]; |
188 | |
189 | /* See get_nsaddr in res_send.c. */ |
190 | void *addr; |
191 | size_t addrlen; |
192 | if (resp->nsaddr_list[i].sin_family == 0 |
193 | && resp->_u._ext.nsaddrs[i] != NULL) |
194 | { |
195 | addr = resp->_u._ext.nsaddrs[i]; |
196 | addrlen = sizeof (*resp->_u._ext.nsaddrs[i]); |
197 | } |
198 | else |
199 | { |
200 | addr = &resp->nsaddr_list[i]; |
201 | addrlen = sizeof (resp->nsaddr_list[i]); |
202 | } |
203 | |
204 | int ret = getnameinfo (sa: addr, salen: addrlen, |
205 | host: host, hostlen: sizeof (host), serv: service, servlen: sizeof (service), |
206 | NI_NUMERICHOST | NI_NUMERICSERV); |
207 | if (ret != 0) |
208 | { |
209 | if (ret == EAI_SYSTEM) |
210 | fprintf (stream: fp, format: "; error: getnameinfo: %m\n" ); |
211 | else |
212 | fprintf (stream: fp, format: "; error: getnameinfo: %s\n" , gai_strerror (ecode: ret)); |
213 | } |
214 | else |
215 | { |
216 | fprintf (stream: fp, format: "nameserver %s\n" , host); |
217 | if (strcmp (s1: service, s2: "53" ) != 0) |
218 | fprintf (stream: fp, format: "; unrepresentable port number %s\n\n" , service); |
219 | } |
220 | } |
221 | |
222 | /* The extended name server list. */ |
223 | { |
224 | size_t i = 0; |
225 | while (true) |
226 | { |
227 | const struct sockaddr *addr = __resolv_context_nameserver (ctx, index: i); |
228 | if (addr == NULL) |
229 | break; |
230 | size_t addrlen; |
231 | switch (addr->sa_family) |
232 | { |
233 | case AF_INET: |
234 | addrlen = sizeof (struct sockaddr_in); |
235 | break; |
236 | case AF_INET6: |
237 | addrlen = sizeof (struct sockaddr_in6); |
238 | break; |
239 | default: |
240 | FAIL_EXIT1 ("invalid address family %d" , addr->sa_family); |
241 | } |
242 | |
243 | char host[NI_MAXHOST]; |
244 | char service[NI_MAXSERV]; |
245 | int ret = getnameinfo (sa: addr, salen: addrlen, |
246 | host: host, hostlen: sizeof (host), serv: service, servlen: sizeof (service), |
247 | NI_NUMERICHOST | NI_NUMERICSERV); |
248 | |
249 | if (ret != 0) |
250 | { |
251 | if (ret == EAI_SYSTEM) |
252 | fprintf (stream: fp, format: "; error: getnameinfo: %m\n" ); |
253 | else |
254 | fprintf (stream: fp, format: "; error: getnameinfo: %s\n" , gai_strerror (ecode: ret)); |
255 | } |
256 | else |
257 | fprintf (stream: fp, format: "; nameserver[%zu]: [%s]:%s\n" , i, host, service); |
258 | ++i; |
259 | } |
260 | } |
261 | |
262 | TEST_VERIFY (!ferror (fp)); |
263 | |
264 | __resolv_context_put (ctx); |
265 | } |
266 | |
267 | /* Parameters of one test case. */ |
268 | struct test_case |
269 | { |
270 | /* A short, descriptive name of the test. */ |
271 | const char *name; |
272 | |
273 | /* The contents of the /etc/resolv.conf file. */ |
274 | const char *conf; |
275 | |
276 | /* The expected output from print_resp. */ |
277 | const char *expected; |
278 | |
279 | /* Setting for the LOCALDOMAIN environment variable. NULL if the |
280 | variable is not to be set. */ |
281 | const char *localdomain; |
282 | |
283 | /* Setting for the RES_OPTIONS environment variable. NULL if the |
284 | variable is not to be set. */ |
285 | const char *res_options; |
286 | |
287 | /* Override the system host name. NULL means that no change is made |
288 | and the default is used (test_hostname). */ |
289 | const char *hostname; |
290 | }; |
291 | |
292 | enum test_init |
293 | { |
294 | test_init, |
295 | test_ninit, |
296 | test_mkquery, |
297 | test_gethostbyname, |
298 | test_getaddrinfo, |
299 | test_init_method_last = test_getaddrinfo |
300 | }; |
301 | |
302 | static const char *const test_init_names[] = |
303 | { |
304 | [test_init] = "res_init" , |
305 | [test_ninit] = "res_ninit" , |
306 | [test_mkquery] = "res_mkquery" , |
307 | [test_gethostbyname] = "gethostbyname" , |
308 | [test_getaddrinfo] = "getaddrinfo" , |
309 | }; |
310 | |
311 | /* Closure argument for run_res_init. */ |
312 | struct test_context |
313 | { |
314 | enum test_init init; |
315 | const struct test_case *t; |
316 | }; |
317 | |
318 | static void |
319 | setup_nss_dns_and_chroot (void) |
320 | { |
321 | /* Load nss_dns outside of the chroot. */ |
322 | if (dlopen (LIBNSS_DNS_SO, RTLD_LAZY) == NULL) |
323 | FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s" , dlerror ()); |
324 | xchroot (path: chroot_env->path_chroot); |
325 | /* Force the use of nss_dns. */ |
326 | __nss_configure_lookup (dbname: "hosts" , string: "dns" ); |
327 | } |
328 | |
329 | /* Run res_ninit or res_init in a subprocess and dump the parsed |
330 | resolver state to standard output. */ |
331 | static void |
332 | run_res_init (void *closure) |
333 | { |
334 | struct test_context *ctx = closure; |
335 | TEST_VERIFY (getenv ("LOCALDOMAIN" ) == NULL); |
336 | TEST_VERIFY (getenv ("RES_OPTIONS" ) == NULL); |
337 | if (ctx->t->localdomain != NULL) |
338 | setenv (name: "LOCALDOMAIN" , value: ctx->t->localdomain, replace: 1); |
339 | if (ctx->t->res_options != NULL) |
340 | setenv (name: "RES_OPTIONS" , value: ctx->t->res_options, replace: 1); |
341 | if (ctx->t->hostname != NULL) |
342 | { |
343 | #ifdef CLONE_NEWUTS |
344 | /* This test needs its own namespace, to avoid changing the host |
345 | name for the parent, too. */ |
346 | TEST_VERIFY_EXIT (unshare (CLONE_NEWUTS) == 0); |
347 | if (sethostname (name: ctx->t->hostname, len: strlen (s: ctx->t->hostname)) != 0) |
348 | FAIL_EXIT1 ("sethostname (\"%s\"): %m" , ctx->t->hostname); |
349 | #else |
350 | FAIL_UNSUPPORTED ("clone (CLONE_NEWUTS) not supported" ); |
351 | #endif |
352 | } |
353 | |
354 | switch (ctx->init) |
355 | { |
356 | case test_init: |
357 | xchroot (path: chroot_env->path_chroot); |
358 | TEST_VERIFY (res_init () == 0); |
359 | print_resp (stdout, resp: &_res); |
360 | return; |
361 | |
362 | case test_ninit: |
363 | xchroot (path: chroot_env->path_chroot); |
364 | res_state resp = xmalloc (n: sizeof (*resp)); |
365 | memset (s: resp, c: 0, n: sizeof (*resp)); |
366 | TEST_VERIFY (res_ninit (resp) == 0); |
367 | print_resp (stdout, resp); |
368 | res_nclose (resp); |
369 | free (ptr: resp); |
370 | return; |
371 | |
372 | case test_mkquery: |
373 | xchroot (path: chroot_env->path_chroot); |
374 | unsigned char buf[512]; |
375 | TEST_VERIFY (res_mkquery (QUERY, "www.example" , |
376 | C_IN, ns_t_a, NULL, 0, |
377 | NULL, buf, sizeof (buf)) > 0); |
378 | print_resp (stdout, resp: &_res); |
379 | return; |
380 | |
381 | case test_gethostbyname: |
382 | setup_nss_dns_and_chroot (); |
383 | /* Trigger implicit initialization of the _res structure. The |
384 | actual lookup result is immaterial. */ |
385 | (void )gethostbyname (name: "www.example" ); |
386 | print_resp (stdout, resp: &_res); |
387 | return; |
388 | |
389 | case test_getaddrinfo: |
390 | setup_nss_dns_and_chroot (); |
391 | /* Trigger implicit initialization of the _res structure. The |
392 | actual lookup result is immaterial. */ |
393 | struct addrinfo *ai; |
394 | (void) getaddrinfo (name: "www.example" , NULL, NULL, pai: &ai); |
395 | print_resp (stdout, resp: &_res); |
396 | return; |
397 | } |
398 | |
399 | FAIL_EXIT1 ("invalid init method %d" , ctx->init); |
400 | } |
401 | |
402 | #if TEST_THREAD |
403 | /* Helper function which calls run_res_init from a thread. */ |
404 | static void * |
405 | run_res_init_thread_func (void *closure) |
406 | { |
407 | run_res_init (closure); |
408 | return NULL; |
409 | } |
410 | |
411 | /* Variant of res_run_init which runs the function on a non-main |
412 | thread. */ |
413 | static void |
414 | run_res_init_on_thread (void *closure) |
415 | { |
416 | xpthread_join (xpthread_create (NULL, run_res_init_thread_func, closure)); |
417 | } |
418 | #endif /* TEST_THREAD */ |
419 | |
420 | struct test_case test_cases[] = |
421 | { |
422 | {.name = "empty file" , |
423 | .conf = "" , |
424 | .expected = "search example.com\n" |
425 | "; search[0]: example.com\n" |
426 | "nameserver 127.0.0.1\n" |
427 | "; nameserver[0]: [127.0.0.1]:53\n" |
428 | }, |
429 | {.name = "empty file, no-dot hostname" , |
430 | .conf = "" , |
431 | .expected = "nameserver 127.0.0.1\n" |
432 | "; nameserver[0]: [127.0.0.1]:53\n" , |
433 | .hostname = "example" , |
434 | }, |
435 | {.name = "empty file with LOCALDOMAIN" , |
436 | .conf = "" , |
437 | .expected = "search example.net\n" |
438 | "; search[0]: example.net\n" |
439 | "nameserver 127.0.0.1\n" |
440 | "; nameserver[0]: [127.0.0.1]:53\n" , |
441 | .localdomain = "example.net" , |
442 | }, |
443 | {.name = "empty file with RES_OPTIONS" , |
444 | .conf = "" , |
445 | .expected = "options attempts:5 edns0\n" |
446 | "search example.com\n" |
447 | "; search[0]: example.com\n" |
448 | "nameserver 127.0.0.1\n" |
449 | "; nameserver[0]: [127.0.0.1]:53\n" , |
450 | .res_options = "edns0 attempts:5" , |
451 | }, |
452 | {.name = "empty file with RES_OPTIONS and LOCALDOMAIN" , |
453 | .conf = "" , |
454 | .expected = "options attempts:5 edns0\n" |
455 | "search example.org\n" |
456 | "; search[0]: example.org\n" |
457 | "nameserver 127.0.0.1\n" |
458 | "; nameserver[0]: [127.0.0.1]:53\n" , |
459 | .localdomain = "example.org" , |
460 | .res_options = "edns0 attempts:5" , |
461 | }, |
462 | {.name = "basic" , |
463 | .conf = "search corp.example.com example.com\n" |
464 | "nameserver 192.0.2.1\n" , |
465 | .expected = "search corp.example.com example.com\n" |
466 | "; search[0]: corp.example.com\n" |
467 | "; search[1]: example.com\n" |
468 | "nameserver 192.0.2.1\n" |
469 | "; nameserver[0]: [192.0.2.1]:53\n" |
470 | }, |
471 | {.name = "basic with no-dot hostname" , |
472 | .conf = "search corp.example.com example.com\n" |
473 | "nameserver 192.0.2.1\n" , |
474 | .expected = "search corp.example.com example.com\n" |
475 | "; search[0]: corp.example.com\n" |
476 | "; search[1]: example.com\n" |
477 | "nameserver 192.0.2.1\n" |
478 | "; nameserver[0]: [192.0.2.1]:53\n" , |
479 | .hostname = "example" , |
480 | }, |
481 | {.name = "basic no-reload" , |
482 | .conf = "options no-reload\n" |
483 | "search corp.example.com example.com\n" |
484 | "nameserver 192.0.2.1\n" , |
485 | .expected = "options no-reload\n" |
486 | "search corp.example.com example.com\n" |
487 | "; search[0]: corp.example.com\n" |
488 | "; search[1]: example.com\n" |
489 | "nameserver 192.0.2.1\n" |
490 | "; nameserver[0]: [192.0.2.1]:53\n" |
491 | }, |
492 | {.name = "basic no-reload via RES_OPTIONS" , |
493 | .conf = "search corp.example.com example.com\n" |
494 | "nameserver 192.0.2.1\n" , |
495 | .expected = "options no-reload\n" |
496 | "search corp.example.com example.com\n" |
497 | "; search[0]: corp.example.com\n" |
498 | "; search[1]: example.com\n" |
499 | "nameserver 192.0.2.1\n" |
500 | "; nameserver[0]: [192.0.2.1]:53\n" , |
501 | .res_options = "no-reload" |
502 | }, |
503 | {.name = "whitespace" , |
504 | .conf = "# This test covers comment and whitespace processing " |
505 | " (trailing whitespace,\n" |
506 | "# missing newline at end of file).\n" |
507 | "\n" |
508 | ";search commented out\n" |
509 | "search corp.example.com\texample.com \n" |
510 | "#nameserver 192.0.2.3\n" |
511 | "nameserver 192.0.2.1 \n" |
512 | "nameserver 192.0.2.2" , /* No \n at end of file. */ |
513 | .expected = "search corp.example.com example.com\n" |
514 | "; search[0]: corp.example.com\n" |
515 | "; search[1]: example.com\n" |
516 | "nameserver 192.0.2.1\n" |
517 | "nameserver 192.0.2.2\n" |
518 | "; nameserver[0]: [192.0.2.1]:53\n" |
519 | "; nameserver[1]: [192.0.2.2]:53\n" |
520 | }, |
521 | {.name = "domain" , |
522 | .conf = "domain example.net\n" |
523 | "nameserver 192.0.2.1\n" , |
524 | .expected = "search example.net\n" |
525 | "; search[0]: example.net\n" |
526 | "nameserver 192.0.2.1\n" |
527 | "; nameserver[0]: [192.0.2.1]:53\n" |
528 | }, |
529 | {.name = "domain space" , |
530 | .conf = "domain example.net \n" |
531 | "nameserver 192.0.2.1\n" , |
532 | .expected = "search example.net\n" |
533 | "; search[0]: example.net\n" |
534 | "nameserver 192.0.2.1\n" |
535 | "; nameserver[0]: [192.0.2.1]:53\n" |
536 | }, |
537 | {.name = "domain tab" , |
538 | .conf = "domain example.net\t\n" |
539 | "nameserver 192.0.2.1\n" , |
540 | .expected = "search example.net\n" |
541 | "; search[0]: example.net\n" |
542 | "nameserver 192.0.2.1\n" |
543 | "; nameserver[0]: [192.0.2.1]:53\n" |
544 | }, |
545 | {.name = "domain override" , |
546 | .conf = "search example.com example.org\n" |
547 | "nameserver 192.0.2.1\n" |
548 | "domain example.net" , /* No \n at end of file. */ |
549 | .expected = "search example.net\n" |
550 | "; search[0]: example.net\n" |
551 | "nameserver 192.0.2.1\n" |
552 | "; nameserver[0]: [192.0.2.1]:53\n" |
553 | }, |
554 | {.name = "option values, multiple servers" , |
555 | .conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n" |
556 | "domain example.net\n" |
557 | ";domain comment\n" |
558 | "search corp.example.com\texample.com\n" |
559 | "nameserver 192.0.2.1\n" |
560 | "nameserver ::1\n" |
561 | "nameserver 192.0.2.2\n" , |
562 | .expected = "options ndots:3 timeout:19 attempts:5 edns0\n" |
563 | "search corp.example.com example.com\n" |
564 | "; search[0]: corp.example.com\n" |
565 | "; search[1]: example.com\n" |
566 | "nameserver 192.0.2.1\n" |
567 | "nameserver ::1\n" |
568 | "nameserver 192.0.2.2\n" |
569 | "; nameserver[0]: [192.0.2.1]:53\n" |
570 | "; nameserver[1]: [::1]:53\n" |
571 | "; nameserver[2]: [192.0.2.2]:53\n" |
572 | }, |
573 | {.name = "out-of-range option vales" , |
574 | .conf = "options use-vc timeout:999 attempts:999 ndots:99\n" |
575 | "search example.com\n" , |
576 | .expected = "options ndots:15 timeout:30 attempts:5 use-vc\n" |
577 | "search example.com\n" |
578 | "; search[0]: example.com\n" |
579 | "nameserver 127.0.0.1\n" |
580 | "; nameserver[0]: [127.0.0.1]:53\n" |
581 | }, |
582 | {.name = "repeated directives" , |
583 | .conf = "options ndots:3 use-vc\n" |
584 | "options edns0 ndots:2\n" |
585 | "domain corp.example\n" |
586 | "search example.net corp.example.com example.com\n" |
587 | "search example.org\n" |
588 | "search\n" , |
589 | .expected = "options ndots:2 use-vc edns0\n" |
590 | "search example.org\n" |
591 | "; search[0]: example.org\n" |
592 | "nameserver 127.0.0.1\n" |
593 | "; nameserver[0]: [127.0.0.1]:53\n" |
594 | }, |
595 | {.name = "many name servers, sortlist" , |
596 | .conf = "options single-request\n" |
597 | "search example.org example.com example.net corp.example.com\n" |
598 | "sortlist 192.0.2.0/255.255.255.0\n" |
599 | "nameserver 192.0.2.1\n" |
600 | "nameserver 192.0.2.2\n" |
601 | "nameserver 192.0.2.3\n" |
602 | "nameserver 192.0.2.4\n" |
603 | "nameserver 192.0.2.5\n" |
604 | "nameserver 192.0.2.6\n" |
605 | "nameserver 192.0.2.7\n" |
606 | "nameserver 192.0.2.8\n" , |
607 | .expected = "options single-request\n" |
608 | "search example.org example.com example.net corp.example.com\n" |
609 | "; search[0]: example.org\n" |
610 | "; search[1]: example.com\n" |
611 | "; search[2]: example.net\n" |
612 | "; search[3]: corp.example.com\n" |
613 | "sortlist 192.0.2.0/255.255.255.0\n" |
614 | "nameserver 192.0.2.1\n" |
615 | "nameserver 192.0.2.2\n" |
616 | "nameserver 192.0.2.3\n" |
617 | "; nameserver[0]: [192.0.2.1]:53\n" |
618 | "; nameserver[1]: [192.0.2.2]:53\n" |
619 | "; nameserver[2]: [192.0.2.3]:53\n" |
620 | "; nameserver[3]: [192.0.2.4]:53\n" |
621 | "; nameserver[4]: [192.0.2.5]:53\n" |
622 | "; nameserver[5]: [192.0.2.6]:53\n" |
623 | "; nameserver[6]: [192.0.2.7]:53\n" |
624 | "; nameserver[7]: [192.0.2.8]:53\n" |
625 | }, |
626 | {.name = "IPv4 and IPv6 nameservers" , |
627 | .conf = "options single-request\n" |
628 | "search example.org example.com example.net corp.example.com" |
629 | " legacy.example.com\n" |
630 | "sortlist 192.0.2.0\n" |
631 | "nameserver 192.0.2.1\n" |
632 | "nameserver 2001:db8::2\n" |
633 | "nameserver 192.0.2.3\n" |
634 | "nameserver 2001:db8::4\n" |
635 | "nameserver 192.0.2.5\n" |
636 | "nameserver 2001:db8::6\n" |
637 | "nameserver 192.0.2.7\n" |
638 | "nameserver 2001:db8::8\n" , |
639 | .expected = "options single-request\n" |
640 | "search example.org example.com example.net corp.example.com" |
641 | " legacy.example.com\n" |
642 | "; search[0]: example.org\n" |
643 | "; search[1]: example.com\n" |
644 | "; search[2]: example.net\n" |
645 | "; search[3]: corp.example.com\n" |
646 | "; search[4]: legacy.example.com\n" |
647 | "sortlist 192.0.2.0/255.255.255.0\n" |
648 | "nameserver 192.0.2.1\n" |
649 | "nameserver 2001:db8::2\n" |
650 | "nameserver 192.0.2.3\n" |
651 | "; nameserver[0]: [192.0.2.1]:53\n" |
652 | "; nameserver[1]: [2001:db8::2]:53\n" |
653 | "; nameserver[2]: [192.0.2.3]:53\n" |
654 | "; nameserver[3]: [2001:db8::4]:53\n" |
655 | "; nameserver[4]: [192.0.2.5]:53\n" |
656 | "; nameserver[5]: [2001:db8::6]:53\n" |
657 | "; nameserver[6]: [192.0.2.7]:53\n" |
658 | "; nameserver[7]: [2001:db8::8]:53\n" , |
659 | }, |
660 | {.name = "garbage after nameserver" , |
661 | .conf = "nameserver 192.0.2.1 garbage\n" |
662 | "nameserver 192.0.2.2:5353\n" |
663 | "nameserver 192.0.2.3 5353\n" , |
664 | .expected = "search example.com\n" |
665 | "; search[0]: example.com\n" |
666 | "nameserver 192.0.2.1\n" |
667 | "nameserver 192.0.2.3\n" |
668 | "; nameserver[0]: [192.0.2.1]:53\n" |
669 | "; nameserver[1]: [192.0.2.3]:53\n" |
670 | }, |
671 | {.name = "RES_OPTIONS is cummulative" , |
672 | .conf = "options timeout:7 ndots:2 use-vc\n" |
673 | "nameserver 192.0.2.1\n" , |
674 | .expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n" |
675 | "search example.com\n" |
676 | "; search[0]: example.com\n" |
677 | "nameserver 192.0.2.1\n" |
678 | "; nameserver[0]: [192.0.2.1]:53\n" , |
679 | .res_options = "attempts:5 ndots:3 edns0 " , |
680 | }, |
681 | {.name = "many search list entries (bug 19569)" , |
682 | .conf = "nameserver 192.0.2.1\n" |
683 | "search corp.example.com support.example.com" |
684 | " community.example.org wan.example.net vpn.example.net" |
685 | " example.com example.org example.net\n" , |
686 | .expected = "search corp.example.com support.example.com" |
687 | " community.example.org wan.example.net vpn.example.net example.com\n" |
688 | "; search[0]: corp.example.com\n" |
689 | "; search[1]: support.example.com\n" |
690 | "; search[2]: community.example.org\n" |
691 | "; search[3]: wan.example.net\n" |
692 | "; search[4]: vpn.example.net\n" |
693 | "; search[5]: example.com\n" |
694 | "; search[6]: example.org\n" |
695 | "; search[7]: example.net\n" |
696 | "nameserver 192.0.2.1\n" |
697 | "; nameserver[0]: [192.0.2.1]:53\n" |
698 | }, |
699 | {.name = "very long search list entries (bug 21475)" , |
700 | .conf = "nameserver 192.0.2.1\n" |
701 | "search example.com " |
702 | #define H63 "this-host-name-is-longer-than-yours-yes-I-really-really-mean-it" |
703 | #define D63 "this-domain-name-is-as-long-as-the-previous-name--63-characters" |
704 | " " H63 "." D63 ".example.org" |
705 | " " H63 "." D63 ".example.net\n" , |
706 | .expected = "search example.com " H63 "." D63 ".example.org\n" |
707 | "; search[0]: example.com\n" |
708 | "; search[1]: " H63 "." D63 ".example.org\n" |
709 | "; search[2]: " H63 "." D63 ".example.net\n" |
710 | #undef H63 |
711 | #undef D63 |
712 | "nameserver 192.0.2.1\n" |
713 | "; nameserver[0]: [192.0.2.1]:53\n" |
714 | }, |
715 | {.name = "trust-ad flag" , |
716 | .conf = "options trust-ad\n" |
717 | "nameserver 192.0.2.1\n" , |
718 | .expected = "options trust-ad\n" |
719 | "search example.com\n" |
720 | "; search[0]: example.com\n" |
721 | "nameserver 192.0.2.1\n" |
722 | "; nameserver[0]: [192.0.2.1]:53\n" |
723 | }, |
724 | { NULL } |
725 | }; |
726 | |
727 | /* Run the indicated test case. This function assumes that the chroot |
728 | contents has already been set up. */ |
729 | static void |
730 | test_file_contents (const struct test_case *t) |
731 | { |
732 | #if TEST_THREAD |
733 | for (int do_thread = 0; do_thread < 2; ++do_thread) |
734 | #endif |
735 | for (int init_method = 0; init_method <= test_init_method_last; |
736 | ++init_method) |
737 | { |
738 | if (test_verbose > 0) |
739 | printf (format: "info: testing init method %s\n" , |
740 | test_init_names[init_method]); |
741 | struct test_context ctx = { .init = init_method, .t = t }; |
742 | void (*func) (void *) = run_res_init; |
743 | #if TEST_THREAD |
744 | if (do_thread) |
745 | func = run_res_init_on_thread; |
746 | #endif |
747 | struct support_capture_subprocess proc |
748 | = support_capture_subprocess (callback: func, closure: &ctx); |
749 | if (strcmp (s1: proc.out.buffer, s2: t->expected) != 0) |
750 | { |
751 | support_record_failure (); |
752 | printf (format: "error: output mismatch for %s (init method %s)\n" , |
753 | t->name, test_init_names[init_method]); |
754 | support_run_diff (left_label: "expected" , left: t->expected, |
755 | right_label: "actual" , right: proc.out.buffer); |
756 | } |
757 | support_capture_subprocess_check (&proc, context: t->name, status_or_signal: 0, |
758 | allowed: sc_allow_stdout); |
759 | support_capture_subprocess_free (&proc); |
760 | } |
761 | } |
762 | |
763 | /* Special tests which do not follow the general pattern. */ |
764 | enum { special_tests_count = 11 }; |
765 | |
766 | /* Implementation of special tests. */ |
767 | static void |
768 | special_test_callback (void *closure) |
769 | { |
770 | unsigned int *test_indexp = closure; |
771 | unsigned test_index = *test_indexp; |
772 | TEST_VERIFY (test_index < special_tests_count); |
773 | if (test_verbose > 0) |
774 | printf (format: "info: special test %u\n" , test_index); |
775 | xchroot (path: chroot_env->path_chroot); |
776 | |
777 | switch (test_index) |
778 | { |
779 | case 0: |
780 | case 1: |
781 | /* Second res_init with missing or empty file preserves |
782 | flags. */ |
783 | if (test_index == 1) |
784 | TEST_VERIFY (unlink (_PATH_RESCONF) == 0); |
785 | _res.options = RES_USE_EDNS0; |
786 | TEST_VERIFY (res_init () == 0); |
787 | /* First res_init clears flag. */ |
788 | TEST_VERIFY (!(_res.options & RES_USE_EDNS0)); |
789 | _res.options |= RES_USE_EDNS0; |
790 | TEST_VERIFY (res_init () == 0); |
791 | /* Second res_init preserves flag. */ |
792 | TEST_VERIFY (_res.options & RES_USE_EDNS0); |
793 | if (test_index == 1) |
794 | /* Restore empty file. */ |
795 | support_write_file_string (_PATH_RESCONF, contents: "" ); |
796 | break; |
797 | |
798 | case 2: |
799 | /* Second res_init is cumulative. */ |
800 | support_write_file_string (_PATH_RESCONF, |
801 | contents: "options rotate\n" |
802 | "nameserver 192.0.2.1\n" ); |
803 | _res.options = RES_USE_EDNS0; |
804 | TEST_VERIFY (res_init () == 0); |
805 | /* First res_init clears flag. */ |
806 | TEST_VERIFY (!(_res.options & RES_USE_EDNS0)); |
807 | /* And sets RES_ROTATE. */ |
808 | TEST_VERIFY (_res.options & RES_ROTATE); |
809 | _res.options |= RES_USE_EDNS0; |
810 | TEST_VERIFY (res_init () == 0); |
811 | /* Second res_init preserves flag. */ |
812 | TEST_VERIFY (_res.options & RES_USE_EDNS0); |
813 | TEST_VERIFY (_res.options & RES_ROTATE); |
814 | /* Reloading the configuration does not clear the explicitly set |
815 | flag. */ |
816 | support_write_file_string (_PATH_RESCONF, |
817 | contents: "nameserver 192.0.2.1\n" |
818 | "nameserver 192.0.2.2\n" ); |
819 | TEST_VERIFY (res_init () == 0); |
820 | TEST_VERIFY (_res.nscount == 2); |
821 | TEST_VERIFY (_res.options & RES_USE_EDNS0); |
822 | /* Whether RES_ROTATE (originally in resolv.conf, now removed) |
823 | should be preserved is subject to debate. See bug 21701. */ |
824 | /* TEST_VERIFY (!(_res.options & RES_ROTATE)); */ |
825 | break; |
826 | |
827 | case 3: |
828 | case 4: |
829 | case 5: |
830 | case 6: |
831 | support_write_file_string (_PATH_RESCONF, |
832 | contents: "options edns0\n" |
833 | "nameserver 192.0.2.1\n" ); |
834 | goto reload_tests; |
835 | case 7: /* 7 and the following tests are with no-reload. */ |
836 | case 8: |
837 | case 9: |
838 | case 10: |
839 | support_write_file_string (_PATH_RESCONF, |
840 | contents: "options edns0 no-reload\n" |
841 | "nameserver 192.0.2.1\n" ); |
842 | /* Fall through. */ |
843 | reload_tests: |
844 | for (int iteration = 0; iteration < 2; ++iteration) |
845 | { |
846 | switch (test_index) |
847 | { |
848 | case 3: |
849 | case 7: |
850 | TEST_VERIFY (res_init () == 0); |
851 | break; |
852 | case 4: |
853 | case 8: |
854 | { |
855 | unsigned char buf[512]; |
856 | TEST_VERIFY |
857 | (res_mkquery (QUERY, test_hostname, C_IN, T_A, |
858 | NULL, 0, NULL, buf, sizeof (buf)) > 0); |
859 | } |
860 | break; |
861 | case 5: |
862 | case 9: |
863 | gethostbyname (name: test_hostname); |
864 | break; |
865 | case 6: |
866 | case 10: |
867 | { |
868 | struct addrinfo *ai; |
869 | (void) getaddrinfo (name: test_hostname, NULL, NULL, pai: &ai); |
870 | } |
871 | break; |
872 | } |
873 | /* test_index == 7 is res_init and performs a reload even |
874 | with no-reload. */ |
875 | if (iteration == 0 || test_index > 7) |
876 | { |
877 | TEST_VERIFY (_res.options & RES_USE_EDNS0); |
878 | TEST_VERIFY (!(_res.options & RES_ROTATE)); |
879 | if (test_index < 7) |
880 | TEST_VERIFY (!(_res.options & RES_NORELOAD)); |
881 | else |
882 | TEST_VERIFY (_res.options & RES_NORELOAD); |
883 | TEST_VERIFY (_res.nscount == 1); |
884 | /* File change triggers automatic reloading. */ |
885 | support_write_file_string (_PATH_RESCONF, |
886 | contents: "options rotate\n" |
887 | "nameserver 192.0.2.1\n" |
888 | "nameserver 192.0.2.2\n" ); |
889 | } |
890 | else |
891 | { |
892 | if (test_index != 3 && test_index != 7) |
893 | /* test_index 3, 7 are res_init; this function does |
894 | not reset flags. See bug 21701. */ |
895 | TEST_VERIFY (!(_res.options & RES_USE_EDNS0)); |
896 | TEST_VERIFY (_res.options & RES_ROTATE); |
897 | TEST_VERIFY (_res.nscount == 2); |
898 | } |
899 | } |
900 | break; |
901 | } |
902 | } |
903 | |
904 | #if TEST_THREAD |
905 | /* Helper function which calls special_test_callback from a |
906 | thread. */ |
907 | static void * |
908 | special_test_thread_func (void *closure) |
909 | { |
910 | special_test_callback (closure); |
911 | return NULL; |
912 | } |
913 | |
914 | /* Variant of special_test_callback which runs the function on a |
915 | non-main thread. */ |
916 | static void |
917 | run_special_test_on_thread (void *closure) |
918 | { |
919 | xpthread_join (xpthread_create (NULL, special_test_thread_func, closure)); |
920 | } |
921 | #endif /* TEST_THREAD */ |
922 | |
923 | /* Perform the requested special test in a subprocess using |
924 | special_test_callback. */ |
925 | static void |
926 | special_test (unsigned int test_index) |
927 | { |
928 | #if TEST_THREAD |
929 | for (int do_thread = 0; do_thread < 2; ++do_thread) |
930 | #endif |
931 | { |
932 | void (*func) (void *) = special_test_callback; |
933 | #if TEST_THREAD |
934 | if (do_thread) |
935 | func = run_special_test_on_thread; |
936 | #endif |
937 | struct support_capture_subprocess proc |
938 | = support_capture_subprocess (callback: func, closure: &test_index); |
939 | char *test_name = xasprintf (format: "special test %u" , test_index); |
940 | if (strcmp (s1: proc.out.buffer, s2: "" ) != 0) |
941 | { |
942 | support_record_failure (); |
943 | printf (format: "error: output mismatch for %s\n" , test_name); |
944 | support_run_diff (left_label: "expected" , left: "" , |
945 | right_label: "actual" , right: proc.out.buffer); |
946 | } |
947 | support_capture_subprocess_check (&proc, context: test_name, status_or_signal: 0, allowed: sc_allow_stdout); |
948 | free (ptr: test_name); |
949 | support_capture_subprocess_free (&proc); |
950 | } |
951 | } |
952 | |
953 | |
954 | /* Dummy DNS server. It ensures that the probe queries sent by |
955 | gethostbyname and getaddrinfo receive a reply even if the system |
956 | applies a very strict rate limit to localhost. */ |
957 | static pid_t |
958 | start_dummy_server (void) |
959 | { |
960 | int server_socket = xsocket (AF_INET, SOCK_DGRAM, 0); |
961 | { |
962 | struct sockaddr_in sin = |
963 | { |
964 | .sin_family = AF_INET, |
965 | .sin_addr = { .s_addr = htonl (INADDR_LOOPBACK) }, |
966 | .sin_port = htons (53), |
967 | }; |
968 | int ret = bind (fd: server_socket, addr: (struct sockaddr *) &sin, len: sizeof (sin)); |
969 | if (ret < 0) |
970 | { |
971 | if (errno == EACCES) |
972 | /* The port is reserved, which means we cannot start the |
973 | server. */ |
974 | return -1; |
975 | FAIL_EXIT1 ("cannot bind socket to port 53: %m" ); |
976 | } |
977 | } |
978 | |
979 | pid_t pid = xfork (); |
980 | if (pid == 0) |
981 | { |
982 | /* Child process. Echo back queries as SERVFAIL responses. */ |
983 | while (true) |
984 | { |
985 | union |
986 | { |
987 | HEADER ; |
988 | unsigned char bytes[512]; |
989 | } packet; |
990 | struct sockaddr_in sin; |
991 | socklen_t sinlen = sizeof (sin); |
992 | |
993 | ssize_t ret = recvfrom |
994 | (fd: server_socket, buf: &packet, n: sizeof (packet), |
995 | MSG_NOSIGNAL, addr: (struct sockaddr *) &sin, addr_len: &sinlen); |
996 | if (ret < 0) |
997 | FAIL_EXIT1 ("recvfrom on fake server socket: %m" ); |
998 | if (ret > sizeof (HEADER)) |
999 | { |
1000 | /* Turn the query into a SERVFAIL response. */ |
1001 | packet.header.qr = 1; |
1002 | packet.header.rcode = ns_r_servfail; |
1003 | |
1004 | /* Send the response. */ |
1005 | ret = sendto (fd: server_socket, buf: &packet, n: ret, |
1006 | MSG_NOSIGNAL, addr: (struct sockaddr *) &sin, addr_len: sinlen); |
1007 | if (ret < 0) |
1008 | /* The peer may have closed socket prematurely, so |
1009 | this is not an error. */ |
1010 | printf (format: "warning: sending DNS server reply: %m\n" ); |
1011 | } |
1012 | } |
1013 | } |
1014 | |
1015 | /* In the parent, close the socket. */ |
1016 | xclose (server_socket); |
1017 | |
1018 | return pid; |
1019 | } |
1020 | |
1021 | static int |
1022 | do_test (void) |
1023 | { |
1024 | support_become_root (); |
1025 | support_enter_network_namespace (); |
1026 | if (!support_in_uts_namespace () || !support_can_chroot ()) |
1027 | return EXIT_UNSUPPORTED; |
1028 | |
1029 | /* We are in an UTS namespace, so we can set the host name without |
1030 | altering the state of the entire system. */ |
1031 | if (sethostname (name: test_hostname, len: strlen (s: test_hostname)) != 0) |
1032 | FAIL_EXIT1 ("sethostname: %m" ); |
1033 | |
1034 | /* These environment variables affect resolv.conf parsing. */ |
1035 | unsetenv (name: "LOCALDOMAIN" ); |
1036 | unsetenv (name: "RES_OPTIONS" ); |
1037 | |
1038 | /* Ensure that the chroot setup worked. */ |
1039 | { |
1040 | struct support_capture_subprocess proc |
1041 | = support_capture_subprocess (callback: check_chroot_working, NULL); |
1042 | support_capture_subprocess_check (&proc, context: "chroot" , status_or_signal: 0, allowed: sc_allow_none); |
1043 | support_capture_subprocess_free (&proc); |
1044 | } |
1045 | |
1046 | pid_t server = start_dummy_server (); |
1047 | |
1048 | for (size_t i = 0; test_cases[i].name != NULL; ++i) |
1049 | { |
1050 | if (test_verbose > 0) |
1051 | printf (format: "info: running test: %s\n" , test_cases[i].name); |
1052 | TEST_VERIFY (test_cases[i].conf != NULL); |
1053 | TEST_VERIFY (test_cases[i].expected != NULL); |
1054 | |
1055 | support_write_file_string (path: chroot_env->path_resolv_conf, |
1056 | contents: test_cases[i].conf); |
1057 | |
1058 | test_file_contents (t: &test_cases[i]); |
1059 | |
1060 | /* The expected output from the empty file test is used for |
1061 | further tests. */ |
1062 | if (test_cases[i].conf[0] == '\0') |
1063 | { |
1064 | if (test_verbose > 0) |
1065 | printf (format: "info: special test: missing file\n" ); |
1066 | TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0); |
1067 | test_file_contents (t: &test_cases[i]); |
1068 | |
1069 | if (test_verbose > 0) |
1070 | printf (format: "info: special test: dangling symbolic link\n" ); |
1071 | TEST_VERIFY (symlink ("does-not-exist" , chroot_env->path_resolv_conf) == 0); |
1072 | test_file_contents (t: &test_cases[i]); |
1073 | TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0); |
1074 | |
1075 | if (test_verbose > 0) |
1076 | printf (format: "info: special test: unreadable file\n" ); |
1077 | support_write_file_string (path: chroot_env->path_resolv_conf, contents: "" ); |
1078 | TEST_VERIFY (chmod (chroot_env->path_resolv_conf, 0) == 0); |
1079 | test_file_contents (t: &test_cases[i]); |
1080 | |
1081 | /* Restore the empty file. */ |
1082 | TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0); |
1083 | support_write_file_string (path: chroot_env->path_resolv_conf, contents: "" ); |
1084 | } |
1085 | } |
1086 | |
1087 | /* The tests which do not follow a regular pattern. */ |
1088 | for (unsigned int test_index = 0; |
1089 | test_index < special_tests_count; ++test_index) |
1090 | special_test (test_index); |
1091 | |
1092 | if (server > 0) |
1093 | { |
1094 | if (kill (pid: server, SIGTERM) < 0) |
1095 | FAIL_EXIT1 ("could not terminate server process: %m" ); |
1096 | xwaitpid (server, NULL, flags: 0); |
1097 | } |
1098 | |
1099 | support_chroot_free (chroot_env); |
1100 | return 0; |
1101 | } |
1102 | |
1103 | #define PREPARE prepare |
1104 | #include <support/test-driver.c> |
1105 | |