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. */
39struct 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. */
51static void
52parse_qname (struct parsed_qname *parsed, const char *qname)
53{
54 int reorder0;
55 int reorder1;
56 int rcode;
57 char *suffix;
58 if (sscanf (s: qname, format: "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 (ptr: 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. */
79static void
80build_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: &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, name: qname, class: qclass, type: 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, name: qname, class: qclass, type: qtype);
109
110 resolv_response_section (b, ns_s_an);
111 resolv_response_open_record (b, name: qname, class: qclass, type: qtype, ttl: 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. */
130struct resolv_response_context *previous_query;
131
132/* Used to keep track of the queries received. */
133static int previous_server_index = -1;
134static 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. */
138static void
139response (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: &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 (format: "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 (format: "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 (format: "info: sending first response for: %s\n", qname);
186 struct resolv_response_builder *btmp
187 = resolv_response_builder_allocate (query_buffer: previous_query->query_buffer,
188 query_length: previous_query->query_length);
189 build_response (ctx, b: btmp, qname, qclass, qtype: 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 (format: "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. */
213static void
214test_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 (dest: q2, src: q1, n: 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 (format: "name: %s\n"
286 "address: 192.0.2.1\n",
287 qname);
288 check_dns_packet (query_description: qname, a_answer, a_answer_length, expected);
289 free (ptr: expected);
290 }
291 {
292 char *expected = xasprintf (format: "name: %s\n"
293 "address: 2001:db8::1\n",
294 qname);
295 check_dns_packet (query_description: qname, aaaa_answer, aaaa_answer_length, expected);
296 free (ptr: expected);
297 }
298
299 if (ans2p_malloced)
300 free (ptr: ans2p);
301
302 __resolv_context_put (ctx);
303}
304
305static int
306do_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 (format: "reorder-%d-%d.rcode-%d.example.net",
324 do_reorder_0, do_reorder_1, rcode);
325 test_qname (qname, rcode);
326 free (ptr: qname);
327 }
328
329 resolv_test_end (aux);
330
331 return 0;
332}
333
334#include <support/test-driver.c>
335

source code of glibc/resolv/tst-resolv-txnid-collision.c