mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-10-30 10:45:40 +03:00 
			
		
		
		
	* posix/tst-mmap-offset.c: Include stdint.h. * resolv/tst-ns_name_compress.c: Include string.h. * resolv/tst-resolv-edns.c: Include resolv.h.
		
			
				
	
	
		
			533 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			533 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Test EDNS handling in the stub resolver.
 | |
|    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/>.  */
 | |
| 
 | |
| #include <errno.h>
 | |
| #include <netdb.h>
 | |
| #include <resolv.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <support/check.h>
 | |
| #include <support/resolv_test.h>
 | |
| #include <support/support.h>
 | |
| #include <support/test-driver.h>
 | |
| #include <support/xthread.h>
 | |
| 
 | |
| /* Data produced by a test query.  */
 | |
| struct response_data
 | |
| {
 | |
|   char *qname;
 | |
|   uint16_t qtype;
 | |
|   struct resolv_edns_info edns;
 | |
| };
 | |
| 
 | |
| /* Global array used by put_response and get_response to record
 | |
|    response data.  The test DNS server returns the index of the array
 | |
|    element which contains the actual response data.  This enables the
 | |
|    test case to return arbitrary amounts of data with the limited
 | |
|    number of bits which fit into an IP addres.
 | |
| 
 | |
|    The volatile specifier is needed because the test case accesses
 | |
|    these variables from a callback function called from a function
 | |
|    which is marked as __THROW (i.e., a leaf function which actually is
 | |
|    not).  */
 | |
| static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 | |
| static struct response_data ** volatile response_data_array;
 | |
| volatile static size_t response_data_count;
 | |
| 
 | |
| /* Extract information from the query, store it in a struct
 | |
|    response_data object, and return its index in the
 | |
|    response_data_array.  */
 | |
| static unsigned int
 | |
| put_response (const struct resolv_response_context *ctx,
 | |
|                  const char *qname, uint16_t qtype)
 | |
| {
 | |
|   xpthread_mutex_lock (&mutex);
 | |
|   ++response_data_count;
 | |
|   /* We only can represent 2**24 indexes in 10.0.0.0/8.  */
 | |
|   TEST_VERIFY (response_data_count < (1 << 24));
 | |
|   response_data_array = xrealloc
 | |
|     (response_data_array, sizeof (*response_data_array) * response_data_count);
 | |
|   unsigned int index = response_data_count - 1;
 | |
|   struct response_data *data = xmalloc (sizeof (*data));
 | |
|   *data = (struct response_data)
 | |
|     {
 | |
|       .qname = xstrdup (qname),
 | |
|       .qtype = qtype,
 | |
|       .edns = ctx->edns,
 | |
|     };
 | |
|   response_data_array[index] = data;
 | |
|   xpthread_mutex_unlock (&mutex);
 | |
|   return index;
 | |
| }
 | |
| 
 | |
| /* Verify the index into the response_data array and return the data
 | |
|    at it.  */
 | |
| static struct response_data *
 | |
| get_response (unsigned int index)
 | |
| {
 | |
|   xpthread_mutex_lock (&mutex);
 | |
|   TEST_VERIFY_EXIT (index < response_data_count);
 | |
|   struct response_data *result = response_data_array[index];
 | |
|   xpthread_mutex_unlock (&mutex);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* Deallocate all response data.  */
 | |
| static void
 | |
| free_response_data (void)
 | |
| {
 | |
|   xpthread_mutex_lock (&mutex);
 | |
|   size_t count = response_data_count;
 | |
|   struct response_data **array = response_data_array;
 | |
|   for (unsigned int i = 0; i < count; ++i)
 | |
|     {
 | |
|       struct response_data *data = array[i];
 | |
|       free (data->qname);
 | |
|       free (data);
 | |
|     }
 | |
|   free (array);
 | |
|   response_data_array = NULL;
 | |
|   response_data_count = 0;
 | |
|   xpthread_mutex_unlock (&mutex);
 | |
| }
 | |
| 
 | |
| #define EDNS_PROBE_EXAMPLE "edns-probe.example"
 | |
| 
 | |
| 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);
 | |
| 
 | |
|   const char *qname_compare = qname;
 | |
| 
 | |
|   /* The "formerr." prefix can be used to request a FORMERR response on the
 | |
|      first server.  */
 | |
|   bool send_formerr;
 | |
|   if (strncmp ("formerr.", qname, strlen ("formerr.")) == 0)
 | |
|     {
 | |
|       send_formerr = true;
 | |
|       qname_compare = qname + strlen ("formerr.");
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       send_formerr = false;
 | |
|       qname_compare = qname;
 | |
|     }
 | |
| 
 | |
|   /* The "tcp." prefix can be used to request TCP fallback.  */
 | |
|   bool force_tcp;
 | |
|   if (strncmp ("tcp.", qname_compare, strlen ("tcp.")) == 0)
 | |
|     {
 | |
|       force_tcp = true;
 | |
|       qname_compare += strlen ("tcp.");
 | |
|     }
 | |
|   else
 | |
|     force_tcp = false;
 | |
| 
 | |
|   enum {edns_probe} requested_qname;
 | |
|   if (strcmp (qname_compare, EDNS_PROBE_EXAMPLE) == 0)
 | |
|     requested_qname = edns_probe;
 | |
|   else
 | |
|     {
 | |
|       support_record_failure ();
 | |
|       printf ("error: unexpected QNAME: %s (reduced: %s)\n",
 | |
|               qname, qname_compare);
 | |
|       return;
 | |
|     }
 | |
|   TEST_VERIFY_EXIT (qclass == C_IN);
 | |
|   struct resolv_response_flags flags = { };
 | |
|   flags.tc = force_tcp && !ctx->tcp;
 | |
|   if (!flags.tc && send_formerr && ctx->server_index == 0)
 | |
|     /* Send a FORMERR for the first full response from the first
 | |
|        server.  */
 | |
|     flags.rcode = 1;          /* FORMERR */
 | |
|   resolv_response_init (b, flags);
 | |
|   resolv_response_add_question (b, qname, qclass, qtype);
 | |
|   if (flags.tc || flags.rcode != 0)
 | |
|     return;
 | |
| 
 | |
|   if (test_verbose)
 | |
|     printf ("info: edns=%d payload_size=%d\n",
 | |
|             ctx->edns.active, ctx->edns.payload_size);
 | |
| 
 | |
|   /* Encode the response_data object in multiple address records.
 | |
|      Each record carries two bytes of payload data, and an index.  */
 | |
|   resolv_response_section (b, ns_s_an);
 | |
|   switch (requested_qname)
 | |
|     {
 | |
|     case edns_probe:
 | |
|       {
 | |
|         unsigned int index = put_response (ctx, qname, qtype);
 | |
|         switch (qtype)
 | |
|           {
 | |
|           case T_A:
 | |
|             {
 | |
|               uint32_t addr = htonl (0x0a000000 | index);
 | |
|               resolv_response_open_record (b, qname, qclass, qtype, 0);
 | |
|               resolv_response_add_data (b, &addr, sizeof (addr));
 | |
|               resolv_response_close_record (b);
 | |
|             }
 | |
|             break;
 | |
|           case T_AAAA:
 | |
|             {
 | |
|               char addr[16]
 | |
|                 = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | |
|                    index >> 16, index >> 8, index};
 | |
|               resolv_response_open_record (b, qname, qclass, qtype, 0);
 | |
|               resolv_response_add_data (b, &addr, sizeof (addr));
 | |
|               resolv_response_close_record (b);
 | |
|             }
 | |
|           }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Update *DATA with data from ADDRESS of SIZE.  Set the corresponding
 | |
|    flag in SHADOW for each byte written.  */
 | |
| static struct response_data *
 | |
| decode_address (const void *address, size_t size)
 | |
| {
 | |
|   switch (size)
 | |
|     {
 | |
|     case 4:
 | |
|       TEST_VERIFY (memcmp (address, "\x0a", 1) == 0);
 | |
|       break;
 | |
|     case 16:
 | |
|       TEST_VERIFY (memcmp (address, "\x20\x01\x0d\xb8", 4) == 0);
 | |
|       break;
 | |
|     default:
 | |
|       FAIL_EXIT1 ("unexpected address size %zu", size);
 | |
|     }
 | |
|   const unsigned char *addr = address;
 | |
|   unsigned int index = addr[size - 3] * 256 * 256
 | |
|     + addr[size - 2] * 256
 | |
|     + addr[size - 1];
 | |
|   return get_response (index);
 | |
| }
 | |
| 
 | |
| static struct response_data *
 | |
| decode_hostent (struct hostent *e)
 | |
| {
 | |
|   TEST_VERIFY_EXIT (e != NULL);
 | |
|   TEST_VERIFY_EXIT (e->h_addr_list[0] != NULL);
 | |
|   TEST_VERIFY (e->h_addr_list[1] == NULL);
 | |
|   return decode_address (e->h_addr_list[0], e->h_length);
 | |
| }
 | |
| 
 | |
| static struct response_data *
 | |
| decode_addrinfo (struct addrinfo *ai, int family)
 | |
| {
 | |
|   struct response_data *data = NULL;
 | |
|   while (ai != NULL)
 | |
|     {
 | |
|       if (ai->ai_family == family)
 | |
|         {
 | |
|           struct response_data *new_data;
 | |
|           switch (family)
 | |
|             {
 | |
|             case AF_INET:
 | |
|               {
 | |
|                 struct sockaddr_in *pin = (struct sockaddr_in *) ai->ai_addr;
 | |
|                 new_data = decode_address (&pin->sin_addr.s_addr, 4);
 | |
|               }
 | |
|               break;
 | |
|             case AF_INET6:
 | |
|               {
 | |
|                 struct sockaddr_in6 *pin = (struct sockaddr_in6 *) ai->ai_addr;
 | |
|                 new_data = decode_address (&pin->sin6_addr.s6_addr, 16);
 | |
|               }
 | |
|               break;
 | |
|             default:
 | |
|               FAIL_EXIT1 ("invalid address family %d", ai->ai_family);
 | |
|             }
 | |
|           if (data == NULL)
 | |
|             data = new_data;
 | |
|           else
 | |
|             /* Check pointer equality because this should be the same
 | |
|                response (same index).  */
 | |
|             TEST_VERIFY (data == new_data);
 | |
|         }
 | |
|       ai = ai->ai_next;
 | |
|     }
 | |
|   TEST_VERIFY_EXIT (data != NULL);
 | |
|   return data;
 | |
| }
 | |
| 
 | |
| /* Updated by the main test loop in accordance with what is set in
 | |
|    _res.options.  */
 | |
| static bool use_edns;
 | |
| static bool use_dnssec;
 | |
| 
 | |
| /* Verify the decoded response data against the flags above.  */
 | |
| static void
 | |
| verify_response_data_payload (struct response_data *data,
 | |
|                               size_t expected_payload)
 | |
| {
 | |
|   bool edns = use_edns || use_dnssec;
 | |
|   TEST_VERIFY (data->edns.active == edns);
 | |
|   if (!edns)
 | |
|     expected_payload = 0;
 | |
|   if (data->edns.payload_size != expected_payload)
 | |
|     {
 | |
|       support_record_failure ();
 | |
|       printf ("error: unexpected payload size %d (edns=%d)\n",
 | |
|               (int) data->edns.payload_size, edns);
 | |
|     }
 | |
|   uint16_t expected_flags = 0;
 | |
|   if (use_dnssec)
 | |
|     expected_flags |= 0x8000;   /* DO flag.  */
 | |
|   if (data->edns.flags != expected_flags)
 | |
|     {
 | |
|       support_record_failure ();
 | |
|       printf ("error: unexpected EDNS flags 0x%04x (edns=%d)\n",
 | |
|               (int) data->edns.flags, edns);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Same as verify_response_data_payload, but use the default
 | |
|    payload.  */
 | |
| static void
 | |
| verify_response_data (struct response_data *data)
 | |
| {
 | |
|   verify_response_data_payload (data, 1200);
 | |
| }
 | |
| 
 | |
| static void
 | |
| check_hostent (struct hostent *e)
 | |
| {
 | |
|   TEST_VERIFY_EXIT (e != NULL);
 | |
|   verify_response_data (decode_hostent (e));
 | |
| }
 | |
| 
 | |
| static void
 | |
| do_ai (int family)
 | |
| {
 | |
|   struct addrinfo hints = { .ai_family = family };
 | |
|   struct addrinfo *ai;
 | |
|   int ret = getaddrinfo (EDNS_PROBE_EXAMPLE, "80", &hints, &ai);
 | |
|   TEST_VERIFY_EXIT (ret == 0);
 | |
|   switch (family)
 | |
|     {
 | |
|     case AF_INET:
 | |
|     case AF_INET6:
 | |
|       verify_response_data (decode_addrinfo (ai, family));
 | |
|       break;
 | |
|     case AF_UNSPEC:
 | |
|       verify_response_data (decode_addrinfo (ai, AF_INET));
 | |
|       verify_response_data (decode_addrinfo (ai, AF_INET6));
 | |
|       break;
 | |
|     default:
 | |
|       FAIL_EXIT1 ("invalid address family %d", family);
 | |
|     }
 | |
|   freeaddrinfo (ai);
 | |
| }
 | |
| 
 | |
| enum res_op
 | |
| {
 | |
|   res_op_search,
 | |
|   res_op_query,
 | |
|   res_op_querydomain,
 | |
|   res_op_nsearch,
 | |
|   res_op_nquery,
 | |
|   res_op_nquerydomain,
 | |
| 
 | |
|   res_op_last = res_op_nquerydomain,
 | |
| };
 | |
| 
 | |
| static const char *
 | |
| res_op_string (enum res_op op)
 | |
| {
 | |
|   switch (op)
 | |
|     {
 | |
|       case res_op_search:
 | |
|         return "res_search";
 | |
|       case res_op_query:
 | |
|         return "res_query";
 | |
|       case res_op_querydomain:
 | |
|         return "res_querydomain";
 | |
|       case res_op_nsearch:
 | |
|         return "res_nsearch";
 | |
|       case res_op_nquery:
 | |
|         return "res_nquery";
 | |
|       case res_op_nquerydomain:
 | |
|         return "res_nquerydomain";
 | |
|     }
 | |
|   FAIL_EXIT1 ("invalid res_op value %d", (int) op);
 | |
| }
 | |
| 
 | |
| /* Call libresolv function OP to look up PROBE_NAME, with an answer
 | |
|    buffer of SIZE bytes.  Check that the advertised UDP buffer size is
 | |
|    in fact EXPECTED_BUFFER_SIZE.  */
 | |
| static void
 | |
| do_res_search (const char *probe_name, enum res_op op, size_t size,
 | |
|                size_t expected_buffer_size)
 | |
| {
 | |
|   if (test_verbose)
 | |
|     printf ("info: testing %s with buffer size %zu\n",
 | |
|             res_op_string (op), size);
 | |
|   unsigned char *buffer = xmalloc (size);
 | |
|   int ret = -1;
 | |
|   switch (op)
 | |
|     {
 | |
|     case res_op_search:
 | |
|       ret = res_search (probe_name, C_IN, T_A, buffer, size);
 | |
|       break;
 | |
|     case res_op_query:
 | |
|       ret = res_query (probe_name, C_IN, T_A, buffer, size);
 | |
|       break;
 | |
|     case res_op_nsearch:
 | |
|       ret = res_nsearch (&_res, probe_name, C_IN, T_A, buffer, size);
 | |
|       break;
 | |
|     case res_op_nquery:
 | |
|       ret = res_nquery (&_res, probe_name, C_IN, T_A, buffer, size);
 | |
|       break;
 | |
|     case res_op_querydomain:
 | |
|     case res_op_nquerydomain:
 | |
|       {
 | |
|         char *example_stripped = xstrdup (probe_name);
 | |
|         char *dot_example = strstr (example_stripped, ".example");
 | |
|         if (dot_example != NULL && strcmp (dot_example, ".example") == 0)
 | |
|           {
 | |
|             /* Truncate the domain name.  */
 | |
|             *dot_example = '\0';
 | |
|             if (op == res_op_querydomain)
 | |
|               ret = res_querydomain
 | |
|               (example_stripped, "example", C_IN, T_A, buffer, size);
 | |
|             else
 | |
|               ret = res_nquerydomain
 | |
|                 (&_res, example_stripped, "example", C_IN, T_A, buffer, size);
 | |
|           }
 | |
|         else
 | |
|           FAIL_EXIT1 ("invalid probe name: %s", probe_name);
 | |
|         free (example_stripped);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   TEST_VERIFY_EXIT (ret > 12);
 | |
|   unsigned char *end = buffer + ret;
 | |
| 
 | |
|   HEADER *hd = (HEADER *) buffer;
 | |
|   TEST_VERIFY (ntohs (hd->qdcount) == 1);
 | |
|   TEST_VERIFY (ntohs (hd->ancount) == 1);
 | |
|   /* Skip over the header.  */
 | |
|   unsigned char *p = buffer + sizeof (*hd);
 | |
|   /* Skip over the question.  */
 | |
|   ret = dn_skipname (p, end);
 | |
|   TEST_VERIFY_EXIT (ret > 0);
 | |
|   p += ret;
 | |
|   TEST_VERIFY_EXIT (end - p >= 4);
 | |
|   p += 4;
 | |
|   /* Skip over the RNAME and the RR header, but stop at the RDATA
 | |
|      length.  */
 | |
|   ret = dn_skipname (p, end);
 | |
|   TEST_VERIFY_EXIT (ret > 0);
 | |
|   p += ret;
 | |
|   TEST_VERIFY_EXIT (end - p >= 2 + 2 + 4 + 2 + 4);
 | |
|   p += 2 + 2 + 4;
 | |
|   /* The IP address should be 4 bytes long.  */
 | |
|   TEST_VERIFY_EXIT (p[0] == 0);
 | |
|   TEST_VERIFY_EXIT (p[1] == 4);
 | |
|   /* Extract the address information.   */
 | |
|   p += 2;
 | |
|   struct response_data *data = decode_address (p, 4);
 | |
| 
 | |
|   verify_response_data_payload (data, expected_buffer_size);
 | |
| 
 | |
|   free (buffer);
 | |
| }
 | |
| 
 | |
| static void
 | |
| run_test (const char *probe_name)
 | |
| {
 | |
|   if (test_verbose)
 | |
|     printf ("\ninfo: * use_edns=%d use_dnssec=%d\n",
 | |
|             use_edns, use_dnssec);
 | |
|   check_hostent (gethostbyname (probe_name));
 | |
|   check_hostent (gethostbyname2 (probe_name, AF_INET));
 | |
|   check_hostent (gethostbyname2 (probe_name, AF_INET6));
 | |
|   do_ai (AF_UNSPEC);
 | |
|   do_ai (AF_INET);
 | |
|   do_ai (AF_INET6);
 | |
| 
 | |
|   for (int op = 0; op <= res_op_last; ++op)
 | |
|     {
 | |
|       do_res_search (probe_name, op, 301, 512);
 | |
|       do_res_search (probe_name, op, 511, 512);
 | |
|       do_res_search (probe_name, op, 512, 512);
 | |
|       do_res_search (probe_name, op, 513, 513);
 | |
|       do_res_search (probe_name, op, 657, 657);
 | |
|       do_res_search (probe_name, op, 1199, 1199);
 | |
|       do_res_search (probe_name, op, 1200, 1200);
 | |
|       do_res_search (probe_name, op, 1201, 1200);
 | |
|       do_res_search (probe_name, op, 65535, 1200);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int
 | |
| do_test (void)
 | |
| {
 | |
|   for (int do_edns = 0; do_edns < 2; ++do_edns)
 | |
|     for (int do_dnssec = 0; do_dnssec < 2; ++do_dnssec)
 | |
|       for (int do_tcp = 0; do_tcp < 2; ++do_tcp)
 | |
|         for (int do_formerr = 0; do_formerr < 2; ++do_formerr)
 | |
|           {
 | |
|             struct resolv_test *aux = resolv_test_start
 | |
|               ((struct resolv_redirect_config)
 | |
|                {
 | |
|                  .response_callback = response,
 | |
|                });
 | |
| 
 | |
|             use_edns = do_edns;
 | |
|             if (do_edns)
 | |
|               _res.options |= RES_USE_EDNS0;
 | |
|             use_dnssec = do_dnssec;
 | |
|             if (do_dnssec)
 | |
|               _res.options |= RES_USE_DNSSEC;
 | |
| 
 | |
|             char *probe_name = xstrdup (EDNS_PROBE_EXAMPLE);
 | |
|             if (do_tcp)
 | |
|               {
 | |
|                 char *n = xasprintf ("tcp.%s", probe_name);
 | |
|                 free (probe_name);
 | |
|                 probe_name = n;
 | |
|               }
 | |
|             if (do_formerr)
 | |
|               {
 | |
|                 /* Send a garbage query in an attempt to trigger EDNS
 | |
|                    fallback.  */
 | |
|                 char *n = xasprintf ("formerr.%s", probe_name);
 | |
|                 gethostbyname (n);
 | |
|                 free (n);
 | |
|               }
 | |
| 
 | |
|             run_test (probe_name);
 | |
| 
 | |
|             free (probe_name);
 | |
|             resolv_test_end (aux);
 | |
|           }
 | |
| 
 | |
|   free_response_data ();
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| #include <support/test-driver.c>
 |