diff options
author | Florian Weimer <fweimer@redhat.com> | 2017-07-05 17:39:33 +0200 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2017-07-05 19:04:40 +0200 |
commit | cb3c27e87b914bde5ec00a02363536c76e08b850 (patch) | |
tree | 8a20c3b98ba3e0fb7ecac4d79d881f66971c905b | |
parent | d4165eedf5b85bfda3ea6b251f69838857e44925 (diff) | |
download | glibc-cb3c27e87b914bde5ec00a02363536c76e08b850.tar.gz glibc-cb3c27e87b914bde5ec00a02363536c76e08b850.tar.xz glibc-cb3c27e87b914bde5ec00a02363536c76e08b850.zip |
support: Add resolver testing mode which does not patch _res
-rw-r--r-- | ChangeLog | 15 | ||||
-rw-r--r-- | resolv/Makefile | 6 | ||||
-rw-r--r-- | resolv/tst-resolv-threads.c | 484 | ||||
-rw-r--r-- | support/resolv_test.c | 37 | ||||
-rw-r--r-- | support/resolv_test.h | 10 |
5 files changed, 549 insertions, 3 deletions
diff --git a/ChangeLog b/ChangeLog index 5cbeb7b89c..a3899c0722 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2017-07-05 Florian Weimer <fweimer@redhat.com> + * resolv/Makefile (tests-internal): Add tst-resolv-threads. + (tst-resolv-threads): Link with -ldl, -lresolv, -lpthread. + * resolv/tst-resolv-threads.c: New file. + +2017-07-05 Florian Weimer <fweimer@redhat.com> + + support: Add resolver testing mode which does not patch _res. + * support/resolv_test.h (struct resolv_redirect_config): Add + disable_redirect, server_address_overrides. + * support/resolv_test.c (make_server_sockets_for_address): New + function. + (resolv_test_start): Call it. + +2017-07-05 Florian Weimer <fweimer@redhat.com> + * support/namespace.h (struct support_chroot_configuration) (struct support_chroot): Define. (support_chroot_create, support_chroot_free): New functions. diff --git a/resolv/Makefile b/resolv/Makefile index 6942e8598f..ec7e4fd146 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -65,7 +65,9 @@ tests-internal += \ tst-resolv-res_init-thread \ # Needs resolv_context. -tests-internal += tst-resolv-res_ninit +tests-internal += \ + tst-resolv-res_ninit \ + tst-resolv-threads \ endif @@ -168,6 +170,8 @@ $(objpfx)tst-resolv-res_init-thread: $(libdl) $(objpfx)libresolv.so \ $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library) +$(objpfx)tst-resolv-threads: \ + $(libdl) $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-canonname: \ $(libdl) $(objpfx)libresolv.so $(shared-thread-library) diff --git a/resolv/tst-resolv-threads.c b/resolv/tst-resolv-threads.c new file mode 100644 index 0000000000..7be417b056 --- /dev/null +++ b/resolv/tst-resolv-threads.c @@ -0,0 +1,484 @@ +/* Test basic nss_dns functionality with multiple threads. + Copyright (C) 2016-2017 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 + <http://www.gnu.org/licenses/>. */ + +/* Unlike tst-resolv-basic, this test does not overwrite the _res + structure and relies on namespaces to achieve the redirection to + the test servers with a custom /etc/resolv.conf file. */ + +#include <dlfcn.h> +#include <errno.h> +#include <gnu/lib-names.h> +#include <netdb.h> +#include <resolv/resolv-internal.h> +#include <resolv/resolv_context.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <support/check.h> +#include <support/namespace.h> +#include <support/resolv_test.h> +#include <support/support.h> +#include <support/temp_file.h> +#include <support/test-driver.h> +#include <support/xthread.h> +#include <support/xunistd.h> + +/* Each client thread sends this many queries. */ +enum { queries_per_thread = 500 }; + +/* Return a small positive number identifying this thread. */ +static int +get_thread_number (void) +{ + static int __thread local; + if (local != 0) + return local; + static int global = 1; + local = __atomic_fetch_add (&global, 1, __ATOMIC_RELAXED); + return local; +} + +static void +response (const struct resolv_response_context *ctx, + struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype) +{ + TEST_VERIFY_EXIT (qname != NULL); + + int counter = 0; + int thread = 0; + int dummy = 0; + TEST_VERIFY (sscanf (qname, "counter%d.thread%d.example.com%n", + &counter, &thread, &dummy) == 2); + TEST_VERIFY (dummy > 0); + + struct resolv_response_flags flags = { 0 }; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + + resolv_response_section (b, ns_s_an); + resolv_response_open_record (b, qname, qclass, qtype, 0); + switch (qtype) + { + case T_A: + { + char ipv4[4] = {10, 0, counter, thread}; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + } + break; + case T_AAAA: + { + char ipv6[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, + counter, 0, thread, 0, 0}; + resolv_response_add_data (b, &ipv6, sizeof (ipv6)); + } + break; + default: + support_record_failure (); + printf ("error: unexpected QTYPE: %s/%u/%u\n", + qname, qclass, qtype); + } + resolv_response_close_record (b); +} + +/* Check that the resolver configuration for this thread has an + extended resolver configuration. */ +static void +check_have_conf (void) +{ + struct resolv_context *ctx = __resolv_context_get (); + TEST_VERIFY_EXIT (ctx != NULL); + TEST_VERIFY (ctx->conf != NULL); + __resolv_context_put (ctx); +} + +/* Verify that E matches the expected response for FAMILY and + COUNTER. */ +static void +check_hostent (const char *caller, const char *function, const char *qname, + int ret, struct hostent *e, int family, int counter) +{ + if (ret != 0) + { + errno = ret; + support_record_failure (); + printf ("error: %s: %s for %s failed: %m\n", caller, function, qname); + return; + } + + TEST_VERIFY_EXIT (e != NULL); + TEST_VERIFY (strcmp (qname, e->h_name) == 0); + TEST_VERIFY (e->h_addrtype == family); + TEST_VERIFY_EXIT (e->h_addr_list[0] != NULL); + TEST_VERIFY (e->h_addr_list[1] == NULL); + switch (family) + { + case AF_INET: + { + char addr[4] = {10, 0, counter, get_thread_number ()}; + TEST_VERIFY (e->h_length == sizeof (addr)); + TEST_VERIFY (memcmp (e->h_addr_list[0], addr, sizeof (addr)) == 0); + } + break; + case AF_INET6: + { + char addr[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, + 0, counter, 0, get_thread_number (), 0, 0}; + TEST_VERIFY (e->h_length == sizeof (addr)); + TEST_VERIFY (memcmp (e->h_addr_list[0], addr, sizeof (addr)) == 0); + } + break; + default: + FAIL_EXIT1 ("%s: invalid address family %d", caller, family); + } + check_have_conf (); +} + +/* Check a getaddrinfo result. */ +static void +check_addrinfo (const char *caller, const char *qname, + int ret, struct addrinfo *ai, int family, int counter) +{ + if (ret != 0) + { + support_record_failure (); + printf ("error: %s: getaddrinfo for %s failed: %s\n", + caller, qname, gai_strerror (ret)); + return; + } + + TEST_VERIFY_EXIT (ai != NULL); + + /* Check that available data matches the requirements. */ + bool have_ipv4 = false; + bool have_ipv6 = false; + for (struct addrinfo *p = ai; p != NULL; p = p->ai_next) + { + TEST_VERIFY (p->ai_socktype == SOCK_STREAM); + TEST_VERIFY (p->ai_protocol == IPPROTO_TCP); + TEST_VERIFY_EXIT (p->ai_addr != NULL); + TEST_VERIFY (p->ai_addr->sa_family == p->ai_family); + + switch (p->ai_family) + { + case AF_INET: + { + TEST_VERIFY (!have_ipv4); + have_ipv4 = true; + struct sockaddr_in *sa = (struct sockaddr_in *) p->ai_addr; + TEST_VERIFY (p->ai_addrlen == sizeof (*sa)); + char addr[4] = {10, 0, counter, get_thread_number ()}; + TEST_VERIFY (memcmp (&sa->sin_addr, addr, sizeof (addr)) == 0); + TEST_VERIFY (ntohs (sa->sin_port) == 80); + } + break; + case AF_INET6: + { + TEST_VERIFY (!have_ipv6); + have_ipv6 = true; + struct sockaddr_in6 *sa = (struct sockaddr_in6 *) p->ai_addr; + TEST_VERIFY (p->ai_addrlen == sizeof (*sa)); + char addr[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, + 0, counter, 0, get_thread_number (), 0, 0}; + TEST_VERIFY (memcmp (&sa->sin6_addr, addr, sizeof (addr)) == 0); + TEST_VERIFY (ntohs (sa->sin6_port) == 80); + } + break; + default: + FAIL_EXIT1 ("%s: invalid address family %d", caller, family); + } + } + + switch (family) + { + case AF_INET: + TEST_VERIFY (have_ipv4); + TEST_VERIFY (!have_ipv6); + break; + case AF_INET6: + TEST_VERIFY (!have_ipv4); + TEST_VERIFY (have_ipv6); + break; + case AF_UNSPEC: + TEST_VERIFY (have_ipv4); + TEST_VERIFY (have_ipv6); + break; + default: + FAIL_EXIT1 ("%s: invalid address family %d", caller, family); + } + + check_have_conf (); +} + +/* This barrier ensures that all test threads begin their work + simultaneously. */ +static pthread_barrier_t barrier; + +/* Test gethostbyname2_r (if do_2 is false) or gethostbyname2_r with + AF_INET (if do_2 is true). */ +static void * +byname (bool do_2) +{ + int this_thread = get_thread_number (); + xpthread_barrier_wait (&barrier); + for (int i = 0; i < queries_per_thread; ++i) + { + char qname[100]; + snprintf (qname, sizeof (qname), "counter%d.thread%d.example.com", + i, this_thread); + struct hostent storage; + char buf[1000]; + struct hostent *e = NULL; + int herrno; + int ret; + if (do_2) + ret = gethostbyname_r (qname, &storage, buf, sizeof (buf), + &e, &herrno); + else + ret = gethostbyname2_r (qname, AF_INET, &storage, buf, sizeof (buf), + &e, &herrno); + check_hostent (__func__, do_2 ? "gethostbyname2_r" : "gethostbyname_r", + qname, ret, e, AF_INET, i); + } + check_have_conf (); + return NULL; +} + +/* Test gethostbyname_r. */ +static void * +thread_byname (void *closure) +{ + return byname (false); +} + +/* Test gethostbyname2_r with AF_INET. */ +static void * +thread_byname2 (void *closure) +{ + return byname (true); +} + +/* Call gethostbyname_r with RES_USE_INET6 (if do_2 is false), or + gethostbyname_r with AF_INET6 (if do_2 is true). */ +static void * +byname_inet6 (bool do_2) +{ + int this_thread = get_thread_number (); + xpthread_barrier_wait (&barrier); + if (!do_2) + { + res_init (); + _res.options |= DEPRECATED_RES_USE_INET6; + TEST_VERIFY (strcmp (_res.defdname, "example.com") == 0); + } + for (int i = 0; i < queries_per_thread; ++i) + { + char qname[100]; + snprintf (qname, sizeof (qname), "counter%d.thread%d.example.com", + i, this_thread); + struct hostent storage; + char buf[1000]; + struct hostent *e = NULL; + int herrno; + int ret; + if (do_2) + ret = gethostbyname2_r (qname, AF_INET6, &storage, buf, sizeof (buf), + &e, &herrno); + else + ret = gethostbyname_r (qname, &storage, buf, sizeof (buf), + &e, &herrno); + check_hostent (__func__, + do_2 ? "gethostbyname2_r" : "gethostbyname_r", + qname, ret, e, AF_INET6, i); + } + return NULL; +} + +/* Test gethostbyname_r with AF_INET6. */ +static void * +thread_byname_inet6 (void *closure) +{ + return byname_inet6 (false); +} + +/* Test gethostbyname2_r with AF_INET6. */ +static void * +thread_byname2_af_inet6 (void *closure) +{ + return byname_inet6 (true); +} + +/* Run getaddrinfo tests for FAMILY. */ +static void * +gai (int family, bool do_inet6) +{ + int this_thread = get_thread_number (); + xpthread_barrier_wait (&barrier); + if (do_inet6) + { + res_init (); + _res.options |= DEPRECATED_RES_USE_INET6; + check_have_conf (); + } + for (int i = 0; i < queries_per_thread; ++i) + { + char qname[100]; + snprintf (qname, sizeof (qname), "counter%d.thread%d.example.com", + i, this_thread); + struct addrinfo hints = + { + .ai_family = family, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai; + int ret = getaddrinfo (qname, "80", &hints, &ai); + check_addrinfo (__func__, qname, ret, ai, family, i); + if (ret == 0) + freeaddrinfo (ai); + } + return NULL; +} + +/* Test getaddrinfo with AF_INET. */ +static void * +thread_gai_inet (void *closure) +{ + return gai (AF_INET, false); +} + +/* Test getaddrinfo with AF_INET6. */ +static void * +thread_gai_inet6 (void *closure) +{ + return gai (AF_INET6, false); +} + +/* Test getaddrinfo with AF_UNSPEC. */ +static void * +thread_gai_unspec (void *closure) +{ + return gai (AF_UNSPEC, false); +} + +/* Test getaddrinfo with AF_INET. */ +static void * +thread_gai_inet_inet6 (void *closure) +{ + return gai (AF_INET, true); +} + +/* Test getaddrinfo with AF_INET6. */ +static void * +thread_gai_inet6_inet6 (void *closure) +{ + return gai (AF_INET6, true); +} + +/* Test getaddrinfo with AF_UNSPEC. */ +static void * +thread_gai_unspec_inet6 (void *closure) +{ + return gai (AF_UNSPEC, true); +} + +/* Description of the chroot environment used to run the tests. */ +static struct support_chroot *chroot_env; + +/* Set up the chroot environment. */ +static void +prepare (int argc, char **argv) +{ + chroot_env = support_chroot_create + ((struct support_chroot_configuration) + { + .resolv_conf = + "search example.com\n" + "nameserver 127.0.0.1\n" + "nameserver 127.0.0.2\n" + "nameserver 127.0.0.3\n", + }); +} + +static int +do_test (void) +{ + support_become_root (); + if (!support_enter_network_namespace ()) + return EXIT_UNSUPPORTED; + if (!support_can_chroot ()) + return EXIT_UNSUPPORTED; + + /* Load the shared object outside of the chroot. */ + TEST_VERIFY (dlopen (LIBNSS_DNS_SO, RTLD_LAZY) != NULL); + + xchroot (chroot_env->path_chroot); + TEST_VERIFY_EXIT (chdir ("/") == 0); + + struct sockaddr_in server_address = + { + .sin_family = AF_INET, + .sin_addr = { .s_addr = htonl (INADDR_LOOPBACK) }, + .sin_port = htons (53) + }; + const struct sockaddr *server_addresses[1] = + { (const struct sockaddr *) &server_address }; + + struct resolv_test *aux = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response, + .nscount = 1, + .disable_redirect = true, + .server_address_overrides = server_addresses, + }); + + enum { thread_count = 10 }; + xpthread_barrier_init (&barrier, NULL, thread_count + 1); + pthread_t threads[thread_count]; + typedef void *(*thread_func) (void *); + thread_func thread_funcs[thread_count] = + { + thread_byname, + thread_byname2, + thread_byname_inet6, + thread_byname2_af_inet6, + thread_gai_inet, + thread_gai_inet6, + thread_gai_unspec, + thread_gai_inet_inet6, + thread_gai_inet6_inet6, + thread_gai_unspec_inet6, + }; + for (int i = 0; i < thread_count; ++i) + threads[i] = xpthread_create (NULL, thread_funcs[i], NULL); + xpthread_barrier_wait (&barrier); /* Start the test threads. */ + for (int i = 0; i < thread_count; ++i) + xpthread_join (threads[i]); + + resolv_test_end (aux); + support_chroot_free (chroot_env); + + return 0; +} + +#define PREPARE prepare +#include <support/test-driver.c> diff --git a/support/resolv_test.c b/support/resolv_test.c index 050cd7154b..1625dcf43a 100644 --- a/support/resolv_test.c +++ b/support/resolv_test.c @@ -1004,6 +1004,29 @@ make_server_sockets (struct resolv_test_server *server) } } +/* Like make_server_sockets, but the caller supplies the address to + use. */ +static void +make_server_sockets_for_address (struct resolv_test_server *server, + const struct sockaddr *addr) +{ + server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); + server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (addr->sa_family == AF_INET) + server->address = *(const struct sockaddr_in *) addr; + else + /* We cannot store the server address in the socket. This should + not matter if disable_redirect is used. */ + server->address = (struct sockaddr_in) { .sin_family = 0, }; + + xbind (server->socket_udp, + (struct sockaddr *)&server->address, sizeof (server->address)); + xbind (server->socket_tcp, + (struct sockaddr *)&server->address, sizeof (server->address)); + xlisten (server->socket_tcp, 5); +} + /* One-time initialization of NSS. */ static void resolv_redirect_once (void) @@ -1064,11 +1087,17 @@ resolv_test_start (struct resolv_redirect_config config) .lock = PTHREAD_MUTEX_INITIALIZER, }; - resolv_test_init (); + if (!config.disable_redirect) + resolv_test_init (); /* Create all the servers, to reserve the necessary ports. */ for (int server_index = 0; server_index < config.nscount; ++server_index) - make_server_sockets (obj->servers + server_index); + if (config.disable_redirect && config.server_address_overrides != NULL) + make_server_sockets_for_address + (obj->servers + server_index, + config.server_address_overrides[server_index]); + else + make_server_sockets (obj->servers + server_index); /* Start server threads. Disable the server ports, as requested. */ @@ -1095,6 +1124,9 @@ resolv_test_start (struct resolv_redirect_config config) if (config.single_thread_udp) start_server_thread_udp_single (obj); + if (config.disable_redirect) + return obj; + int timeout = 1; /* Initialize libresolv. */ @@ -1129,6 +1161,7 @@ resolv_test_start (struct resolv_redirect_config config) } for (int server_index = 0; server_index < config.nscount; ++server_index) { + TEST_VERIFY_EXIT (obj->servers[server_index].address.sin_port != 0); _res.nsaddr_list[server_index] = obj->servers[server_index].address; if (test_verbose) { diff --git a/support/resolv_test.h b/support/resolv_test.h index 6498751569..b953dc1200 100644 --- a/support/resolv_test.h +++ b/support/resolv_test.h @@ -93,6 +93,16 @@ struct resolv_redirect_config may results in more predictable ordering of queries and responses. */ bool single_thread_udp; + + /* Do not rewrite the _res variable or change NSS defaults. Use + server_address_overrides below to tell the testing framework on + which addresses to create the servers. */ + bool disable_redirect; + + /* Use these addresses for creating the DNS servers. The array must + have ns_count (or resolv_max_test_servers) sockaddr * elements if + not NULL. */ + const struct sockaddr *const *server_address_overrides; }; /* Configure NSS to use, nss_dns only for aplicable databases, and try |