]>
Commit | Line | Data |
---|---|---|
f1f00c07 | 1 | /* Test parallel queries with transaction ID collisions. |
6d7e8eda | 2 | Copyright (C) 2020-2023 Free Software Foundation, Inc. |
f1f00c07 FW |
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, | |
b8b53b33 FW |
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, | |
f1f00c07 FW |
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> |