/* Test basic nss_dns functionality with multiple threads. Copyright (C) 2016-2019 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 . */ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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