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. */
32struct 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). */
49static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
50static struct response_data ** volatile response_data_array;
51volatile 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. */
56static unsigned int
57put_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. */
81static struct response_data *
82get_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. */
92static void
93free_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
112static void
113response (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. */
206static struct response_data *
207decode_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
227static struct response_data *
228decode_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
236static struct response_data *
237decode_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. */
277static bool use_edns;
278static bool use_dnssec;
279
280/* Verify the decoded response data against the flags above. */
281static void
282verify_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. */
308static void
309verify_response_data (struct response_data *data)
310{
311 verify_response_data_payload (data, expected_payload: 1200);
312}
313
314static void
315check_hostent (struct hostent *e)
316{
317 TEST_VERIFY_EXIT (e != NULL);
318 verify_response_data (data: decode_hostent (e));
319}
320
321static void
322do_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
344enum 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
356static const char *
357res_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. */
380static void
381do_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
458static void
459run_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
485static int
486do_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

source code of glibc/resolv/tst-resolv-edns.c