mirror of
git://sourceware.org/git/glibc.git
synced 2024-11-21 01:12:26 +08:00
1115 lines
35 KiB
C
1115 lines
35 KiB
C
/* Test parsing of /etc/resolv.conf. Generic version.
|
|
Copyright (C) 2017-2023 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with the GNU C Library; if not, see
|
|
<https://www.gnu.org/licenses/>. */
|
|
|
|
/* Before including this file, TEST_THREAD has to be defined to 0 or
|
|
1, depending on whether the threading tests should be compiled
|
|
in. */
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <gnu/lib-names.h>
|
|
#include <netdb.h>
|
|
#include <resolv/resolv_context.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <support/capture_subprocess.h>
|
|
#include <support/check.h>
|
|
#include <support/namespace.h>
|
|
#include <support/run_diff.h>
|
|
#include <support/support.h>
|
|
#include <support/temp_file.h>
|
|
#include <support/test-driver.h>
|
|
#include <support/xsocket.h>
|
|
#include <support/xstdio.h>
|
|
#include <support/xunistd.h>
|
|
|
|
#if TEST_THREAD
|
|
# include <support/xthread.h>
|
|
#endif
|
|
|
|
/* This is the host name used to ensure predictable behavior of
|
|
res_init. */
|
|
static const char *const test_hostname = "www.example.com";
|
|
|
|
struct support_chroot *chroot_env;
|
|
|
|
static void
|
|
prepare (int argc, char **argv)
|
|
{
|
|
chroot_env = support_chroot_create
|
|
((struct support_chroot_configuration)
|
|
{
|
|
.resolv_conf = "",
|
|
});
|
|
}
|
|
|
|
/* Verify that the chroot environment has been set up. */
|
|
static void
|
|
check_chroot_working (void *closure)
|
|
{
|
|
xchroot (chroot_env->path_chroot);
|
|
FILE *fp = xfopen (_PATH_RESCONF, "r");
|
|
xfclose (fp);
|
|
|
|
TEST_VERIFY_EXIT (res_init () == 0);
|
|
TEST_VERIFY (_res.options & RES_INIT);
|
|
|
|
char buf[100];
|
|
if (gethostname (buf, sizeof (buf)) < 0)
|
|
FAIL_EXIT1 ("gethostname: %m");
|
|
if (strcmp (buf, test_hostname) != 0)
|
|
FAIL_EXIT1 ("unexpected host name: %s", buf);
|
|
}
|
|
|
|
/* If FLAG is set in *OPTIONS, write NAME to FP, and clear it in
|
|
*OPTIONS. */
|
|
static void
|
|
print_option_flag (FILE *fp, int *options, int flag, const char *name)
|
|
{
|
|
if (*options & flag)
|
|
{
|
|
fprintf (fp, " %s", name);
|
|
*options &= ~flag;
|
|
}
|
|
}
|
|
|
|
/* Write a decoded version of the resolver configuration *RESP to the
|
|
stream FP. */
|
|
static void
|
|
print_resp (FILE *fp, res_state resp)
|
|
{
|
|
struct resolv_context *ctx = __resolv_context_get_override (resp);
|
|
TEST_VERIFY_EXIT (ctx != NULL);
|
|
if (ctx->conf == NULL)
|
|
fprintf (fp, "; extended resolver state missing\n");
|
|
|
|
/* The options directive. */
|
|
{
|
|
/* RES_INIT is used internally for tracking initialization. */
|
|
TEST_VERIFY (resp->options & RES_INIT);
|
|
/* Also mask out other default flags which cannot be set through
|
|
the options directive. */
|
|
int options
|
|
= resp->options & ~(RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH);
|
|
if (options != 0
|
|
|| resp->ndots != 1
|
|
|| resp->retrans != RES_TIMEOUT
|
|
|| resp->retry != RES_DFLRETRY)
|
|
{
|
|
fputs ("options", fp);
|
|
if (resp->ndots != 1)
|
|
fprintf (fp, " ndots:%d", resp->ndots);
|
|
if (resp->retrans != RES_TIMEOUT)
|
|
fprintf (fp, " timeout:%d", resp->retrans);
|
|
if (resp->retry != RES_DFLRETRY)
|
|
fprintf (fp, " attempts:%d", resp->retry);
|
|
print_option_flag (fp, &options, RES_USEVC, "use-vc");
|
|
print_option_flag (fp, &options, RES_ROTATE, "rotate");
|
|
print_option_flag (fp, &options, RES_USE_EDNS0, "edns0");
|
|
print_option_flag (fp, &options, RES_SNGLKUP,
|
|
"single-request");
|
|
print_option_flag (fp, &options, RES_SNGLKUPREOP,
|
|
"single-request-reopen");
|
|
print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
|
|
print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
|
|
print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
|
|
print_option_flag (fp, &options, RES_NOAAAA, "no-aaaa");
|
|
fputc ('\n', fp);
|
|
if (options != 0)
|
|
fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
|
|
}
|
|
}
|
|
|
|
/* The search and domain directives. */
|
|
if (resp->dnsrch[0] != NULL)
|
|
{
|
|
fputs ("search", fp);
|
|
for (int i = 0; i < MAXDNSRCH && resp->dnsrch[i] != NULL; ++i)
|
|
{
|
|
fputc (' ', fp);
|
|
fputs (resp->dnsrch[i], fp);
|
|
}
|
|
fputc ('\n', fp);
|
|
}
|
|
else if (resp->defdname[0] != '\0')
|
|
fprintf (fp, "domain %s\n", resp->defdname);
|
|
|
|
/* The extended search path. */
|
|
{
|
|
size_t i = 0;
|
|
while (true)
|
|
{
|
|
const char *name = __resolv_context_search_list (ctx, i);
|
|
if (name == NULL)
|
|
break;
|
|
fprintf (fp, "; search[%zu]: %s\n", i, name);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
/* The sortlist directive. */
|
|
if (resp->nsort > 0)
|
|
{
|
|
fputs ("sortlist", fp);
|
|
for (int i = 0; i < resp->nsort && i < MAXRESOLVSORT; ++i)
|
|
{
|
|
char net[20];
|
|
if (inet_ntop (AF_INET, &resp->sort_list[i].addr,
|
|
net, sizeof (net)) == NULL)
|
|
FAIL_EXIT1 ("inet_ntop: %m\n");
|
|
char mask[20];
|
|
if (inet_ntop (AF_INET, &resp->sort_list[i].mask,
|
|
mask, sizeof (mask)) == NULL)
|
|
FAIL_EXIT1 ("inet_ntop: %m\n");
|
|
fprintf (fp, " %s/%s", net, mask);
|
|
}
|
|
fputc ('\n', fp);
|
|
}
|
|
|
|
/* The nameserver directives. */
|
|
for (size_t i = 0; i < resp->nscount; ++i)
|
|
{
|
|
char host[NI_MAXHOST];
|
|
char service[NI_MAXSERV];
|
|
|
|
/* See get_nsaddr in res_send.c. */
|
|
void *addr;
|
|
size_t addrlen;
|
|
if (resp->nsaddr_list[i].sin_family == 0
|
|
&& resp->_u._ext.nsaddrs[i] != NULL)
|
|
{
|
|
addr = resp->_u._ext.nsaddrs[i];
|
|
addrlen = sizeof (*resp->_u._ext.nsaddrs[i]);
|
|
}
|
|
else
|
|
{
|
|
addr = &resp->nsaddr_list[i];
|
|
addrlen = sizeof (resp->nsaddr_list[i]);
|
|
}
|
|
|
|
int ret = getnameinfo (addr, addrlen,
|
|
host, sizeof (host), service, sizeof (service),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
if (ret != 0)
|
|
{
|
|
if (ret == EAI_SYSTEM)
|
|
fprintf (fp, "; error: getnameinfo: %m\n");
|
|
else
|
|
fprintf (fp, "; error: getnameinfo: %s\n", gai_strerror (ret));
|
|
}
|
|
else
|
|
{
|
|
fprintf (fp, "nameserver %s\n", host);
|
|
if (strcmp (service, "53") != 0)
|
|
fprintf (fp, "; unrepresentable port number %s\n\n", service);
|
|
}
|
|
}
|
|
|
|
/* The extended name server list. */
|
|
{
|
|
size_t i = 0;
|
|
while (true)
|
|
{
|
|
const struct sockaddr *addr = __resolv_context_nameserver (ctx, i);
|
|
if (addr == NULL)
|
|
break;
|
|
size_t addrlen;
|
|
switch (addr->sa_family)
|
|
{
|
|
case AF_INET:
|
|
addrlen = sizeof (struct sockaddr_in);
|
|
break;
|
|
case AF_INET6:
|
|
addrlen = sizeof (struct sockaddr_in6);
|
|
break;
|
|
default:
|
|
FAIL_EXIT1 ("invalid address family %d", addr->sa_family);
|
|
}
|
|
|
|
char host[NI_MAXHOST];
|
|
char service[NI_MAXSERV];
|
|
int ret = getnameinfo (addr, addrlen,
|
|
host, sizeof (host), service, sizeof (service),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
|
|
if (ret != 0)
|
|
{
|
|
if (ret == EAI_SYSTEM)
|
|
fprintf (fp, "; error: getnameinfo: %m\n");
|
|
else
|
|
fprintf (fp, "; error: getnameinfo: %s\n", gai_strerror (ret));
|
|
}
|
|
else
|
|
fprintf (fp, "; nameserver[%zu]: [%s]:%s\n", i, host, service);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
TEST_VERIFY (!ferror (fp));
|
|
|
|
__resolv_context_put (ctx);
|
|
}
|
|
|
|
/* Parameters of one test case. */
|
|
struct test_case
|
|
{
|
|
/* A short, descriptive name of the test. */
|
|
const char *name;
|
|
|
|
/* The contents of the /etc/resolv.conf file. */
|
|
const char *conf;
|
|
|
|
/* The expected output from print_resp. */
|
|
const char *expected;
|
|
|
|
/* Setting for the LOCALDOMAIN environment variable. NULL if the
|
|
variable is not to be set. */
|
|
const char *localdomain;
|
|
|
|
/* Setting for the RES_OPTIONS environment variable. NULL if the
|
|
variable is not to be set. */
|
|
const char *res_options;
|
|
|
|
/* Override the system host name. NULL means that no change is made
|
|
and the default is used (test_hostname). */
|
|
const char *hostname;
|
|
};
|
|
|
|
enum test_init
|
|
{
|
|
test_init,
|
|
test_ninit,
|
|
test_mkquery,
|
|
test_gethostbyname,
|
|
test_getaddrinfo,
|
|
test_init_method_last = test_getaddrinfo
|
|
};
|
|
|
|
static const char *const test_init_names[] =
|
|
{
|
|
[test_init] = "res_init",
|
|
[test_ninit] = "res_ninit",
|
|
[test_mkquery] = "res_mkquery",
|
|
[test_gethostbyname] = "gethostbyname",
|
|
[test_getaddrinfo] = "getaddrinfo",
|
|
};
|
|
|
|
/* Closure argument for run_res_init. */
|
|
struct test_context
|
|
{
|
|
enum test_init init;
|
|
const struct test_case *t;
|
|
};
|
|
|
|
static void
|
|
setup_nss_dns_and_chroot (void)
|
|
{
|
|
/* Load nss_dns outside of the chroot. */
|
|
if (dlopen (LIBNSS_DNS_SO, RTLD_LAZY) == NULL)
|
|
FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s", dlerror ());
|
|
xchroot (chroot_env->path_chroot);
|
|
/* Force the use of nss_dns. */
|
|
__nss_configure_lookup ("hosts", "dns");
|
|
}
|
|
|
|
/* Run res_ninit or res_init in a subprocess and dump the parsed
|
|
resolver state to standard output. */
|
|
static void
|
|
run_res_init (void *closure)
|
|
{
|
|
struct test_context *ctx = closure;
|
|
TEST_VERIFY (getenv ("LOCALDOMAIN") == NULL);
|
|
TEST_VERIFY (getenv ("RES_OPTIONS") == NULL);
|
|
if (ctx->t->localdomain != NULL)
|
|
setenv ("LOCALDOMAIN", ctx->t->localdomain, 1);
|
|
if (ctx->t->res_options != NULL)
|
|
setenv ("RES_OPTIONS", ctx->t->res_options, 1);
|
|
if (ctx->t->hostname != NULL)
|
|
{
|
|
#ifdef CLONE_NEWUTS
|
|
/* This test needs its own namespace, to avoid changing the host
|
|
name for the parent, too. */
|
|
TEST_VERIFY_EXIT (unshare (CLONE_NEWUTS) == 0);
|
|
if (sethostname (ctx->t->hostname, strlen (ctx->t->hostname)) != 0)
|
|
FAIL_EXIT1 ("sethostname (\"%s\"): %m", ctx->t->hostname);
|
|
#else
|
|
FAIL_UNSUPPORTED ("clone (CLONE_NEWUTS) not supported");
|
|
#endif
|
|
}
|
|
|
|
switch (ctx->init)
|
|
{
|
|
case test_init:
|
|
xchroot (chroot_env->path_chroot);
|
|
TEST_VERIFY (res_init () == 0);
|
|
print_resp (stdout, &_res);
|
|
return;
|
|
|
|
case test_ninit:
|
|
xchroot (chroot_env->path_chroot);
|
|
res_state resp = xmalloc (sizeof (*resp));
|
|
memset (resp, 0, sizeof (*resp));
|
|
TEST_VERIFY (res_ninit (resp) == 0);
|
|
print_resp (stdout, resp);
|
|
res_nclose (resp);
|
|
free (resp);
|
|
return;
|
|
|
|
case test_mkquery:
|
|
xchroot (chroot_env->path_chroot);
|
|
unsigned char buf[512];
|
|
TEST_VERIFY (res_mkquery (QUERY, "www.example",
|
|
C_IN, ns_t_a, NULL, 0,
|
|
NULL, buf, sizeof (buf)) > 0);
|
|
print_resp (stdout, &_res);
|
|
return;
|
|
|
|
case test_gethostbyname:
|
|
setup_nss_dns_and_chroot ();
|
|
/* Trigger implicit initialization of the _res structure. The
|
|
actual lookup result is immaterial. */
|
|
(void )gethostbyname ("www.example");
|
|
print_resp (stdout, &_res);
|
|
return;
|
|
|
|
case test_getaddrinfo:
|
|
setup_nss_dns_and_chroot ();
|
|
/* Trigger implicit initialization of the _res structure. The
|
|
actual lookup result is immaterial. */
|
|
struct addrinfo *ai;
|
|
(void) getaddrinfo ("www.example", NULL, NULL, &ai);
|
|
print_resp (stdout, &_res);
|
|
return;
|
|
}
|
|
|
|
FAIL_EXIT1 ("invalid init method %d", ctx->init);
|
|
}
|
|
|
|
#if TEST_THREAD
|
|
/* Helper function which calls run_res_init from a thread. */
|
|
static void *
|
|
run_res_init_thread_func (void *closure)
|
|
{
|
|
run_res_init (closure);
|
|
return NULL;
|
|
}
|
|
|
|
/* Variant of res_run_init which runs the function on a non-main
|
|
thread. */
|
|
static void
|
|
run_res_init_on_thread (void *closure)
|
|
{
|
|
xpthread_join (xpthread_create (NULL, run_res_init_thread_func, closure));
|
|
}
|
|
#endif /* TEST_THREAD */
|
|
|
|
struct test_case test_cases[] =
|
|
{
|
|
{.name = "empty file",
|
|
.conf = "",
|
|
.expected = "search example.com\n"
|
|
"; search[0]: example.com\n"
|
|
"nameserver 127.0.0.1\n"
|
|
"; nameserver[0]: [127.0.0.1]:53\n"
|
|
},
|
|
{.name = "empty file, no-dot hostname",
|
|
.conf = "",
|
|
.expected = "nameserver 127.0.0.1\n"
|
|
"; nameserver[0]: [127.0.0.1]:53\n",
|
|
.hostname = "example",
|
|
},
|
|
{.name = "empty file with LOCALDOMAIN",
|
|
.conf = "",
|
|
.expected = "search example.net\n"
|
|
"; search[0]: example.net\n"
|
|
"nameserver 127.0.0.1\n"
|
|
"; nameserver[0]: [127.0.0.1]:53\n",
|
|
.localdomain = "example.net",
|
|
},
|
|
{.name = "empty file with RES_OPTIONS",
|
|
.conf = "",
|
|
.expected = "options attempts:5 edns0\n"
|
|
"search example.com\n"
|
|
"; search[0]: example.com\n"
|
|
"nameserver 127.0.0.1\n"
|
|
"; nameserver[0]: [127.0.0.1]:53\n",
|
|
.res_options = "edns0 attempts:5",
|
|
},
|
|
{.name = "empty file with RES_OPTIONS and LOCALDOMAIN",
|
|
.conf = "",
|
|
.expected = "options attempts:5 edns0\n"
|
|
"search example.org\n"
|
|
"; search[0]: example.org\n"
|
|
"nameserver 127.0.0.1\n"
|
|
"; nameserver[0]: [127.0.0.1]:53\n",
|
|
.localdomain = "example.org",
|
|
.res_options = "edns0 attempts:5",
|
|
},
|
|
{.name = "basic",
|
|
.conf = "search corp.example.com example.com\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "search corp.example.com example.com\n"
|
|
"; search[0]: corp.example.com\n"
|
|
"; search[1]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "basic with no-dot hostname",
|
|
.conf = "search corp.example.com example.com\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "search corp.example.com example.com\n"
|
|
"; search[0]: corp.example.com\n"
|
|
"; search[1]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n",
|
|
.hostname = "example",
|
|
},
|
|
{.name = "basic no-reload",
|
|
.conf = "options no-reload\n"
|
|
"search corp.example.com example.com\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "options no-reload\n"
|
|
"search corp.example.com example.com\n"
|
|
"; search[0]: corp.example.com\n"
|
|
"; search[1]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "basic no-reload via RES_OPTIONS",
|
|
.conf = "search corp.example.com example.com\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "options no-reload\n"
|
|
"search corp.example.com example.com\n"
|
|
"; search[0]: corp.example.com\n"
|
|
"; search[1]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n",
|
|
.res_options = "no-reload"
|
|
},
|
|
{.name = "whitespace",
|
|
.conf = "# This test covers comment and whitespace processing "
|
|
" (trailing whitespace,\n"
|
|
"# missing newline at end of file).\n"
|
|
"\n"
|
|
";search commented out\n"
|
|
"search corp.example.com\texample.com \n"
|
|
"#nameserver 192.0.2.3\n"
|
|
"nameserver 192.0.2.1 \n"
|
|
"nameserver 192.0.2.2", /* No \n at end of file. */
|
|
.expected = "search corp.example.com example.com\n"
|
|
"; search[0]: corp.example.com\n"
|
|
"; search[1]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 192.0.2.2\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
"; nameserver[1]: [192.0.2.2]:53\n"
|
|
},
|
|
{.name = "domain",
|
|
.conf = "domain example.net\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "search example.net\n"
|
|
"; search[0]: example.net\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "domain space",
|
|
.conf = "domain example.net \n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "search example.net\n"
|
|
"; search[0]: example.net\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "domain tab",
|
|
.conf = "domain example.net\t\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "search example.net\n"
|
|
"; search[0]: example.net\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "domain override",
|
|
.conf = "search example.com example.org\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"domain example.net", /* No \n at end of file. */
|
|
.expected = "search example.net\n"
|
|
"; search[0]: example.net\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "option values, multiple servers",
|
|
.conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
|
|
"domain example.net\n"
|
|
";domain comment\n"
|
|
"search corp.example.com\texample.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver ::1\n"
|
|
"nameserver 192.0.2.2\n",
|
|
.expected = "options ndots:3 timeout:19 attempts:5 edns0\n"
|
|
"search corp.example.com example.com\n"
|
|
"; search[0]: corp.example.com\n"
|
|
"; search[1]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver ::1\n"
|
|
"nameserver 192.0.2.2\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
"; nameserver[1]: [::1]:53\n"
|
|
"; nameserver[2]: [192.0.2.2]:53\n"
|
|
},
|
|
{.name = "out-of-range option vales",
|
|
.conf = "options use-vc timeout:999 attempts:999 ndots:99\n"
|
|
"search example.com\n",
|
|
.expected = "options ndots:15 timeout:30 attempts:5 use-vc\n"
|
|
"search example.com\n"
|
|
"; search[0]: example.com\n"
|
|
"nameserver 127.0.0.1\n"
|
|
"; nameserver[0]: [127.0.0.1]:53\n"
|
|
},
|
|
{.name = "repeated directives",
|
|
.conf = "options ndots:3 use-vc\n"
|
|
"options edns0 ndots:2\n"
|
|
"domain corp.example\n"
|
|
"search example.net corp.example.com example.com\n"
|
|
"search example.org\n"
|
|
"search\n",
|
|
.expected = "options ndots:2 use-vc edns0\n"
|
|
"search example.org\n"
|
|
"; search[0]: example.org\n"
|
|
"nameserver 127.0.0.1\n"
|
|
"; nameserver[0]: [127.0.0.1]:53\n"
|
|
},
|
|
{.name = "many name servers, sortlist",
|
|
.conf = "options single-request\n"
|
|
"search example.org example.com example.net corp.example.com\n"
|
|
"sortlist 192.0.2.0/255.255.255.0\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 192.0.2.2\n"
|
|
"nameserver 192.0.2.3\n"
|
|
"nameserver 192.0.2.4\n"
|
|
"nameserver 192.0.2.5\n"
|
|
"nameserver 192.0.2.6\n"
|
|
"nameserver 192.0.2.7\n"
|
|
"nameserver 192.0.2.8\n",
|
|
.expected = "options single-request\n"
|
|
"search example.org example.com example.net corp.example.com\n"
|
|
"; search[0]: example.org\n"
|
|
"; search[1]: example.com\n"
|
|
"; search[2]: example.net\n"
|
|
"; search[3]: corp.example.com\n"
|
|
"sortlist 192.0.2.0/255.255.255.0\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 192.0.2.2\n"
|
|
"nameserver 192.0.2.3\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
"; nameserver[1]: [192.0.2.2]:53\n"
|
|
"; nameserver[2]: [192.0.2.3]:53\n"
|
|
"; nameserver[3]: [192.0.2.4]:53\n"
|
|
"; nameserver[4]: [192.0.2.5]:53\n"
|
|
"; nameserver[5]: [192.0.2.6]:53\n"
|
|
"; nameserver[6]: [192.0.2.7]:53\n"
|
|
"; nameserver[7]: [192.0.2.8]:53\n"
|
|
},
|
|
{.name = "IPv4 and IPv6 nameservers",
|
|
.conf = "options single-request\n"
|
|
"search example.org example.com example.net corp.example.com"
|
|
" legacy.example.com\n"
|
|
"sortlist 192.0.2.0\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 2001:db8::2\n"
|
|
"nameserver 192.0.2.3\n"
|
|
"nameserver 2001:db8::4\n"
|
|
"nameserver 192.0.2.5\n"
|
|
"nameserver 2001:db8::6\n"
|
|
"nameserver 192.0.2.7\n"
|
|
"nameserver 2001:db8::8\n",
|
|
.expected = "options single-request\n"
|
|
"search example.org example.com example.net corp.example.com"
|
|
" legacy.example.com\n"
|
|
"; search[0]: example.org\n"
|
|
"; search[1]: example.com\n"
|
|
"; search[2]: example.net\n"
|
|
"; search[3]: corp.example.com\n"
|
|
"; search[4]: legacy.example.com\n"
|
|
"sortlist 192.0.2.0/255.255.255.0\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 2001:db8::2\n"
|
|
"nameserver 192.0.2.3\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
"; nameserver[1]: [2001:db8::2]:53\n"
|
|
"; nameserver[2]: [192.0.2.3]:53\n"
|
|
"; nameserver[3]: [2001:db8::4]:53\n"
|
|
"; nameserver[4]: [192.0.2.5]:53\n"
|
|
"; nameserver[5]: [2001:db8::6]:53\n"
|
|
"; nameserver[6]: [192.0.2.7]:53\n"
|
|
"; nameserver[7]: [2001:db8::8]:53\n",
|
|
},
|
|
{.name = "garbage after nameserver",
|
|
.conf = "nameserver 192.0.2.1 garbage\n"
|
|
"nameserver 192.0.2.2:5353\n"
|
|
"nameserver 192.0.2.3 5353\n",
|
|
.expected = "search example.com\n"
|
|
"; search[0]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 192.0.2.3\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
"; nameserver[1]: [192.0.2.3]:53\n"
|
|
},
|
|
{.name = "RES_OPTIONS is cumulative",
|
|
.conf = "options timeout:7 ndots:2 use-vc\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n"
|
|
"search example.com\n"
|
|
"; search[0]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n",
|
|
.res_options = "attempts:5 ndots:3 edns0 ",
|
|
},
|
|
{.name = "many search list entries (bug 19569)",
|
|
.conf = "nameserver 192.0.2.1\n"
|
|
"search corp.example.com support.example.com"
|
|
" community.example.org wan.example.net vpn.example.net"
|
|
" example.com example.org example.net\n",
|
|
.expected = "search corp.example.com support.example.com"
|
|
" community.example.org wan.example.net vpn.example.net example.com\n"
|
|
"; search[0]: corp.example.com\n"
|
|
"; search[1]: support.example.com\n"
|
|
"; search[2]: community.example.org\n"
|
|
"; search[3]: wan.example.net\n"
|
|
"; search[4]: vpn.example.net\n"
|
|
"; search[5]: example.com\n"
|
|
"; search[6]: example.org\n"
|
|
"; search[7]: example.net\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "very long search list entries (bug 21475)",
|
|
.conf = "nameserver 192.0.2.1\n"
|
|
"search example.com "
|
|
#define H63 "this-host-name-is-longer-than-yours-yes-I-really-really-mean-it"
|
|
#define D63 "this-domain-name-is-as-long-as-the-previous-name--63-characters"
|
|
" " H63 "." D63 ".example.org"
|
|
" " H63 "." D63 ".example.net\n",
|
|
.expected = "search example.com " H63 "." D63 ".example.org\n"
|
|
"; search[0]: example.com\n"
|
|
"; search[1]: " H63 "." D63 ".example.org\n"
|
|
"; search[2]: " H63 "." D63 ".example.net\n"
|
|
#undef H63
|
|
#undef D63
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "trust-ad flag",
|
|
.conf = "options trust-ad\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "options trust-ad\n"
|
|
"search example.com\n"
|
|
"; search[0]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{.name = "no-aaaa flag",
|
|
.conf = "options no-aaaa\n"
|
|
"nameserver 192.0.2.1\n",
|
|
.expected = "options no-aaaa\n"
|
|
"search example.com\n"
|
|
"; search[0]: example.com\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"; nameserver[0]: [192.0.2.1]:53\n"
|
|
},
|
|
{ NULL }
|
|
};
|
|
|
|
/* Run the indicated test case. This function assumes that the chroot
|
|
contents has already been set up. */
|
|
static void
|
|
test_file_contents (const struct test_case *t)
|
|
{
|
|
#if TEST_THREAD
|
|
for (int do_thread = 0; do_thread < 2; ++do_thread)
|
|
#endif
|
|
for (int init_method = 0; init_method <= test_init_method_last;
|
|
++init_method)
|
|
{
|
|
if (test_verbose > 0)
|
|
printf ("info: testing init method %s\n",
|
|
test_init_names[init_method]);
|
|
struct test_context ctx = { .init = init_method, .t = t };
|
|
void (*func) (void *) = run_res_init;
|
|
#if TEST_THREAD
|
|
if (do_thread)
|
|
func = run_res_init_on_thread;
|
|
#endif
|
|
struct support_capture_subprocess proc
|
|
= support_capture_subprocess (func, &ctx);
|
|
if (strcmp (proc.out.buffer, t->expected) != 0)
|
|
{
|
|
support_record_failure ();
|
|
printf ("error: output mismatch for %s (init method %s)\n",
|
|
t->name, test_init_names[init_method]);
|
|
support_run_diff ("expected", t->expected,
|
|
"actual", proc.out.buffer);
|
|
}
|
|
support_capture_subprocess_check (&proc, t->name, 0,
|
|
sc_allow_stdout);
|
|
support_capture_subprocess_free (&proc);
|
|
}
|
|
}
|
|
|
|
/* Special tests which do not follow the general pattern. */
|
|
enum { special_tests_count = 11 };
|
|
|
|
/* Implementation of special tests. */
|
|
static void
|
|
special_test_callback (void *closure)
|
|
{
|
|
unsigned int *test_indexp = closure;
|
|
unsigned test_index = *test_indexp;
|
|
TEST_VERIFY (test_index < special_tests_count);
|
|
if (test_verbose > 0)
|
|
printf ("info: special test %u\n", test_index);
|
|
xchroot (chroot_env->path_chroot);
|
|
|
|
switch (test_index)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
/* Second res_init with missing or empty file preserves
|
|
flags. */
|
|
if (test_index == 1)
|
|
TEST_VERIFY (unlink (_PATH_RESCONF) == 0);
|
|
_res.options = RES_USE_EDNS0;
|
|
TEST_VERIFY (res_init () == 0);
|
|
/* First res_init clears flag. */
|
|
TEST_VERIFY (!(_res.options & RES_USE_EDNS0));
|
|
_res.options |= RES_USE_EDNS0;
|
|
TEST_VERIFY (res_init () == 0);
|
|
/* Second res_init preserves flag. */
|
|
TEST_VERIFY (_res.options & RES_USE_EDNS0);
|
|
if (test_index == 1)
|
|
/* Restore empty file. */
|
|
support_write_file_string (_PATH_RESCONF, "");
|
|
break;
|
|
|
|
case 2:
|
|
/* Second res_init is cumulative. */
|
|
support_write_file_string (_PATH_RESCONF,
|
|
"options rotate\n"
|
|
"nameserver 192.0.2.1\n");
|
|
_res.options = RES_USE_EDNS0;
|
|
TEST_VERIFY (res_init () == 0);
|
|
/* First res_init clears flag. */
|
|
TEST_VERIFY (!(_res.options & RES_USE_EDNS0));
|
|
/* And sets RES_ROTATE. */
|
|
TEST_VERIFY (_res.options & RES_ROTATE);
|
|
_res.options |= RES_USE_EDNS0;
|
|
TEST_VERIFY (res_init () == 0);
|
|
/* Second res_init preserves flag. */
|
|
TEST_VERIFY (_res.options & RES_USE_EDNS0);
|
|
TEST_VERIFY (_res.options & RES_ROTATE);
|
|
/* Reloading the configuration does not clear the explicitly set
|
|
flag. */
|
|
support_write_file_string (_PATH_RESCONF,
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 192.0.2.2\n");
|
|
TEST_VERIFY (res_init () == 0);
|
|
TEST_VERIFY (_res.nscount == 2);
|
|
TEST_VERIFY (_res.options & RES_USE_EDNS0);
|
|
/* Whether RES_ROTATE (originally in resolv.conf, now removed)
|
|
should be preserved is subject to debate. See bug 21701. */
|
|
/* TEST_VERIFY (!(_res.options & RES_ROTATE)); */
|
|
break;
|
|
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
support_write_file_string (_PATH_RESCONF,
|
|
"options edns0\n"
|
|
"nameserver 192.0.2.1\n");
|
|
goto reload_tests;
|
|
case 7: /* 7 and the following tests are with no-reload. */
|
|
case 8:
|
|
case 9:
|
|
case 10:
|
|
support_write_file_string (_PATH_RESCONF,
|
|
"options edns0 no-reload\n"
|
|
"nameserver 192.0.2.1\n");
|
|
/* Fall through. */
|
|
reload_tests:
|
|
for (int iteration = 0; iteration < 2; ++iteration)
|
|
{
|
|
switch (test_index)
|
|
{
|
|
case 3:
|
|
case 7:
|
|
TEST_VERIFY (res_init () == 0);
|
|
break;
|
|
case 4:
|
|
case 8:
|
|
{
|
|
unsigned char buf[512];
|
|
TEST_VERIFY
|
|
(res_mkquery (QUERY, test_hostname, C_IN, T_A,
|
|
NULL, 0, NULL, buf, sizeof (buf)) > 0);
|
|
}
|
|
break;
|
|
case 5:
|
|
case 9:
|
|
gethostbyname (test_hostname);
|
|
break;
|
|
case 6:
|
|
case 10:
|
|
{
|
|
struct addrinfo *ai;
|
|
(void) getaddrinfo (test_hostname, NULL, NULL, &ai);
|
|
}
|
|
break;
|
|
}
|
|
/* test_index == 7 is res_init and performs a reload even
|
|
with no-reload. */
|
|
if (iteration == 0 || test_index > 7)
|
|
{
|
|
TEST_VERIFY (_res.options & RES_USE_EDNS0);
|
|
TEST_VERIFY (!(_res.options & RES_ROTATE));
|
|
if (test_index < 7)
|
|
TEST_VERIFY (!(_res.options & RES_NORELOAD));
|
|
else
|
|
TEST_VERIFY (_res.options & RES_NORELOAD);
|
|
TEST_VERIFY (_res.nscount == 1);
|
|
/* File change triggers automatic reloading. */
|
|
support_write_file_string (_PATH_RESCONF,
|
|
"options rotate\n"
|
|
"nameserver 192.0.2.1\n"
|
|
"nameserver 192.0.2.2\n");
|
|
}
|
|
else
|
|
{
|
|
if (test_index != 3 && test_index != 7)
|
|
/* test_index 3, 7 are res_init; this function does
|
|
not reset flags. See bug 21701. */
|
|
TEST_VERIFY (!(_res.options & RES_USE_EDNS0));
|
|
TEST_VERIFY (_res.options & RES_ROTATE);
|
|
TEST_VERIFY (_res.nscount == 2);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if TEST_THREAD
|
|
/* Helper function which calls special_test_callback from a
|
|
thread. */
|
|
static void *
|
|
special_test_thread_func (void *closure)
|
|
{
|
|
special_test_callback (closure);
|
|
return NULL;
|
|
}
|
|
|
|
/* Variant of special_test_callback which runs the function on a
|
|
non-main thread. */
|
|
static void
|
|
run_special_test_on_thread (void *closure)
|
|
{
|
|
xpthread_join (xpthread_create (NULL, special_test_thread_func, closure));
|
|
}
|
|
#endif /* TEST_THREAD */
|
|
|
|
/* Perform the requested special test in a subprocess using
|
|
special_test_callback. */
|
|
static void
|
|
special_test (unsigned int test_index)
|
|
{
|
|
#if TEST_THREAD
|
|
for (int do_thread = 0; do_thread < 2; ++do_thread)
|
|
#endif
|
|
{
|
|
void (*func) (void *) = special_test_callback;
|
|
#if TEST_THREAD
|
|
if (do_thread)
|
|
func = run_special_test_on_thread;
|
|
#endif
|
|
struct support_capture_subprocess proc
|
|
= support_capture_subprocess (func, &test_index);
|
|
char *test_name = xasprintf ("special test %u", test_index);
|
|
if (strcmp (proc.out.buffer, "") != 0)
|
|
{
|
|
support_record_failure ();
|
|
printf ("error: output mismatch for %s\n", test_name);
|
|
support_run_diff ("expected", "",
|
|
"actual", proc.out.buffer);
|
|
}
|
|
support_capture_subprocess_check (&proc, test_name, 0, sc_allow_stdout);
|
|
free (test_name);
|
|
support_capture_subprocess_free (&proc);
|
|
}
|
|
}
|
|
|
|
|
|
/* Dummy DNS server. It ensures that the probe queries sent by
|
|
gethostbyname and getaddrinfo receive a reply even if the system
|
|
applies a very strict rate limit to localhost. */
|
|
static pid_t
|
|
start_dummy_server (void)
|
|
{
|
|
int server_socket = xsocket (AF_INET, SOCK_DGRAM, 0);
|
|
{
|
|
struct sockaddr_in sin =
|
|
{
|
|
.sin_family = AF_INET,
|
|
.sin_addr = { .s_addr = htonl (INADDR_LOOPBACK) },
|
|
.sin_port = htons (53),
|
|
};
|
|
int ret = bind (server_socket, (struct sockaddr *) &sin, sizeof (sin));
|
|
if (ret < 0)
|
|
{
|
|
if (errno == EACCES)
|
|
/* The port is reserved, which means we cannot start the
|
|
server. */
|
|
return -1;
|
|
FAIL_EXIT1 ("cannot bind socket to port 53: %m");
|
|
}
|
|
}
|
|
|
|
pid_t pid = xfork ();
|
|
if (pid == 0)
|
|
{
|
|
/* Child process. Echo back queries as SERVFAIL responses. */
|
|
while (true)
|
|
{
|
|
union
|
|
{
|
|
HEADER header;
|
|
unsigned char bytes[512];
|
|
} packet;
|
|
struct sockaddr_in sin;
|
|
socklen_t sinlen = sizeof (sin);
|
|
|
|
ssize_t ret = recvfrom
|
|
(server_socket, &packet, sizeof (packet),
|
|
MSG_NOSIGNAL, (struct sockaddr *) &sin, &sinlen);
|
|
if (ret < 0)
|
|
FAIL_EXIT1 ("recvfrom on fake server socket: %m");
|
|
if (ret > sizeof (HEADER))
|
|
{
|
|
/* Turn the query into a SERVFAIL response. */
|
|
packet.header.qr = 1;
|
|
packet.header.rcode = ns_r_servfail;
|
|
|
|
/* Send the response. */
|
|
ret = sendto (server_socket, &packet, ret,
|
|
MSG_NOSIGNAL, (struct sockaddr *) &sin, sinlen);
|
|
if (ret < 0)
|
|
/* The peer may have closed socket prematurely, so
|
|
this is not an error. */
|
|
printf ("warning: sending DNS server reply: %m\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* In the parent, close the socket. */
|
|
xclose (server_socket);
|
|
|
|
return pid;
|
|
}
|
|
|
|
static int
|
|
do_test (void)
|
|
{
|
|
support_become_root ();
|
|
support_enter_network_namespace ();
|
|
if (!support_in_uts_namespace () || !support_can_chroot ())
|
|
return EXIT_UNSUPPORTED;
|
|
|
|
/* We are in an UTS namespace, so we can set the host name without
|
|
altering the state of the entire system. */
|
|
if (sethostname (test_hostname, strlen (test_hostname)) != 0)
|
|
FAIL_EXIT1 ("sethostname: %m");
|
|
|
|
/* These environment variables affect resolv.conf parsing. */
|
|
unsetenv ("LOCALDOMAIN");
|
|
unsetenv ("RES_OPTIONS");
|
|
|
|
/* Ensure that the chroot setup worked. */
|
|
{
|
|
struct support_capture_subprocess proc
|
|
= support_capture_subprocess (check_chroot_working, NULL);
|
|
support_capture_subprocess_check (&proc, "chroot", 0, sc_allow_none);
|
|
support_capture_subprocess_free (&proc);
|
|
}
|
|
|
|
pid_t server = start_dummy_server ();
|
|
|
|
for (size_t i = 0; test_cases[i].name != NULL; ++i)
|
|
{
|
|
if (test_verbose > 0)
|
|
printf ("info: running test: %s\n", test_cases[i].name);
|
|
TEST_VERIFY (test_cases[i].conf != NULL);
|
|
TEST_VERIFY (test_cases[i].expected != NULL);
|
|
|
|
support_write_file_string (chroot_env->path_resolv_conf,
|
|
test_cases[i].conf);
|
|
|
|
test_file_contents (&test_cases[i]);
|
|
|
|
/* The expected output from the empty file test is used for
|
|
further tests. */
|
|
if (test_cases[i].conf[0] == '\0')
|
|
{
|
|
if (test_verbose > 0)
|
|
printf ("info: special test: missing file\n");
|
|
TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0);
|
|
test_file_contents (&test_cases[i]);
|
|
|
|
if (test_verbose > 0)
|
|
printf ("info: special test: dangling symbolic link\n");
|
|
TEST_VERIFY (symlink ("does-not-exist", chroot_env->path_resolv_conf) == 0);
|
|
test_file_contents (&test_cases[i]);
|
|
TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0);
|
|
|
|
if (test_verbose > 0)
|
|
printf ("info: special test: unreadable file\n");
|
|
support_write_file_string (chroot_env->path_resolv_conf, "");
|
|
TEST_VERIFY (chmod (chroot_env->path_resolv_conf, 0) == 0);
|
|
test_file_contents (&test_cases[i]);
|
|
|
|
/* Restore the empty file. */
|
|
TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0);
|
|
support_write_file_string (chroot_env->path_resolv_conf, "");
|
|
}
|
|
}
|
|
|
|
/* The tests which do not follow a regular pattern. */
|
|
for (unsigned int test_index = 0;
|
|
test_index < special_tests_count; ++test_index)
|
|
special_test (test_index);
|
|
|
|
if (server > 0)
|
|
{
|
|
if (kill (server, SIGTERM) < 0)
|
|
FAIL_EXIT1 ("could not terminate server process: %m");
|
|
xwaitpid (server, NULL, 0);
|
|
}
|
|
|
|
support_chroot_free (chroot_env);
|
|
return 0;
|
|
}
|
|
|
|
#define PREPARE prepare
|
|
#include <support/test-driver.c>
|