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 |
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 (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. */ |
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: &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. */ |
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: &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. */ |
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 (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 | |
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 (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 | |