]> git.ipfire.org Git - thirdparty/glibc.git/blob - resolv/tst-resolv-res_init-skeleton.c
resolv: Tests for various versions of res_init
[thirdparty/glibc.git] / resolv / tst-resolv-res_init-skeleton.c
1 /* Test parsing of /etc/resolv.conf. Genric version.
2 Copyright (C) 2017 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 <http://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 <gnu/lib-names.h>
25 #include <netdb.h>
26 #include <resolv/resolv-internal.h> /* For DEPRECATED_RES_USE_INET6. */
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <support/capture_subprocess.h>
30 #include <support/check.h>
31 #include <support/namespace.h>
32 #include <support/run_diff.h>
33 #include <support/support.h>
34 #include <support/temp_file.h>
35 #include <support/test-driver.h>
36 #include <support/xstdio.h>
37 #include <support/xunistd.h>
38
39 #if TEST_THREAD
40 # include <support/xthread.h>
41 #endif
42
43 /* This is the host name used to ensure predictable behavior of
44 res_init. */
45 static const char *const test_hostname = "www.example.com";
46
47 /* Path to the test root directory. */
48 static char *path_chroot;
49
50 /* Path to resolv.conf under path_chroot (outside the chroot). */
51 static char *path_resolv_conf;
52
53 static void
54 prepare (int argc, char **argv)
55 {
56 path_chroot = xasprintf ("%s/tst-resolv-res_init-XXXXXX", test_dir);
57 if (mkdtemp (path_chroot) == NULL)
58 FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", path_chroot);
59 add_temp_file (path_chroot);
60
61 /* Create the /etc directory in the chroot environment. */
62 char *path_etc = xasprintf ("%s/etc", path_chroot);
63 xmkdir (path_etc, 0777);
64 add_temp_file (path_etc);
65
66 /* Create an empty resolv.conf file. */
67 path_resolv_conf = xasprintf ("%s/resolv.conf", path_etc);
68 add_temp_file (path_resolv_conf);
69 support_write_file_string (path_resolv_conf, "");
70
71 free (path_etc);
72
73 /* valgrind needs a temporary directory in the chroot. */
74 {
75 char *path_tmp = xasprintf ("%s/tmp", path_chroot);
76 xmkdir (path_tmp, 0777);
77 add_temp_file (path_tmp);
78 free (path_tmp);
79 }
80 }
81
82 /* Verify that the chroot environment has been set up. */
83 static void
84 check_chroot_working (void *closure)
85 {
86 xchroot (path_chroot);
87 FILE *fp = xfopen (_PATH_RESCONF, "r");
88 xfclose (fp);
89
90 TEST_VERIFY_EXIT (res_init () == 0);
91 TEST_VERIFY (_res.options & RES_INIT);
92
93 char buf[100];
94 if (gethostname (buf, sizeof (buf)) < 0)
95 FAIL_EXIT1 ("gethostname: %m");
96 if (strcmp (buf, test_hostname) != 0)
97 FAIL_EXIT1 ("unexpected host name: %s", buf);
98 }
99
100 /* If FLAG is set in *OPTIONS, write NAME to FP, and clear it in
101 *OPTIONS. */
102 static void
103 print_option_flag (FILE *fp, int *options, int flag, const char *name)
104 {
105 if (*options & flag)
106 {
107 fprintf (fp, " %s", name);
108 *options &= ~flag;
109 }
110 }
111
112 /* Write a decoded version of the resolver configuration *RESP to the
113 stream FP. */
114 static void
115 print_resp (FILE *fp, res_state resp)
116 {
117 /* The options directive. */
118 {
119 /* RES_INIT is used internally for tracking initialization. */
120 TEST_VERIFY (resp->options & RES_INIT);
121 /* Also mask out other default flags which cannot be set through
122 the options directive. */
123 int options
124 = resp->options & ~(RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH);
125 if (options != 0
126 || resp->ndots != 1
127 || resp->retrans != RES_TIMEOUT
128 || resp->retry != RES_DFLRETRY)
129 {
130 fputs ("options", fp);
131 if (resp->ndots != 1)
132 fprintf (fp, " ndots:%d", resp->ndots);
133 if (resp->retrans != RES_TIMEOUT)
134 fprintf (fp, " timeout:%d", resp->retrans);
135 if (resp->retry != RES_DFLRETRY)
136 fprintf (fp, " attempts:%d", resp->retry);
137 print_option_flag (fp, &options, RES_USEVC, "use-vc");
138 print_option_flag (fp, &options, DEPRECATED_RES_USE_INET6, "inet6");
139 print_option_flag (fp, &options, RES_ROTATE, "rotate");
140 print_option_flag (fp, &options, RES_USE_EDNS0, "edns0");
141 print_option_flag (fp, &options, RES_SNGLKUP,
142 "single-request");
143 print_option_flag (fp, &options, RES_SNGLKUPREOP,
144 "single-request-reopen");
145 print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
146 fputc ('\n', fp);
147 if (options != 0)
148 fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
149 }
150 }
151
152 /* The search and domain directives. */
153 if (resp->dnsrch[0] != NULL)
154 {
155 fputs ("search", fp);
156 for (int i = 0; i < MAXDNSRCH && resp->dnsrch[i] != NULL; ++i)
157 {
158 fputc (' ', fp);
159 fputs (resp->dnsrch[i], fp);
160 }
161 fputc ('\n', fp);
162 }
163 else if (resp->defdname[0] != '\0')
164 fprintf (fp, "domain %s\n", resp->defdname);
165
166 /* The sortlist directive. */
167 if (resp->nsort > 0)
168 {
169 fputs ("sortlist", fp);
170 for (int i = 0; i < resp->nsort && i < MAXRESOLVSORT; ++i)
171 {
172 char net[20];
173 if (inet_ntop (AF_INET, &resp->sort_list[i].addr,
174 net, sizeof (net)) == NULL)
175 FAIL_EXIT1 ("inet_ntop: %m\n");
176 char mask[20];
177 if (inet_ntop (AF_INET, &resp->sort_list[i].mask,
178 mask, sizeof (mask)) == NULL)
179 FAIL_EXIT1 ("inet_ntop: %m\n");
180 fprintf (fp, " %s/%s", net, mask);
181 }
182 fputc ('\n', fp);
183 }
184
185 /* The nameserver directives. */
186 for (size_t i = 0; i < resp->nscount; ++i)
187 {
188 char host[NI_MAXHOST];
189 char service[NI_MAXSERV];
190
191 /* See get_nsaddr in res_send.c. */
192 void *addr;
193 size_t addrlen;
194 if (resp->nsaddr_list[i].sin_family == 0
195 && resp->_u._ext.nsaddrs[i] != NULL)
196 {
197 addr = resp->_u._ext.nsaddrs[i];
198 addrlen = sizeof (*resp->_u._ext.nsaddrs[i]);
199 }
200 else
201 {
202 addr = &resp->nsaddr_list[i];
203 addrlen = sizeof (resp->nsaddr_list[i]);
204 }
205
206 int ret = getnameinfo (addr, addrlen,
207 host, sizeof (host), service, sizeof (service),
208 NI_NUMERICHOST | NI_NUMERICSERV);
209 if (ret != 0)
210 {
211 if (ret == EAI_SYSTEM)
212 fprintf (fp, "; error: getnameinfo: %m\n");
213 else
214 fprintf (fp, "; error: getnameinfo: %s\n", gai_strerror (ret));
215 }
216 else
217 {
218 fprintf (fp, "nameserver %s\n", host);
219 if (strcmp (service, "53") != 0)
220 fprintf (fp, "; unrepresentable port number %s\n\n", service);
221 }
222 }
223
224 TEST_VERIFY (!ferror (fp));
225 }
226
227 /* Parameters of one test case. */
228 struct test_case
229 {
230 /* A short, descriptive name of the test. */
231 const char *name;
232
233 /* The contents of the /etc/resolv.conf file. */
234 const char *conf;
235
236 /* The expected output from print_resp. */
237 const char *expected;
238
239 /* Setting for the LOCALDOMAIN environment variable. NULL if the
240 variable is not to be set. */
241 const char *localdomain;
242
243 /* Setting for the RES_OPTIONS environment variable. NULL if the
244 variable is not to be set. */
245 const char *res_options;
246 };
247
248 enum test_init
249 {
250 test_init,
251 test_ninit,
252 test_mkquery,
253 test_gethostbyname,
254 test_getaddrinfo,
255 test_init_method_last = test_getaddrinfo
256 };
257
258 /* Closure argument for run_res_init. */
259 struct test_context
260 {
261 enum test_init init;
262 const struct test_case *t;
263 };
264
265 static void
266 setup_nss_dns_and_chroot (void)
267 {
268 /* Load nss_dns outside of the chroot. */
269 if (dlopen (LIBNSS_DNS_SO, RTLD_LAZY) == NULL)
270 FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s", dlerror ());
271 xchroot (path_chroot);
272 /* Force the use of nss_dns. */
273 __nss_configure_lookup ("hosts", "dns");
274 }
275
276 /* Run res_ninit or res_init in a subprocess and dump the parsed
277 resolver state to standard output. */
278 static void
279 run_res_init (void *closure)
280 {
281 struct test_context *ctx = closure;
282 TEST_VERIFY (getenv ("LOCALDOMAIN") == NULL);
283 TEST_VERIFY (getenv ("RES_OPTIONS") == NULL);
284 if (ctx->t->localdomain != NULL)
285 setenv ("LOCALDOMAIN", ctx->t->localdomain, 1);
286 if (ctx->t->res_options != NULL)
287 setenv ("RES_OPTIONS", ctx->t->res_options, 1);
288
289 switch (ctx->init)
290 {
291 case test_init:
292 xchroot (path_chroot);
293 TEST_VERIFY (res_init () == 0);
294 print_resp (stdout, &_res);
295 return;
296
297 case test_ninit:
298 xchroot (path_chroot);
299 res_state resp = xmalloc (sizeof (*resp));
300 memset (resp, 0, sizeof (*resp));
301 TEST_VERIFY (res_ninit (resp) == 0);
302 print_resp (stdout, resp);
303 res_nclose (resp);
304 free (resp);
305 return;
306
307 case test_mkquery:
308 xchroot (path_chroot);
309 unsigned char buf[512];
310 TEST_VERIFY (res_mkquery (QUERY, "www.example",
311 C_IN, ns_t_a, NULL, 0,
312 NULL, buf, sizeof (buf)) > 0);
313 print_resp (stdout, &_res);
314 return;
315
316 case test_gethostbyname:
317 setup_nss_dns_and_chroot ();
318 /* Trigger implicit initialization of the _res structure. The
319 actual lookup result is immaterial. */
320 (void )gethostbyname ("www.example");
321 print_resp (stdout, &_res);
322 return;
323
324 case test_getaddrinfo:
325 setup_nss_dns_and_chroot ();
326 /* Trigger implicit initialization of the _res structure. The
327 actual lookup result is immaterial. */
328 struct addrinfo *ai;
329 (void) getaddrinfo ("www.example", NULL, NULL, &ai);
330 print_resp (stdout, &_res);
331 return;
332 }
333
334 FAIL_EXIT1 ("invalid init method %d", ctx->init);
335 }
336
337 #if TEST_THREAD
338 /* Helper function which calls run_res_init from a thread. */
339 static void *
340 run_res_init_thread_func (void *closure)
341 {
342 run_res_init (closure);
343 return NULL;
344 }
345
346 /* Variant of res_run_init which runs the function on a non-main
347 thread. */
348 static void
349 run_res_init_on_thread (void *closure)
350 {
351 xpthread_join (xpthread_create (NULL, run_res_init_thread_func, closure));
352 }
353 #endif /* TEST_THREAD */
354
355 struct test_case test_cases[] =
356 {
357 {.name = "empty file",
358 .conf = "",
359 .expected = "search example.com\n"
360 "nameserver 127.0.0.1\n"
361 },
362 {.name = "empty file with LOCALDOMAIN",
363 .conf = "",
364 .expected = "search example.net\n"
365 "nameserver 127.0.0.1\n",
366 .localdomain = "example.net",
367 },
368 {.name = "empty file with RES_OPTIONS",
369 .conf = "",
370 .expected = "options attempts:5 edns0\n"
371 "search example.com\n"
372 "nameserver 127.0.0.1\n",
373 .res_options = "edns0 attempts:5",
374 },
375 {.name = "empty file with RES_OPTIONS and LOCALDOMAIN",
376 .conf = "",
377 .expected = "options attempts:5 edns0\n"
378 "search example.org\n"
379 "nameserver 127.0.0.1\n",
380 .localdomain = "example.org",
381 .res_options = "edns0 attempts:5",
382 },
383 {.name = "basic",
384 .conf = "domain example.net\n"
385 "search corp.example.com example.com\n"
386 "nameserver 192.0.2.1\n",
387 .expected = "search corp.example.com example.com\n"
388 "nameserver 192.0.2.1\n"
389 },
390 {.name = "whitespace",
391 .conf = "# This test covers comment and whitespace processing "
392 " (trailing whitespace,\n"
393 "# missing newline at end of file).\n"
394 "\n"
395 "domain example.net\n"
396 ";search commented out\n"
397 "search corp.example.com\texample.com\n"
398 "#nameserver 192.0.2.3\n"
399 "nameserver 192.0.2.1 \n"
400 "nameserver 192.0.2.2", /* No \n at end of file. */
401 .expected = "search corp.example.com example.com\n"
402 "nameserver 192.0.2.1\n"
403 "nameserver 192.0.2.2\n"
404 },
405 {.name = "option values, multiple servers",
406 .conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
407 "domain example.net\n"
408 ";domain comment\n"
409 "search corp.example.com\texample.com\n"
410 "nameserver 192.0.2.1\n"
411 "nameserver ::1\n"
412 "nameserver 192.0.2.2\n",
413 .expected = "options ndots:3 timeout:19 attempts:5 inet6 edns0\n"
414 "search corp.example.com example.com\n"
415 "nameserver 192.0.2.1\n"
416 "nameserver ::1\n"
417 "nameserver 192.0.2.2\n"
418 },
419 {.name = "out-of-range option vales",
420 .conf = "options use-vc timeout:999 attempts:999 ndots:99\n"
421 "search example.com\n",
422 .expected = "options ndots:15 timeout:30 attempts:5 use-vc\n"
423 "search example.com\n"
424 "nameserver 127.0.0.1\n"
425 },
426 {.name = "repeated directives",
427 .conf = "options ndots:3 use-vc\n"
428 "options edns0 ndots:2\n"
429 "domain corp.example\n"
430 "search example.net corp.example.com example.com\n"
431 "search example.org\n"
432 "search\n",
433 .expected = "options ndots:2 use-vc edns0\n"
434 "search example.org\n"
435 "nameserver 127.0.0.1\n"
436 },
437 {.name = "many name servers, sortlist",
438 .conf = "options single-request\n"
439 "search example.org example.com example.net corp.example.com\n"
440 "sortlist 192.0.2.0/255.255.255.0\n"
441 "nameserver 192.0.2.1\n"
442 "nameserver 192.0.2.2\n"
443 "nameserver 192.0.2.3\n"
444 "nameserver 192.0.2.4\n"
445 "nameserver 192.0.2.5\n"
446 "nameserver 192.0.2.6\n"
447 "nameserver 192.0.2.7\n"
448 "nameserver 192.0.2.8\n",
449 .expected = "options single-request\n"
450 "search example.org example.com example.net corp.example.com\n"
451 "sortlist 192.0.2.0/255.255.255.0\n"
452 "nameserver 192.0.2.1\n"
453 "nameserver 192.0.2.2\n"
454 "nameserver 192.0.2.3\n"
455 },
456 {.name = "IPv4 and IPv6 nameservers",
457 .conf = "options single-request\n"
458 "search example.org example.com example.net corp.example.com"
459 " legacy.example.com\n"
460 "sortlist 192.0.2.0\n"
461 "nameserver 192.0.2.1\n"
462 "nameserver 2001:db8::2\n"
463 "nameserver 192.0.2.3\n"
464 "nameserver 2001:db8::4\n"
465 "nameserver 192.0.2.5\n"
466 "nameserver 2001:db8::6\n"
467 "nameserver 192.0.2.7\n"
468 "nameserver 2001:db8::8\n",
469 .expected = "options single-request\n"
470 "search example.org example.com example.net corp.example.com"
471 " legacy.example.com\n"
472 "sortlist 192.0.2.0/255.255.255.0\n"
473 "nameserver 192.0.2.1\n"
474 "nameserver 2001:db8::2\n"
475 "nameserver 192.0.2.3\n"
476 },
477 {.name = "garbage after nameserver",
478 .conf = "nameserver 192.0.2.1 garbage\n"
479 "nameserver 192.0.2.2:5353\n"
480 "nameserver 192.0.2.3 5353\n",
481 .expected = "search example.com\n"
482 "nameserver 192.0.2.1\n"
483 "nameserver 192.0.2.3\n"
484 },
485 {.name = "RES_OPTIONS is cummulative",
486 .conf = "options timeout:7 ndots:2 use-vc\n"
487 "nameserver 192.0.2.1\n",
488 .expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n"
489 "search example.com\n"
490 "nameserver 192.0.2.1\n",
491 .res_options = "attempts:5 ndots:3 edns0 ",
492 },
493 { NULL }
494 };
495
496 /* Run the indicated test case. This function assumes that the chroot
497 contents has already been set up. */
498 static void
499 test_file_contents (const struct test_case *t)
500 {
501 #if TEST_THREAD
502 for (int do_thread = 0; do_thread < 2; ++do_thread)
503 #endif
504 for (int init_method = 0; init_method <= test_init_method_last;
505 ++init_method)
506 {
507 if (test_verbose > 0)
508 printf ("info: testing init method %d\n", init_method);
509 struct test_context ctx = { .init = init_method, .t = t };
510 void (*func) (void *) = run_res_init;
511 #if TEST_THREAD
512 if (do_thread)
513 func = run_res_init_on_thread;
514 #endif
515 struct support_capture_subprocess proc
516 = support_capture_subprocess (func, &ctx);
517 if (strcmp (proc.out.buffer, t->expected) != 0)
518 {
519 support_record_failure ();
520 printf ("error: output mismatch for %s\n", t->name);
521 support_run_diff ("expected", t->expected,
522 "actual", proc.out.buffer);
523 }
524 support_capture_subprocess_check (&proc, t->name, 0,
525 sc_allow_stdout);
526 support_capture_subprocess_free (&proc);
527 }
528 }
529
530 static int
531 do_test (void)
532 {
533 support_become_root ();
534 support_enter_network_namespace ();
535 if (!support_in_uts_namespace () || !support_can_chroot ())
536 return EXIT_UNSUPPORTED;
537
538 /* We are in an UTS namespace, so we can set the host name without
539 altering the state of the entire system. */
540 if (sethostname (test_hostname, strlen (test_hostname)) != 0)
541 FAIL_EXIT1 ("sethostname: %m");
542
543 /* These environment variables affect resolv.conf parsing. */
544 unsetenv ("LOCALDOMAIN");
545 unsetenv ("RES_OPTIONS");
546
547 /* Ensure that the chroot setup worked. */
548 {
549 struct support_capture_subprocess proc
550 = support_capture_subprocess (check_chroot_working, NULL);
551 support_capture_subprocess_check (&proc, "chroot", 0, sc_allow_none);
552 support_capture_subprocess_free (&proc);
553 }
554
555 for (size_t i = 0; test_cases[i].name != NULL; ++i)
556 {
557 if (test_verbose > 0)
558 printf ("info: running test: %s\n", test_cases[i].name);
559 TEST_VERIFY (test_cases[i].conf != NULL);
560 TEST_VERIFY (test_cases[i].expected != NULL);
561
562 support_write_file_string (path_resolv_conf, test_cases[i].conf);
563
564 test_file_contents (&test_cases[i]);
565
566 /* The expected output from the empty file test is used for
567 further tests. */
568 if (test_cases[i].conf[0] == '\0')
569 {
570 if (test_verbose > 0)
571 printf ("info: special test: missing file\n");
572 TEST_VERIFY (unlink (path_resolv_conf) == 0);
573 test_file_contents (&test_cases[i]);
574
575 if (test_verbose > 0)
576 printf ("info: special test: dangling symbolic link\n");
577 TEST_VERIFY (symlink ("does-not-exist", path_resolv_conf) == 0);
578 test_file_contents (&test_cases[i]);
579 TEST_VERIFY (unlink (path_resolv_conf) == 0);
580
581 if (test_verbose > 0)
582 printf ("info: special test: unreadable file\n");
583 support_write_file_string (path_resolv_conf, "");
584 TEST_VERIFY (chmod (path_resolv_conf, 0) == 0);
585 test_file_contents (&test_cases[i]);
586
587 /* Restore the empty file. */
588 TEST_VERIFY (unlink (path_resolv_conf) == 0);
589 support_write_file_string (path_resolv_conf, "");
590 }
591 }
592
593 free (path_chroot);
594 path_chroot = NULL;
595 free (path_resolv_conf);
596 path_resolv_conf = NULL;
597 return 0;
598 }
599
600 #define PREPARE prepare
601 #include <support/test-driver.c>