1 /* Test parallel queries with transaction ID collisions.
2    Copyright (C) 2020-2022 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
parse_qname(struct parsed_qname * parsed,const char * qname)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
build_response(const struct resolv_response_context * ctx,struct resolv_response_builder * b,const char * qname,uint16_t qclass,uint16_t qtype)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
response(const struct resolv_response_context * ctx,struct resolv_response_builder * b,const char * qname,uint16_t qclass,uint16_t qtype)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
test_qname(const char * qname,int rcode)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
do_test(void)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>
335