1 | /* Test EDNS handling in the stub resolver. |
2 | Copyright (C) 2016-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 <errno.h> |
20 | #include <netdb.h> |
21 | #include <resolv.h> |
22 | #include <stdio.h> |
23 | #include <stdlib.h> |
24 | #include <string.h> |
25 | #include <support/check.h> |
26 | #include <support/resolv_test.h> |
27 | #include <support/support.h> |
28 | #include <support/test-driver.h> |
29 | #include <support/xthread.h> |
30 | |
31 | /* Data produced by a test query. */ |
32 | struct response_data |
33 | { |
34 | char *qname; |
35 | uint16_t qtype; |
36 | struct resolv_edns_info edns; |
37 | }; |
38 | |
39 | /* Global array used by put_response and get_response to record |
40 | response data. The test DNS server returns the index of the array |
41 | element which contains the actual response data. This enables the |
42 | test case to return arbitrary amounts of data with the limited |
43 | number of bits which fit into an IP addres. |
44 | |
45 | The volatile specifier is needed because the test case accesses |
46 | these variables from a callback function called from a function |
47 | which is marked as __THROW (i.e., a leaf function which actually is |
48 | not). */ |
49 | static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
50 | static struct response_data ** volatile response_data_array; |
51 | volatile static size_t response_data_count; |
52 | |
53 | /* Extract information from the query, store it in a struct |
54 | response_data object, and return its index in the |
55 | response_data_array. */ |
56 | static unsigned int |
57 | put_response (const struct resolv_response_context *ctx, |
58 | const char *qname, uint16_t qtype) |
59 | { |
60 | xpthread_mutex_lock (mutex: &mutex); |
61 | ++response_data_count; |
62 | /* We only can represent 2**24 indexes in 10.0.0.0/8. */ |
63 | TEST_VERIFY (response_data_count < (1 << 24)); |
64 | response_data_array = xrealloc |
65 | (o: response_data_array, n: sizeof (*response_data_array) * response_data_count); |
66 | unsigned int index = response_data_count - 1; |
67 | struct response_data *data = xmalloc (n: sizeof (*data)); |
68 | *data = (struct response_data) |
69 | { |
70 | .qname = xstrdup (qname), |
71 | .qtype = qtype, |
72 | .edns = ctx->edns, |
73 | }; |
74 | response_data_array[index] = data; |
75 | xpthread_mutex_unlock (mutex: &mutex); |
76 | return index; |
77 | } |
78 | |
79 | /* Verify the index into the response_data array and return the data |
80 | at it. */ |
81 | static struct response_data * |
82 | get_response (unsigned int index) |
83 | { |
84 | xpthread_mutex_lock (mutex: &mutex); |
85 | TEST_VERIFY_EXIT (index < response_data_count); |
86 | struct response_data *result = response_data_array[index]; |
87 | xpthread_mutex_unlock (mutex: &mutex); |
88 | return result; |
89 | } |
90 | |
91 | /* Deallocate all response data. */ |
92 | static void |
93 | free_response_data (void) |
94 | { |
95 | xpthread_mutex_lock (mutex: &mutex); |
96 | size_t count = response_data_count; |
97 | struct response_data **array = response_data_array; |
98 | for (unsigned int i = 0; i < count; ++i) |
99 | { |
100 | struct response_data *data = array[i]; |
101 | free (ptr: data->qname); |
102 | free (ptr: data); |
103 | } |
104 | free (ptr: array); |
105 | response_data_array = NULL; |
106 | response_data_count = 0; |
107 | xpthread_mutex_unlock (mutex: &mutex); |
108 | } |
109 | |
110 | #define EDNS_PROBE_EXAMPLE "edns-probe.example" |
111 | |
112 | static void |
113 | response (const struct resolv_response_context *ctx, |
114 | struct resolv_response_builder *b, |
115 | const char *qname, uint16_t qclass, uint16_t qtype) |
116 | { |
117 | TEST_VERIFY_EXIT (qname != NULL); |
118 | |
119 | const char *qname_compare = qname; |
120 | |
121 | /* The "formerr." prefix can be used to request a FORMERR response on the |
122 | first server. */ |
123 | bool send_formerr; |
124 | if (strncmp (s1: "formerr." , s2: qname, n: strlen (s: "formerr." )) == 0) |
125 | { |
126 | send_formerr = true; |
127 | qname_compare = qname + strlen (s: "formerr." ); |
128 | } |
129 | else |
130 | { |
131 | send_formerr = false; |
132 | qname_compare = qname; |
133 | } |
134 | |
135 | /* The "tcp." prefix can be used to request TCP fallback. */ |
136 | bool force_tcp; |
137 | if (strncmp (s1: "tcp." , s2: qname_compare, n: strlen (s: "tcp." )) == 0) |
138 | { |
139 | force_tcp = true; |
140 | qname_compare += strlen (s: "tcp." ); |
141 | } |
142 | else |
143 | force_tcp = false; |
144 | |
145 | enum {edns_probe} requested_qname; |
146 | if (strcmp (s1: qname_compare, EDNS_PROBE_EXAMPLE) == 0) |
147 | requested_qname = edns_probe; |
148 | else |
149 | { |
150 | support_record_failure (); |
151 | printf (format: "error: unexpected QNAME: %s (reduced: %s)\n" , |
152 | qname, qname_compare); |
153 | return; |
154 | } |
155 | TEST_VERIFY_EXIT (qclass == C_IN); |
156 | struct resolv_response_flags flags = { }; |
157 | flags.tc = force_tcp && !ctx->tcp; |
158 | if (!flags.tc && send_formerr && ctx->server_index == 0) |
159 | /* Send a FORMERR for the first full response from the first |
160 | server. */ |
161 | flags.rcode = 1; /* FORMERR */ |
162 | resolv_response_init (b, flags); |
163 | resolv_response_add_question (b, name: qname, class: qclass, type: qtype); |
164 | if (flags.tc || flags.rcode != 0) |
165 | return; |
166 | |
167 | if (test_verbose) |
168 | printf (format: "info: edns=%d payload_size=%d\n" , |
169 | ctx->edns.active, ctx->edns.payload_size); |
170 | |
171 | /* Encode the response_data object in multiple address records. |
172 | Each record carries two bytes of payload data, and an index. */ |
173 | resolv_response_section (b, ns_s_an); |
174 | switch (requested_qname) |
175 | { |
176 | case edns_probe: |
177 | { |
178 | unsigned int index = put_response (ctx, qname, qtype); |
179 | switch (qtype) |
180 | { |
181 | case T_A: |
182 | { |
183 | uint32_t addr = htonl (0x0a000000 | index); |
184 | resolv_response_open_record (b, name: qname, class: qclass, type: qtype, ttl: 0); |
185 | resolv_response_add_data (b, &addr, sizeof (addr)); |
186 | resolv_response_close_record (b); |
187 | } |
188 | break; |
189 | case T_AAAA: |
190 | { |
191 | char addr[16] |
192 | = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
193 | index >> 16, index >> 8, index}; |
194 | resolv_response_open_record (b, name: qname, class: qclass, type: qtype, ttl: 0); |
195 | resolv_response_add_data (b, &addr, sizeof (addr)); |
196 | resolv_response_close_record (b); |
197 | } |
198 | } |
199 | } |
200 | break; |
201 | } |
202 | } |
203 | |
204 | /* Update *DATA with data from ADDRESS of SIZE. Set the corresponding |
205 | flag in SHADOW for each byte written. */ |
206 | static struct response_data * |
207 | decode_address (const void *address, size_t size) |
208 | { |
209 | switch (size) |
210 | { |
211 | case 4: |
212 | TEST_VERIFY (memcmp (address, "\x0a" , 1) == 0); |
213 | break; |
214 | case 16: |
215 | TEST_VERIFY (memcmp (address, "\x20\x01\x0d\xb8" , 4) == 0); |
216 | break; |
217 | default: |
218 | FAIL_EXIT1 ("unexpected address size %zu" , size); |
219 | } |
220 | const unsigned char *addr = address; |
221 | unsigned int index = addr[size - 3] * 256 * 256 |
222 | + addr[size - 2] * 256 |
223 | + addr[size - 1]; |
224 | return get_response (index); |
225 | } |
226 | |
227 | static struct response_data * |
228 | decode_hostent (struct hostent *e) |
229 | { |
230 | TEST_VERIFY_EXIT (e != NULL); |
231 | TEST_VERIFY_EXIT (e->h_addr_list[0] != NULL); |
232 | TEST_VERIFY (e->h_addr_list[1] == NULL); |
233 | return decode_address (address: e->h_addr_list[0], size: e->h_length); |
234 | } |
235 | |
236 | static struct response_data * |
237 | decode_addrinfo (struct addrinfo *ai, int family) |
238 | { |
239 | struct response_data *data = NULL; |
240 | while (ai != NULL) |
241 | { |
242 | if (ai->ai_family == family) |
243 | { |
244 | struct response_data *new_data; |
245 | switch (family) |
246 | { |
247 | case AF_INET: |
248 | { |
249 | struct sockaddr_in *pin = (struct sockaddr_in *) ai->ai_addr; |
250 | new_data = decode_address (address: &pin->sin_addr.s_addr, size: 4); |
251 | } |
252 | break; |
253 | case AF_INET6: |
254 | { |
255 | struct sockaddr_in6 *pin = (struct sockaddr_in6 *) ai->ai_addr; |
256 | new_data = decode_address (address: &pin->sin6_addr.s6_addr, size: 16); |
257 | } |
258 | break; |
259 | default: |
260 | FAIL_EXIT1 ("invalid address family %d" , ai->ai_family); |
261 | } |
262 | if (data == NULL) |
263 | data = new_data; |
264 | else |
265 | /* Check pointer equality because this should be the same |
266 | response (same index). */ |
267 | TEST_VERIFY (data == new_data); |
268 | } |
269 | ai = ai->ai_next; |
270 | } |
271 | TEST_VERIFY_EXIT (data != NULL); |
272 | return data; |
273 | } |
274 | |
275 | /* Updated by the main test loop in accordance with what is set in |
276 | _res.options. */ |
277 | static bool use_edns; |
278 | static bool use_dnssec; |
279 | |
280 | /* Verify the decoded response data against the flags above. */ |
281 | static void |
282 | verify_response_data_payload (struct response_data *data, |
283 | size_t expected_payload) |
284 | { |
285 | bool edns = use_edns || use_dnssec; |
286 | TEST_VERIFY (data->edns.active == edns); |
287 | if (!edns) |
288 | expected_payload = 0; |
289 | if (data->edns.payload_size != expected_payload) |
290 | { |
291 | support_record_failure (); |
292 | printf (format: "error: unexpected payload size %d (edns=%d)\n" , |
293 | (int) data->edns.payload_size, edns); |
294 | } |
295 | uint16_t expected_flags = 0; |
296 | if (use_dnssec) |
297 | expected_flags |= 0x8000; /* DO flag. */ |
298 | if (data->edns.flags != expected_flags) |
299 | { |
300 | support_record_failure (); |
301 | printf (format: "error: unexpected EDNS flags 0x%04x (edns=%d)\n" , |
302 | (int) data->edns.flags, edns); |
303 | } |
304 | } |
305 | |
306 | /* Same as verify_response_data_payload, but use the default |
307 | payload. */ |
308 | static void |
309 | verify_response_data (struct response_data *data) |
310 | { |
311 | verify_response_data_payload (data, expected_payload: 1200); |
312 | } |
313 | |
314 | static void |
315 | check_hostent (struct hostent *e) |
316 | { |
317 | TEST_VERIFY_EXIT (e != NULL); |
318 | verify_response_data (data: decode_hostent (e)); |
319 | } |
320 | |
321 | static void |
322 | do_ai (int family) |
323 | { |
324 | struct addrinfo hints = { .ai_family = family }; |
325 | struct addrinfo *ai; |
326 | int ret = getaddrinfo (EDNS_PROBE_EXAMPLE, service: "80" , req: &hints, pai: &ai); |
327 | TEST_VERIFY_EXIT (ret == 0); |
328 | switch (family) |
329 | { |
330 | case AF_INET: |
331 | case AF_INET6: |
332 | verify_response_data (data: decode_addrinfo (ai, family)); |
333 | break; |
334 | case AF_UNSPEC: |
335 | verify_response_data (data: decode_addrinfo (ai, AF_INET)); |
336 | verify_response_data (data: decode_addrinfo (ai, AF_INET6)); |
337 | break; |
338 | default: |
339 | FAIL_EXIT1 ("invalid address family %d" , family); |
340 | } |
341 | freeaddrinfo (ai: ai); |
342 | } |
343 | |
344 | enum res_op |
345 | { |
346 | res_op_search, |
347 | res_op_query, |
348 | res_op_querydomain, |
349 | res_op_nsearch, |
350 | res_op_nquery, |
351 | res_op_nquerydomain, |
352 | |
353 | res_op_last = res_op_nquerydomain, |
354 | }; |
355 | |
356 | static const char * |
357 | res_op_string (enum res_op op) |
358 | { |
359 | switch (op) |
360 | { |
361 | case res_op_search: |
362 | return "res_search" ; |
363 | case res_op_query: |
364 | return "res_query" ; |
365 | case res_op_querydomain: |
366 | return "res_querydomain" ; |
367 | case res_op_nsearch: |
368 | return "res_nsearch" ; |
369 | case res_op_nquery: |
370 | return "res_nquery" ; |
371 | case res_op_nquerydomain: |
372 | return "res_nquerydomain" ; |
373 | } |
374 | FAIL_EXIT1 ("invalid res_op value %d" , (int) op); |
375 | } |
376 | |
377 | /* Call libresolv function OP to look up PROBE_NAME, with an answer |
378 | buffer of SIZE bytes. Check that the advertised UDP buffer size is |
379 | in fact EXPECTED_BUFFER_SIZE. */ |
380 | static void |
381 | do_res_search (const char *probe_name, enum res_op op, size_t size, |
382 | size_t expected_buffer_size) |
383 | { |
384 | if (test_verbose) |
385 | printf (format: "info: testing %s with buffer size %zu\n" , |
386 | res_op_string (op), size); |
387 | unsigned char *buffer = xmalloc (n: size); |
388 | int ret = -1; |
389 | switch (op) |
390 | { |
391 | case res_op_search: |
392 | ret = res_search (probe_name, C_IN, T_A, buffer, size); |
393 | break; |
394 | case res_op_query: |
395 | ret = res_query (probe_name, C_IN, T_A, buffer, size); |
396 | break; |
397 | case res_op_nsearch: |
398 | ret = res_nsearch (&_res, probe_name, C_IN, T_A, buffer, size); |
399 | break; |
400 | case res_op_nquery: |
401 | ret = res_nquery (&_res, probe_name, C_IN, T_A, buffer, size); |
402 | break; |
403 | case res_op_querydomain: |
404 | case res_op_nquerydomain: |
405 | { |
406 | char *example_stripped = xstrdup (probe_name); |
407 | char *dot_example = strstr (haystack: example_stripped, needle: ".example" ); |
408 | if (dot_example != NULL && strcmp (s1: dot_example, s2: ".example" ) == 0) |
409 | { |
410 | /* Truncate the domain name. */ |
411 | *dot_example = '\0'; |
412 | if (op == res_op_querydomain) |
413 | ret = res_querydomain |
414 | (example_stripped, "example" , C_IN, T_A, buffer, size); |
415 | else |
416 | ret = res_nquerydomain |
417 | (&_res, example_stripped, "example" , C_IN, T_A, buffer, size); |
418 | } |
419 | else |
420 | FAIL_EXIT1 ("invalid probe name: %s" , probe_name); |
421 | free (ptr: example_stripped); |
422 | } |
423 | break; |
424 | } |
425 | TEST_VERIFY_EXIT (ret > 12); |
426 | unsigned char *end = buffer + ret; |
427 | |
428 | HEADER *hd = (HEADER *) buffer; |
429 | TEST_VERIFY (ntohs (hd->qdcount) == 1); |
430 | TEST_VERIFY (ntohs (hd->ancount) == 1); |
431 | /* Skip over the header. */ |
432 | unsigned char *p = buffer + sizeof (*hd); |
433 | /* Skip over the question. */ |
434 | ret = dn_skipname (p, end); |
435 | TEST_VERIFY_EXIT (ret > 0); |
436 | p += ret; |
437 | TEST_VERIFY_EXIT (end - p >= 4); |
438 | p += 4; |
439 | /* Skip over the RNAME and the RR header, but stop at the RDATA |
440 | length. */ |
441 | ret = dn_skipname (p, end); |
442 | TEST_VERIFY_EXIT (ret > 0); |
443 | p += ret; |
444 | TEST_VERIFY_EXIT (end - p >= 2 + 2 + 4 + 2 + 4); |
445 | p += 2 + 2 + 4; |
446 | /* The IP address should be 4 bytes long. */ |
447 | TEST_VERIFY_EXIT (p[0] == 0); |
448 | TEST_VERIFY_EXIT (p[1] == 4); |
449 | /* Extract the address information. */ |
450 | p += 2; |
451 | struct response_data *data = decode_address (address: p, size: 4); |
452 | |
453 | verify_response_data_payload (data, expected_payload: expected_buffer_size); |
454 | |
455 | free (ptr: buffer); |
456 | } |
457 | |
458 | static void |
459 | run_test (const char *probe_name) |
460 | { |
461 | if (test_verbose) |
462 | printf (format: "\ninfo: * use_edns=%d use_dnssec=%d\n" , |
463 | use_edns, use_dnssec); |
464 | check_hostent (e: gethostbyname (name: probe_name)); |
465 | check_hostent (e: gethostbyname2 (name: probe_name, AF_INET)); |
466 | check_hostent (e: gethostbyname2 (name: probe_name, AF_INET6)); |
467 | do_ai (AF_UNSPEC); |
468 | do_ai (AF_INET); |
469 | do_ai (AF_INET6); |
470 | |
471 | for (int op = 0; op <= res_op_last; ++op) |
472 | { |
473 | do_res_search (probe_name, op, size: 301, expected_buffer_size: 512); |
474 | do_res_search (probe_name, op, size: 511, expected_buffer_size: 512); |
475 | do_res_search (probe_name, op, size: 512, expected_buffer_size: 512); |
476 | do_res_search (probe_name, op, size: 513, expected_buffer_size: 513); |
477 | do_res_search (probe_name, op, size: 657, expected_buffer_size: 657); |
478 | do_res_search (probe_name, op, size: 1199, expected_buffer_size: 1199); |
479 | do_res_search (probe_name, op, size: 1200, expected_buffer_size: 1200); |
480 | do_res_search (probe_name, op, size: 1201, expected_buffer_size: 1200); |
481 | do_res_search (probe_name, op, size: 65535, expected_buffer_size: 1200); |
482 | } |
483 | } |
484 | |
485 | static int |
486 | do_test (void) |
487 | { |
488 | for (int do_edns = 0; do_edns < 2; ++do_edns) |
489 | for (int do_dnssec = 0; do_dnssec < 2; ++do_dnssec) |
490 | for (int do_tcp = 0; do_tcp < 2; ++do_tcp) |
491 | for (int do_formerr = 0; do_formerr < 2; ++do_formerr) |
492 | { |
493 | struct resolv_test *aux = resolv_test_start |
494 | ((struct resolv_redirect_config) |
495 | { |
496 | .response_callback = response, |
497 | }); |
498 | |
499 | use_edns = do_edns; |
500 | if (do_edns) |
501 | _res.options |= RES_USE_EDNS0; |
502 | use_dnssec = do_dnssec; |
503 | if (do_dnssec) |
504 | _res.options |= RES_USE_DNSSEC; |
505 | |
506 | char *probe_name = xstrdup (EDNS_PROBE_EXAMPLE); |
507 | if (do_tcp) |
508 | { |
509 | char *n = xasprintf (format: "tcp.%s" , probe_name); |
510 | free (ptr: probe_name); |
511 | probe_name = n; |
512 | } |
513 | if (do_formerr) |
514 | { |
515 | /* Send a garbage query in an attempt to trigger EDNS |
516 | fallback. */ |
517 | char *n = xasprintf (format: "formerr.%s" , probe_name); |
518 | gethostbyname (name: n); |
519 | free (ptr: n); |
520 | } |
521 | |
522 | run_test (probe_name); |
523 | |
524 | free (ptr: probe_name); |
525 | resolv_test_end (aux); |
526 | } |
527 | |
528 | free_response_data (); |
529 | return 0; |
530 | } |
531 | |
532 | #include <support/test-driver.c> |
533 | |