]>
Commit | Line | Data |
---|---|---|
e14a2772 | 1 | /* Test EDNS handling in the stub resolver. |
688903eb | 2 | Copyright (C) 2016-2018 Free Software Foundation, Inc. |
e14a2772 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 | <http://www.gnu.org/licenses/>. */ | |
18 | ||
19 | #include <errno.h> | |
20 | #include <netdb.h> | |
25cfd502 | 21 | #include <resolv.h> |
e14a2772 FW |
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); | |
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 | (response_data_array, sizeof (*response_data_array) * response_data_count); | |
66 | unsigned int index = response_data_count - 1; | |
67 | struct response_data *data = xmalloc (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); | |
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); | |
85 | TEST_VERIFY_EXIT (index < response_data_count); | |
86 | struct response_data *result = response_data_array[index]; | |
87 | xpthread_mutex_unlock (&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); | |
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 (data->qname); | |
102 | free (data); | |
103 | } | |
104 | free (array); | |
105 | response_data_array = NULL; | |
106 | response_data_count = 0; | |
107 | xpthread_mutex_unlock (&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 | ||
e14a2772 | 119 | const char *qname_compare = qname; |
44500cbb FW |
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 ("formerr.", qname, strlen ("formerr.")) == 0) | |
125 | { | |
126 | send_formerr = true; | |
127 | qname_compare = qname + strlen ("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. */ | |
e14a2772 FW |
136 | bool force_tcp; |
137 | if (strncmp ("tcp.", qname_compare, strlen ("tcp.")) == 0) | |
138 | { | |
139 | force_tcp = true; | |
140 | qname_compare += strlen ("tcp."); | |
141 | } | |
142 | else | |
143 | force_tcp = false; | |
144 | ||
145 | enum {edns_probe} requested_qname; | |
146 | if (strcmp (qname_compare, EDNS_PROBE_EXAMPLE) == 0) | |
147 | requested_qname = edns_probe; | |
148 | else | |
149 | { | |
150 | support_record_failure (); | |
44500cbb FW |
151 | printf ("error: unexpected QNAME: %s (reduced: %s)\n", |
152 | qname, qname_compare); | |
e14a2772 FW |
153 | return; |
154 | } | |
155 | TEST_VERIFY_EXIT (qclass == C_IN); | |
44500cbb FW |
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 */ | |
e14a2772 FW |
162 | resolv_response_init (b, flags); |
163 | resolv_response_add_question (b, qname, qclass, qtype); | |
44500cbb | 164 | if (flags.tc || flags.rcode != 0) |
e14a2772 FW |
165 | return; |
166 | ||
167 | if (test_verbose) | |
168 | printf ("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, qname, qclass, qtype, 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, qname, qclass, qtype, 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 (e->h_addr_list[0], 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 (&pin->sin_addr.s_addr, 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 (&pin->sin6_addr.s6_addr, 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 ("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 ("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, 1200); | |
312 | } | |
313 | ||
314 | static void | |
315 | check_hostent (struct hostent *e) | |
316 | { | |
317 | TEST_VERIFY_EXIT (e != NULL); | |
318 | verify_response_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, "80", &hints, &ai); | |
327 | TEST_VERIFY_EXIT (ret == 0); | |
328 | switch (family) | |
329 | { | |
330 | case AF_INET: | |
331 | case AF_INET6: | |
332 | verify_response_data (decode_addrinfo (ai, family)); | |
333 | break; | |
334 | case AF_UNSPEC: | |
335 | verify_response_data (decode_addrinfo (ai, AF_INET)); | |
336 | verify_response_data (decode_addrinfo (ai, AF_INET6)); | |
337 | break; | |
338 | default: | |
339 | FAIL_EXIT1 ("invalid address family %d", family); | |
340 | } | |
341 | freeaddrinfo (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 ("info: testing %s with buffer size %zu\n", | |
386 | res_op_string (op), size); | |
387 | unsigned char *buffer = xmalloc (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 (example_stripped, ".example"); | |
408 | if (dot_example != NULL && strcmp (dot_example, ".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 (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 (p, 4); | |
452 | ||
453 | verify_response_data_payload (data, expected_buffer_size); | |
454 | ||
455 | free (buffer); | |
456 | } | |
457 | ||
458 | static void | |
459 | run_test (const char *probe_name) | |
460 | { | |
461 | if (test_verbose) | |
462 | printf ("\ninfo: * use_edns=%d use_dnssec=%d\n", | |
463 | use_edns, use_dnssec); | |
464 | check_hostent (gethostbyname (probe_name)); | |
465 | check_hostent (gethostbyname2 (probe_name, AF_INET)); | |
466 | check_hostent (gethostbyname2 (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, 301, 512); | |
474 | do_res_search (probe_name, op, 511, 512); | |
475 | do_res_search (probe_name, op, 512, 512); | |
476 | do_res_search (probe_name, op, 513, 513); | |
477 | do_res_search (probe_name, op, 657, 657); | |
478 | do_res_search (probe_name, op, 1199, 1199); | |
479 | do_res_search (probe_name, op, 1200, 1200); | |
480 | do_res_search (probe_name, op, 1201, 1200); | |
481 | do_res_search (probe_name, op, 65535, 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) | |
44500cbb FW |
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 ("tcp.%s", probe_name); | |
510 | free (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 ("formerr.%s", probe_name); | |
518 | gethostbyname (n); | |
519 | free (n); | |
520 | } | |
e14a2772 | 521 | |
44500cbb | 522 | run_test (probe_name); |
e14a2772 | 523 | |
44500cbb FW |
524 | free (probe_name); |
525 | resolv_test_end (aux); | |
526 | } | |
e14a2772 FW |
527 | |
528 | free_response_data (); | |
529 | return 0; | |
530 | } | |
531 | ||
532 | #include <support/test-driver.c> |