(root)/
glibc-2.38/
resolv/
tst-resolv-txnid-collision.c
       1  /* Test parallel queries with transaction ID collisions.
       2     Copyright (C) 2020-2023 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     <https://www.gnu.org/licenses/>.  */
      18  
      19  #include <arpa/nameser.h>
      20  #include <array_length.h>
      21  #include <resolv-internal.h>
      22  #include <resolv_context.h>
      23  #include <stdbool.h>
      24  #include <stdio.h>
      25  #include <string.h>
      26  #include <support/check.h>
      27  #include <support/check_nss.h>
      28  #include <support/resolv_test.h>
      29  #include <support/support.h>
      30  #include <support/test-driver.h>
      31  
      32  /* Result of parsing a DNS question name.
      33  
      34     A question name has the form reorder-N-M-rcode-C.example.net, where
      35     N and M are either 0 and 1, corresponding to the reorder member,
      36     and C is a number that will be stored in the rcode field.
      37  
      38     Also see parse_qname below.  */
      39  struct parsed_qname
      40  {
      41    /* The DNS response code requested from the first server.  The
      42       second server always responds with RCODE zero.  */
      43    int rcode;
      44  
      45    /* Indicates whether to perform reordering in the responses from the
      46       respective server.  */
      47    bool reorder[2];
      48  };
      49  
      50  /* Fills *PARSED based on QNAME.  */
      51  static void
      52  parse_qname (struct parsed_qname *parsed, const char *qname)
      53  {
      54    int reorder0;
      55    int reorder1;
      56    int rcode;
      57    char *suffix;
      58    if (sscanf (qname, "reorder-%d-%d.rcode-%d.%ms",
      59                &reorder0, &reorder1, &rcode, &suffix) == 4)
      60      {
      61        if (reorder0 != 0)
      62          TEST_COMPARE (reorder0, 1);
      63        if (reorder1 != 0)
      64          TEST_COMPARE (reorder1, 1);
      65        TEST_VERIFY (rcode >= 0 && rcode <= 15);
      66        TEST_COMPARE_STRING (suffix, "example.net");
      67        free (suffix);
      68  
      69        parsed->rcode = rcode;
      70        parsed->reorder[0] = reorder0;
      71        parsed->reorder[1] = reorder1;
      72      }
      73    else
      74      FAIL_EXIT1 ("unexpected query: %s", qname);
      75  }
      76  
      77  /* Used to construct a response. The first server responds with an
      78     error, the second server succeeds.  */
      79  static void
      80  build_response (const struct resolv_response_context *ctx,
      81                  struct resolv_response_builder *b,
      82                  const char *qname, uint16_t qclass, uint16_t qtype)
      83  {
      84    struct parsed_qname parsed;
      85    parse_qname (&parsed, qname);
      86  
      87    switch (ctx->server_index)
      88      {
      89      case 0:
      90        {
      91          struct resolv_response_flags flags = { 0 };
      92          if (parsed.rcode == 0)
      93            /* Simulate a delegation in case a NODATA (RCODE zero)
      94               response is requested.  */
      95            flags.clear_ra = true;
      96          else
      97            flags.rcode = parsed.rcode;
      98  
      99          resolv_response_init (b, flags);
     100          resolv_response_add_question (b, qname, qclass, qtype);
     101        }
     102        break;
     103  
     104      case 1:
     105        {
     106          struct resolv_response_flags flags = { 0, };
     107          resolv_response_init (b, flags);
     108          resolv_response_add_question (b, qname, qclass, qtype);
     109  
     110          resolv_response_section (b, ns_s_an);
     111          resolv_response_open_record (b, qname, qclass, qtype, 0);
     112          if (qtype == T_A)
     113            {
     114              char ipv4[4] = { 192, 0, 2, 1 };
     115              resolv_response_add_data (b, &ipv4, sizeof (ipv4));
     116            }
     117          else
     118            {
     119              char ipv6[16]
     120                = { 0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
     121              resolv_response_add_data (b, &ipv6, sizeof (ipv6));
     122            }
     123          resolv_response_close_record (b);
     124        }
     125        break;
     126      }
     127  }
     128  
     129  /* Used to reorder responses.  */
     130  struct resolv_response_context *previous_query;
     131  
     132  /* Used to keep track of the queries received.  */
     133  static int previous_server_index = -1;
     134  static uint16_t previous_qtype;
     135  
     136  /* For each server, buffer the first query and then send both answers
     137     to the second query, reordered if requested.  */
     138  static void
     139  response (const struct resolv_response_context *ctx,
     140            struct resolv_response_builder *b,
     141            const char *qname, uint16_t qclass, uint16_t qtype)
     142  {
     143    TEST_VERIFY (qtype == T_A || qtype == T_AAAA);
     144    if (ctx->server_index != 0)
     145      TEST_COMPARE (ctx->server_index, 1);
     146  
     147    struct parsed_qname parsed;
     148    parse_qname (&parsed, qname);
     149  
     150    if (previous_query == NULL)
     151      {
     152        /* No buffered query.  Record this query and do not send a
     153           response.  */
     154        TEST_COMPARE (previous_qtype, 0);
     155        previous_query = resolv_response_context_duplicate (ctx);
     156        previous_qtype = qtype;
     157        resolv_response_drop (b);
     158        previous_server_index = ctx->server_index;
     159  
     160        if (test_verbose)
     161          printf ("info: buffering first query for: %s\n", qname);
     162      }
     163    else
     164      {
     165        TEST_VERIFY (previous_query != 0);
     166        TEST_COMPARE (ctx->server_index, previous_server_index);
     167        TEST_VERIFY (previous_qtype != qtype); /* Not a duplicate.  */
     168  
     169        /* If reordering, send a response for this query explicitly, and
     170           then skip the implicit send.  */
     171        if (parsed.reorder[ctx->server_index])
     172          {
     173            if (test_verbose)
     174              printf ("info: sending reordered second response for: %s\n",
     175                      qname);
     176            build_response (ctx, b, qname, qclass, qtype);
     177            resolv_response_send_udp (ctx, b);
     178            resolv_response_drop (b);
     179          }
     180  
     181        /* Build a response for the previous query and send it, thus
     182           reordering the two responses.  */
     183        {
     184          if (test_verbose)
     185            printf ("info: sending first response for: %s\n", qname);
     186          struct resolv_response_builder *btmp
     187            = resolv_response_builder_allocate (previous_query->query_buffer,
     188                                                previous_query->query_length);
     189          build_response (ctx, btmp, qname, qclass, previous_qtype);
     190          resolv_response_send_udp (ctx, btmp);
     191          resolv_response_builder_free (btmp);
     192        }
     193  
     194        /* If not reordering, send the reply as usual.  */
     195        if (!parsed.reorder[ctx->server_index])
     196          {
     197            if (test_verbose)
     198              printf ("info: sending non-reordered second response for: %s\n",
     199                      qname);
     200            build_response (ctx, b, qname, qclass, qtype);
     201          }
     202  
     203        /* Unbuffer the response and prepare for the next query.  */
     204        resolv_response_context_free (previous_query);
     205        previous_query = NULL;
     206        previous_qtype = 0;
     207        previous_server_index = -1;
     208      }
     209  }
     210  
     211  /* Runs a query for QNAME and checks for the expected reply.  See
     212     struct parsed_qname for the expected format for QNAME.  */
     213  static void
     214  test_qname (const char *qname, int rcode)
     215  {
     216    struct resolv_context *ctx = __resolv_context_get ();
     217    TEST_VERIFY_EXIT (ctx != NULL);
     218  
     219    unsigned char q1[512];
     220    int q1len = res_mkquery (QUERY, qname, C_IN, T_A, NULL, 0, NULL,
     221                             q1, sizeof (q1));
     222    TEST_VERIFY_EXIT (q1len > 12);
     223  
     224    unsigned char q2[512];
     225    int q2len = res_mkquery (QUERY, qname, C_IN, T_AAAA, NULL, 0, NULL,
     226                             q2, sizeof (q2));
     227    TEST_VERIFY_EXIT (q2len > 12);
     228  
     229    /* Produce a transaction ID collision.  */
     230    memcpy (q2, q1, 2);
     231  
     232    unsigned char ans1[512];
     233    unsigned char *ans1p = ans1;
     234    unsigned char *ans2p = NULL;
     235    int nans2p = 0;
     236    int resplen2 = 0;
     237    int ans2p_malloced = 0;
     238  
     239    /* Perform a parallel A/AAAA query.  */
     240    int resplen1 = __res_context_send (ctx, q1, q1len, q2, q2len,
     241                                       ans1, sizeof (ans1), &ans1p,
     242                                       &ans2p, &nans2p,
     243                                       &resplen2, &ans2p_malloced);
     244  
     245    TEST_VERIFY (resplen1 > 12);
     246    TEST_VERIFY (resplen2 > 12);
     247    if (resplen1 <= 12 || resplen2 <= 12)
     248      return;
     249  
     250    if (rcode == 1 || rcode == 3)
     251      {
     252        /* Format Error and Name Error responses does not trigger
     253           switching to the next server.  */
     254        TEST_COMPARE (ans1p[3] & 0x0f, rcode);
     255        TEST_COMPARE (ans2p[3] & 0x0f, rcode);
     256        return;
     257      }
     258  
     259    /* The response should be successful.  */
     260    TEST_COMPARE (ans1p[3] & 0x0f, 0);
     261    TEST_COMPARE (ans2p[3] & 0x0f, 0);
     262  
     263    /* Due to bug 19691, the answer may not be in the slot matching the
     264       query.  Assume that the AAAA response is the longer one.  */
     265    unsigned char *a_answer;
     266    int a_answer_length;
     267    unsigned char *aaaa_answer;
     268    int aaaa_answer_length;
     269    if (resplen2 > resplen1)
     270      {
     271        a_answer = ans1p;
     272        a_answer_length = resplen1;
     273        aaaa_answer = ans2p;
     274        aaaa_answer_length = resplen2;
     275      }
     276    else
     277      {
     278        a_answer = ans2p;
     279        a_answer_length = resplen2;
     280        aaaa_answer = ans1p;
     281        aaaa_answer_length = resplen1;
     282      }
     283  
     284    {
     285      char *expected = xasprintf ("name: %s\n"
     286                                  "address: 192.0.2.1\n",
     287                                  qname);
     288      check_dns_packet (qname, a_answer, a_answer_length, expected);
     289      free (expected);
     290    }
     291    {
     292      char *expected = xasprintf ("name: %s\n"
     293                                  "address: 2001:db8::1\n",
     294                                  qname);
     295      check_dns_packet (qname, aaaa_answer, aaaa_answer_length, expected);
     296      free (expected);
     297    }
     298  
     299    if (ans2p_malloced)
     300      free (ans2p);
     301  
     302    __resolv_context_put (ctx);
     303  }
     304  
     305  static int
     306  do_test (void)
     307  {
     308    struct resolv_test *aux = resolv_test_start
     309      ((struct resolv_redirect_config)
     310       {
     311         .response_callback = response,
     312  
     313         /* The response callback use global state (the previous_*
     314            variables), and query processing must therefore be
     315            serialized.  */
     316         .single_thread_udp = true,
     317       });
     318  
     319    for (int rcode = 0; rcode <= 5; ++rcode)
     320      for (int do_reorder_0 = 0; do_reorder_0 < 2; ++do_reorder_0)
     321        for (int do_reorder_1 = 0; do_reorder_1 < 2; ++do_reorder_1)
     322          {
     323            char *qname = xasprintf ("reorder-%d-%d.rcode-%d.example.net",
     324                                     do_reorder_0, do_reorder_1, rcode);
     325            test_qname (qname, rcode);
     326            free (qname);
     327          }
     328  
     329    resolv_test_end (aux);
     330  
     331    return 0;
     332  }
     333  
     334  #include <support/test-driver.c>