1 | /* DNS test framework and libresolv redirection. |
2 | Copyright (C) 2016-2017 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 | <http://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <support/resolv_test.h> |
20 | |
21 | #include <arpa/inet.h> |
22 | #include <errno.h> |
23 | #include <fcntl.h> |
24 | #include <nss.h> |
25 | #include <resolv.h> |
26 | #include <search.h> |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | #include <support/check.h> |
30 | #include <support/namespace.h> |
31 | #include <support/support.h> |
32 | #include <support/test-driver.h> |
33 | #include <support/xsocket.h> |
34 | #include <support/xthread.h> |
35 | #include <support/xunistd.h> |
36 | #include <sys/uio.h> |
37 | #include <unistd.h> |
38 | |
39 | /* Response builder. */ |
40 | |
41 | enum |
42 | { |
43 | max_response_length = 65536 |
44 | }; |
45 | |
46 | /* List of pointers to be freed. The hash table implementation |
47 | (struct hsearch_data) does not provide a way to deallocate all |
48 | objects, so this approach is used to avoid memory leaks. */ |
49 | struct to_be_freed |
50 | { |
51 | struct to_be_freed *next; |
52 | void *ptr; |
53 | }; |
54 | |
55 | struct resolv_response_builder |
56 | { |
57 | const unsigned char *query_buffer; |
58 | size_t query_length; |
59 | |
60 | size_t offset; /* Bytes written so far in buffer. */ |
61 | ns_sect section; /* Current section in the DNS packet. */ |
62 | unsigned int truncate_bytes; /* Bytes to remove at end of response. */ |
63 | bool drop; /* Discard generated response. */ |
64 | bool close; /* Close TCP client connection. */ |
65 | |
66 | /* Offset of the two-byte RDATA length field in the currently |
67 | written RDATA sub-structure. 0 if no RDATA is being written. */ |
68 | size_t current_rdata_offset; |
69 | |
70 | /* Hash table for locating targets for label compression. */ |
71 | struct hsearch_data compression_offsets; |
72 | /* List of pointers which need to be freed. Used for domain names |
73 | involved in label compression. */ |
74 | struct to_be_freed *to_be_freed; |
75 | |
76 | /* Must be last. Not zeroed for performance reasons. */ |
77 | unsigned char buffer[max_response_length]; |
78 | }; |
79 | |
80 | /* Response builder. */ |
81 | |
82 | /* Add a pointer to the list of pointers to be freed when B is |
83 | deallocated. */ |
84 | static void |
85 | response_push_pointer_to_free (struct resolv_response_builder *b, void *ptr) |
86 | { |
87 | if (ptr == NULL) |
88 | return; |
89 | struct to_be_freed *e = xmalloc (sizeof (*e)); |
90 | *e = (struct to_be_freed) {b->to_be_freed, ptr}; |
91 | b->to_be_freed = e; |
92 | } |
93 | |
94 | void |
95 | resolv_response_init (struct resolv_response_builder *b, |
96 | struct resolv_response_flags flags) |
97 | { |
98 | if (b->offset > 0) |
99 | FAIL_EXIT1 ("response_init: called at offset %zu" , b->offset); |
100 | if (b->query_length < 12) |
101 | FAIL_EXIT1 ("response_init called for a query of size %zu" , |
102 | b->query_length); |
103 | if (flags.rcode > 15) |
104 | FAIL_EXIT1 ("response_init: invalid RCODE %u" , flags.rcode); |
105 | |
106 | /* Copy the transaction ID. */ |
107 | b->buffer[0] = b->query_buffer[0]; |
108 | b->buffer[1] = b->query_buffer[1]; |
109 | |
110 | /* Initialize the flags. */ |
111 | b->buffer[2] = 0x80; /* Mark as response. */ |
112 | b->buffer[2] |= b->query_buffer[2] & 0x01; /* Copy the RD bit. */ |
113 | if (flags.tc) |
114 | b->buffer[2] |= 0x02; |
115 | b->buffer[3] = 0x80 | flags.rcode; /* Always set RA. */ |
116 | |
117 | /* Fill in the initial section count values. */ |
118 | b->buffer[4] = flags.qdcount >> 8; |
119 | b->buffer[5] = flags.qdcount; |
120 | b->buffer[6] = flags.ancount >> 8; |
121 | b->buffer[7] = flags.ancount; |
122 | b->buffer[8] = flags.nscount >> 8; |
123 | b->buffer[9] = flags.nscount; |
124 | b->buffer[10] = flags.adcount >> 8; |
125 | b->buffer[11] = flags.adcount; |
126 | |
127 | b->offset = 12; |
128 | } |
129 | |
130 | void |
131 | resolv_response_section (struct resolv_response_builder *b, ns_sect section) |
132 | { |
133 | if (b->offset == 0) |
134 | FAIL_EXIT1 ("resolv_response_section: response_init not called before" ); |
135 | if (section < b->section) |
136 | FAIL_EXIT1 ("resolv_response_section: cannot go back to previous section" ); |
137 | b->section = section; |
138 | } |
139 | |
140 | /* Add a single byte to B. */ |
141 | static inline void |
142 | response_add_byte (struct resolv_response_builder *b, unsigned char ch) |
143 | { |
144 | if (b->offset == max_response_length) |
145 | FAIL_EXIT1 ("DNS response exceeds 64 KiB limit" ); |
146 | b->buffer[b->offset] = ch; |
147 | ++b->offset; |
148 | } |
149 | |
150 | /* Add a 16-bit word VAL to B, in big-endian format. */ |
151 | static void |
152 | response_add_16 (struct resolv_response_builder *b, uint16_t val) |
153 | { |
154 | response_add_byte (b, val >> 8); |
155 | response_add_byte (b, val); |
156 | } |
157 | |
158 | /* Increment the pers-section record counter in the packet header. */ |
159 | static void |
160 | response_count_increment (struct resolv_response_builder *b) |
161 | { |
162 | unsigned int offset = b->section; |
163 | offset = 4 + 2 * offset; |
164 | ++b->buffer[offset + 1]; |
165 | if (b->buffer[offset + 1] == 0) |
166 | { |
167 | /* Carry. */ |
168 | ++b->buffer[offset]; |
169 | if (b->buffer[offset] == 0) |
170 | /* Overflow. */ |
171 | FAIL_EXIT1 ("too many records in section" ); |
172 | } |
173 | } |
174 | |
175 | void |
176 | resolv_response_add_question (struct resolv_response_builder *b, |
177 | const char *name, uint16_t class, uint16_t type) |
178 | { |
179 | if (b->offset == 0) |
180 | FAIL_EXIT1 ("resolv_response_add_question: " |
181 | "resolv_response_init not called" ); |
182 | if (b->section != ns_s_qd) |
183 | FAIL_EXIT1 ("resolv_response_add_question: " |
184 | "must be called in the question section" ); |
185 | |
186 | resolv_response_add_name (b, name); |
187 | response_add_16 (b, type); |
188 | response_add_16 (b, class); |
189 | |
190 | response_count_increment (b); |
191 | } |
192 | |
193 | void |
194 | resolv_response_add_name (struct resolv_response_builder *b, |
195 | const char *const origname) |
196 | { |
197 | /* Normalized name. */ |
198 | char *name; |
199 | /* Normalized name with case preserved. */ |
200 | char *name_case; |
201 | { |
202 | size_t namelen = strlen (origname); |
203 | /* Remove trailing dots. FIXME: Handle trailing quoted dots. */ |
204 | while (namelen > 0 && origname[namelen - 1] == '.') |
205 | --namelen; |
206 | name = xmalloc (namelen + 1); |
207 | name_case = xmalloc (namelen + 1); |
208 | /* Copy and convert to lowercase. FIXME: This needs to normalize |
209 | escaping as well. */ |
210 | for (size_t i = 0; i < namelen; ++i) |
211 | { |
212 | char ch = origname[i]; |
213 | name_case[i] = ch; |
214 | if ('A' <= ch && ch <= 'Z') |
215 | ch = ch - 'A' + 'a'; |
216 | name[i] = ch; |
217 | } |
218 | name[namelen] = 0; |
219 | name_case[namelen] = 0; |
220 | } |
221 | char *name_start = name; |
222 | char *name_case_start = name_case; |
223 | |
224 | bool compression = false; |
225 | while (*name) |
226 | { |
227 | /* Search for a previous name we can reference. */ |
228 | ENTRY new_entry = |
229 | { |
230 | .key = name, |
231 | .data = (void *) (uintptr_t) b->offset, |
232 | }; |
233 | |
234 | /* If the label can be a compression target because it is at a |
235 | reachable offset, add it to the hash table. */ |
236 | ACTION action; |
237 | if (b->offset < (1 << 12)) |
238 | action = ENTER; |
239 | else |
240 | action = FIND; |
241 | |
242 | /* Search for known compression offsets in the hash table. */ |
243 | ENTRY *e; |
244 | if (hsearch_r (new_entry, action, &e, &b->compression_offsets) == 0) |
245 | { |
246 | if (action == FIND && errno == ESRCH) |
247 | /* Fall through. */ |
248 | e = NULL; |
249 | else |
250 | FAIL_EXIT1 ("hsearch_r failure in name compression: %m" ); |
251 | } |
252 | |
253 | /* The name is known. Reference the previous location. */ |
254 | if (e != NULL && e->data != new_entry.data) |
255 | { |
256 | size_t old_offset = (uintptr_t) e->data; |
257 | response_add_byte (b, 0xC0 | (old_offset >> 8)); |
258 | response_add_byte (b, old_offset); |
259 | compression = true; |
260 | break; |
261 | } |
262 | |
263 | /* The name does not exist yet. Write one label. First, add |
264 | room for the label length. */ |
265 | size_t buffer_label_offset = b->offset; |
266 | response_add_byte (b, 0); |
267 | |
268 | /* Copy the label. */ |
269 | while (true) |
270 | { |
271 | char ch = *name_case; |
272 | if (ch == '\0') |
273 | break; |
274 | ++name; |
275 | ++name_case; |
276 | if (ch == '.') |
277 | break; |
278 | /* FIXME: Handle escaping. */ |
279 | response_add_byte (b, ch); |
280 | } |
281 | |
282 | /* Patch in the label length. */ |
283 | size_t label_length = b->offset - buffer_label_offset - 1; |
284 | if (label_length == 0) |
285 | FAIL_EXIT1 ("empty label in name compression: %s" , origname); |
286 | if (label_length > 63) |
287 | FAIL_EXIT1 ("label too long in name compression: %s" , origname); |
288 | b->buffer[buffer_label_offset] = label_length; |
289 | |
290 | /* Continue with the tail of the name and the next label. */ |
291 | } |
292 | |
293 | if (compression) |
294 | { |
295 | /* If we found an immediate match for the name, we have not put |
296 | it into the hash table, and can free it immediately. */ |
297 | if (name == name_start) |
298 | free (name_start); |
299 | else |
300 | response_push_pointer_to_free (b, name_start); |
301 | } |
302 | else |
303 | { |
304 | /* Terminate the sequence of labels. With compression, this is |
305 | implicit in the compression reference. */ |
306 | response_add_byte (b, 0); |
307 | response_push_pointer_to_free (b, name_start); |
308 | } |
309 | |
310 | free (name_case_start); |
311 | } |
312 | |
313 | void |
314 | resolv_response_open_record (struct resolv_response_builder *b, |
315 | const char *name, |
316 | uint16_t class, uint16_t type, uint32_t ttl) |
317 | { |
318 | if (b->section == ns_s_qd) |
319 | FAIL_EXIT1 ("resolv_response_open_record called in question section" ); |
320 | if (b->current_rdata_offset != 0) |
321 | FAIL_EXIT1 ("resolv_response_open_record called with open record" ); |
322 | |
323 | resolv_response_add_name (b, name); |
324 | response_add_16 (b, type); |
325 | response_add_16 (b, class); |
326 | response_add_16 (b, ttl >> 16); |
327 | response_add_16 (b, ttl); |
328 | |
329 | b->current_rdata_offset = b->offset; |
330 | /* Add room for the RDATA length. */ |
331 | response_add_16 (b, 0); |
332 | } |
333 | |
334 | |
335 | void |
336 | resolv_response_close_record (struct resolv_response_builder *b) |
337 | { |
338 | size_t rdata_offset = b->current_rdata_offset; |
339 | if (rdata_offset == 0) |
340 | FAIL_EXIT1 ("response_close_record called without open record" ); |
341 | size_t rdata_length = b->offset - rdata_offset - 2; |
342 | if (rdata_length > 65535) |
343 | FAIL_EXIT1 ("RDATA length %zu exceeds limit" , rdata_length); |
344 | b->buffer[rdata_offset] = rdata_length >> 8; |
345 | b->buffer[rdata_offset + 1] = rdata_length; |
346 | response_count_increment (b); |
347 | b->current_rdata_offset = 0; |
348 | } |
349 | |
350 | void |
351 | resolv_response_add_data (struct resolv_response_builder *b, |
352 | const void *data, size_t length) |
353 | { |
354 | size_t remaining = max_response_length - b->offset; |
355 | if (remaining < length) |
356 | FAIL_EXIT1 ("resolv_response_add_data: not enough room for %zu bytes" , |
357 | length); |
358 | memcpy (b->buffer + b->offset, data, length); |
359 | b->offset += length; |
360 | } |
361 | |
362 | void |
363 | resolv_response_drop (struct resolv_response_builder *b) |
364 | { |
365 | b->drop = true; |
366 | } |
367 | |
368 | void |
369 | resolv_response_close (struct resolv_response_builder *b) |
370 | { |
371 | b->close = true; |
372 | } |
373 | |
374 | void |
375 | resolv_response_truncate_data (struct resolv_response_builder *b, size_t count) |
376 | { |
377 | if (count > 65535) |
378 | FAIL_EXIT1 ("resolv_response_truncate_data: argument too large: %zu" , |
379 | count); |
380 | b->truncate_bytes = count; |
381 | } |
382 | |
383 | |
384 | size_t |
385 | resolv_response_length (const struct resolv_response_builder *b) |
386 | { |
387 | return b->offset; |
388 | } |
389 | |
390 | unsigned char * |
391 | resolv_response_buffer (const struct resolv_response_builder *b) |
392 | { |
393 | unsigned char *result = xmalloc (b->offset); |
394 | memcpy (result, b->buffer, b->offset); |
395 | return result; |
396 | } |
397 | |
398 | static struct resolv_response_builder * |
399 | response_builder_allocate |
400 | (const unsigned char *query_buffer, size_t query_length) |
401 | { |
402 | struct resolv_response_builder *b = xmalloc (sizeof (*b)); |
403 | memset (b, 0, offsetof (struct resolv_response_builder, buffer)); |
404 | b->query_buffer = query_buffer; |
405 | b->query_length = query_length; |
406 | TEST_VERIFY_EXIT (hcreate_r (10000, &b->compression_offsets) != 0); |
407 | return b; |
408 | } |
409 | |
410 | static void |
411 | response_builder_free (struct resolv_response_builder *b) |
412 | { |
413 | struct to_be_freed *current = b->to_be_freed; |
414 | while (current != NULL) |
415 | { |
416 | struct to_be_freed *next = current->next; |
417 | free (current->ptr); |
418 | free (current); |
419 | current = next; |
420 | } |
421 | hdestroy_r (&b->compression_offsets); |
422 | free (b); |
423 | } |
424 | |
425 | /* DNS query processing. */ |
426 | |
427 | /* Data extracted from the question section of a DNS packet. */ |
428 | struct query_info |
429 | { |
430 | char qname[MAXDNAME]; |
431 | uint16_t qclass; |
432 | uint16_t qtype; |
433 | struct resolv_edns_info edns; |
434 | }; |
435 | |
436 | /* Update *INFO from the specified DNS packet. */ |
437 | static void |
438 | parse_query (struct query_info *info, |
439 | const unsigned char *buffer, size_t length) |
440 | { |
441 | HEADER hd; |
442 | _Static_assert (sizeof (hd) == 12, "DNS header size" ); |
443 | if (length < sizeof (hd)) |
444 | FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes" , length); |
445 | memcpy (&hd, buffer, sizeof (hd)); |
446 | |
447 | if (ntohs (hd.qdcount) != 1) |
448 | FAIL_EXIT1 ("malformed DNS query: wrong question count: %d" , |
449 | (int) ntohs (hd.qdcount)); |
450 | if (ntohs (hd.ancount) != 0) |
451 | FAIL_EXIT1 ("malformed DNS query: wrong answer count: %d" , |
452 | (int) ntohs (hd.ancount)); |
453 | if (ntohs (hd.nscount) != 0) |
454 | FAIL_EXIT1 ("malformed DNS query: wrong authority count: %d" , |
455 | (int) ntohs (hd.nscount)); |
456 | if (ntohs (hd.arcount) > 1) |
457 | FAIL_EXIT1 ("malformed DNS query: wrong additional count: %d" , |
458 | (int) ntohs (hd.arcount)); |
459 | |
460 | int ret = dn_expand (buffer, buffer + length, buffer + sizeof (hd), |
461 | info->qname, sizeof (info->qname)); |
462 | if (ret < 0) |
463 | FAIL_EXIT1 ("malformed DNS query: cannot uncompress QNAME" ); |
464 | |
465 | /* Obtain QTYPE and QCLASS. */ |
466 | size_t remaining = length - (12 + ret); |
467 | struct |
468 | { |
469 | uint16_t qtype; |
470 | uint16_t qclass; |
471 | } qtype_qclass; |
472 | if (remaining < sizeof (qtype_qclass)) |
473 | FAIL_EXIT1 ("malformed DNS query: " |
474 | "query lacks QCLASS/QTYPE, QNAME: %s" , info->qname); |
475 | memcpy (&qtype_qclass, buffer + 12 + ret, sizeof (qtype_qclass)); |
476 | info->qclass = ntohs (qtype_qclass.qclass); |
477 | info->qtype = ntohs (qtype_qclass.qtype); |
478 | |
479 | memset (&info->edns, 0, sizeof (info->edns)); |
480 | if (ntohs (hd.arcount) > 0) |
481 | { |
482 | /* Parse EDNS record. */ |
483 | struct __attribute__ ((packed, aligned (1))) |
484 | { |
485 | uint8_t root; |
486 | uint16_t rtype; |
487 | uint16_t payload; |
488 | uint8_t edns_extended_rcode; |
489 | uint8_t edns_version; |
490 | uint16_t flags; |
491 | uint16_t rdatalen; |
492 | } rr; |
493 | _Static_assert (sizeof (rr) == 11, "EDNS record size" ); |
494 | |
495 | if (remaining < 4 + sizeof (rr)) |
496 | FAIL_EXIT1 ("mailformed DNS query: no room for EDNS record" ); |
497 | memcpy (&rr, buffer + 12 + ret + 4, sizeof (rr)); |
498 | if (rr.root != 0) |
499 | FAIL_EXIT1 ("malformed DNS query: invalid OPT RNAME: %d\n" , rr.root); |
500 | if (rr.rtype != htons (41)) |
501 | FAIL_EXIT1 ("malformed DNS query: invalid OPT type: %d\n" , |
502 | ntohs (rr.rtype)); |
503 | info->edns.active = true; |
504 | info->edns.extended_rcode = rr.edns_extended_rcode; |
505 | info->edns.version = rr.edns_version; |
506 | info->edns.flags = ntohs (rr.flags); |
507 | info->edns.payload_size = ntohs (rr.payload); |
508 | } |
509 | } |
510 | |
511 | |
512 | /* Main testing framework. */ |
513 | |
514 | /* Per-server information. One struct is allocated for each test |
515 | server. */ |
516 | struct resolv_test_server |
517 | { |
518 | /* Local address of the server. UDP and TCP use the same port. */ |
519 | struct sockaddr_in address; |
520 | |
521 | /* File descriptor of the UDP server, or -1 if this server is |
522 | disabled. */ |
523 | int socket_udp; |
524 | |
525 | /* File descriptor of the TCP server, or -1 if this server is |
526 | disabled. */ |
527 | int socket_tcp; |
528 | |
529 | /* Counter of the number of responses processed so far. */ |
530 | size_t response_number; |
531 | |
532 | /* Thread handles for the server threads (if not disabled in the |
533 | configuration). */ |
534 | pthread_t thread_udp; |
535 | pthread_t thread_tcp; |
536 | }; |
537 | |
538 | /* Main struct for keeping track of libresolv redirection and |
539 | testing. */ |
540 | struct resolv_test |
541 | { |
542 | /* After initialization, any access to the struct must be performed |
543 | while this lock is acquired. */ |
544 | pthread_mutex_t lock; |
545 | |
546 | /* Data for each test server. */ |
547 | struct resolv_test_server servers[resolv_max_test_servers]; |
548 | |
549 | /* Used if config.single_thread_udp is true. */ |
550 | pthread_t thread_udp_single; |
551 | |
552 | struct resolv_redirect_config config; |
553 | bool termination_requested; |
554 | }; |
555 | |
556 | /* Function implementing a server thread. */ |
557 | typedef void (*thread_callback) (struct resolv_test *, int server_index); |
558 | |
559 | /* Storage for thread-specific data, for passing to the |
560 | thread_callback function. */ |
561 | struct thread_closure |
562 | { |
563 | struct resolv_test *obj; /* Current test object. */ |
564 | thread_callback callback; /* Function to call. */ |
565 | int server_index; /* Index of the implemented server. */ |
566 | }; |
567 | |
568 | /* Wrap response_callback as a function which can be passed to |
569 | pthread_create. */ |
570 | static void * |
571 | thread_callback_wrapper (void *arg) |
572 | { |
573 | struct thread_closure *closure = arg; |
574 | closure->callback (closure->obj, closure->server_index); |
575 | free (closure); |
576 | return NULL; |
577 | } |
578 | |
579 | /* Start a server thread for the specified SERVER_INDEX, implemented |
580 | by CALLBACK. */ |
581 | static pthread_t |
582 | start_server_thread (struct resolv_test *obj, int server_index, |
583 | thread_callback callback) |
584 | { |
585 | struct thread_closure *closure = xmalloc (sizeof (*closure)); |
586 | *closure = (struct thread_closure) |
587 | { |
588 | .obj = obj, |
589 | .callback = callback, |
590 | .server_index = server_index, |
591 | }; |
592 | return xpthread_create (NULL, thread_callback_wrapper, closure); |
593 | } |
594 | |
595 | /* Process one UDP query. Return false if a termination requested has |
596 | been detected. */ |
597 | static bool |
598 | server_thread_udp_process_one (struct resolv_test *obj, int server_index) |
599 | { |
600 | unsigned char query[512]; |
601 | struct sockaddr_storage peer; |
602 | socklen_t peerlen = sizeof (peer); |
603 | size_t length = xrecvfrom (obj->servers[server_index].socket_udp, |
604 | query, sizeof (query), 0, |
605 | (struct sockaddr *) &peer, &peerlen); |
606 | /* Check for termination. */ |
607 | { |
608 | bool termination_requested; |
609 | xpthread_mutex_lock (&obj->lock); |
610 | termination_requested = obj->termination_requested; |
611 | xpthread_mutex_unlock (&obj->lock); |
612 | if (termination_requested) |
613 | return false; |
614 | } |
615 | |
616 | |
617 | struct query_info qinfo; |
618 | parse_query (&qinfo, query, length); |
619 | if (test_verbose > 0) |
620 | { |
621 | if (test_verbose > 1) |
622 | printf ("info: UDP server %d: incoming query:" |
623 | " %zd bytes, %s/%u/%u, tnxid=0x%02x%02x\n" , |
624 | server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype, |
625 | query[0], query[1]); |
626 | else |
627 | printf ("info: UDP server %d: incoming query:" |
628 | " %zd bytes, %s/%u/%u\n" , |
629 | server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype); |
630 | } |
631 | |
632 | struct resolv_response_context ctx = |
633 | { |
634 | .query_buffer = query, |
635 | .query_length = length, |
636 | .server_index = server_index, |
637 | .tcp = false, |
638 | .edns = qinfo.edns, |
639 | }; |
640 | struct resolv_response_builder *b = response_builder_allocate (query, length); |
641 | obj->config.response_callback |
642 | (&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype); |
643 | |
644 | if (b->drop) |
645 | { |
646 | if (test_verbose) |
647 | printf ("info: UDP server %d: dropping response to %s/%u/%u\n" , |
648 | server_index, qinfo.qname, qinfo.qclass, qinfo.qtype); |
649 | } |
650 | else |
651 | { |
652 | if (test_verbose) |
653 | { |
654 | if (b->offset >= 12) |
655 | printf ("info: UDP server %d: sending response:" |
656 | " %zu bytes, RCODE %d (for %s/%u/%u)\n" , |
657 | server_index, b->offset, b->buffer[3] & 0x0f, |
658 | qinfo.qname, qinfo.qclass, qinfo.qtype); |
659 | else |
660 | printf ("info: UDP server %d: sending response: %zu bytes" |
661 | " (for %s/%u/%u)\n" , |
662 | server_index, b->offset, |
663 | qinfo.qname, qinfo.qclass, qinfo.qtype); |
664 | if (b->truncate_bytes > 0) |
665 | printf ("info: truncated by %u bytes\n" , b->truncate_bytes); |
666 | } |
667 | size_t to_send = b->offset; |
668 | if (to_send < b->truncate_bytes) |
669 | to_send = 0; |
670 | else |
671 | to_send -= b->truncate_bytes; |
672 | |
673 | /* Ignore most errors here because the other end may have closed |
674 | the socket. */ |
675 | if (sendto (obj->servers[server_index].socket_udp, |
676 | b->buffer, to_send, 0, |
677 | (struct sockaddr *) &peer, peerlen) < 0) |
678 | TEST_VERIFY_EXIT (errno != EBADF); |
679 | } |
680 | response_builder_free (b); |
681 | return true; |
682 | } |
683 | |
684 | /* UDP thread_callback function. Variant for one thread per |
685 | server. */ |
686 | static void |
687 | server_thread_udp (struct resolv_test *obj, int server_index) |
688 | { |
689 | while (server_thread_udp_process_one (obj, server_index)) |
690 | ; |
691 | } |
692 | |
693 | /* Single-threaded UDP processing function, for the single_thread_udp |
694 | case. */ |
695 | static void * |
696 | server_thread_udp_single (void *closure) |
697 | { |
698 | struct resolv_test *obj = closure; |
699 | |
700 | struct pollfd fds[resolv_max_test_servers]; |
701 | for (int server_index = 0; server_index < resolv_max_test_servers; |
702 | ++server_index) |
703 | if (obj->config.servers[server_index].disable_udp) |
704 | fds[server_index] = (struct pollfd) {.fd = -1}; |
705 | else |
706 | { |
707 | fds[server_index] = (struct pollfd) |
708 | { |
709 | .fd = obj->servers[server_index].socket_udp, |
710 | .events = POLLIN |
711 | }; |
712 | |
713 | /* Make the socket non-blocking. */ |
714 | int flags = fcntl (obj->servers[server_index].socket_udp, F_GETFL, 0); |
715 | if (flags < 0) |
716 | FAIL_EXIT1 ("fcntl (F_GETFL): %m" ); |
717 | flags |= O_NONBLOCK; |
718 | if (fcntl (obj->servers[server_index].socket_udp, F_SETFL, flags) < 0) |
719 | FAIL_EXIT1 ("fcntl (F_SETFL): %m" ); |
720 | } |
721 | |
722 | while (true) |
723 | { |
724 | xpoll (fds, resolv_max_test_servers, -1); |
725 | for (int server_index = 0; server_index < resolv_max_test_servers; |
726 | ++server_index) |
727 | if (fds[server_index].revents != 0) |
728 | { |
729 | if (!server_thread_udp_process_one (obj, server_index)) |
730 | goto out; |
731 | fds[server_index].revents = 0; |
732 | } |
733 | } |
734 | |
735 | out: |
736 | return NULL; |
737 | } |
738 | |
739 | /* Start the single UDP handler thread (for the single_thread_udp |
740 | case). */ |
741 | static void |
742 | start_server_thread_udp_single (struct resolv_test *obj) |
743 | { |
744 | obj->thread_udp_single |
745 | = xpthread_create (NULL, server_thread_udp_single, obj); |
746 | } |
747 | |
748 | /* Data describing a TCP client connect. */ |
749 | struct tcp_thread_closure |
750 | { |
751 | struct resolv_test *obj; |
752 | int server_index; |
753 | int client_socket; |
754 | }; |
755 | |
756 | /* Read a complete DNS query packet. If EOF_OK, an immediate |
757 | end-of-file condition is acceptable. */ |
758 | static bool |
759 | read_fully (int fd, void *buf, size_t len, bool eof_ok) |
760 | { |
761 | const void *const end = buf + len; |
762 | while (buf < end) |
763 | { |
764 | ssize_t ret = read (fd, buf, end - buf); |
765 | if (ret == 0) |
766 | { |
767 | if (!eof_ok) |
768 | { |
769 | support_record_failure (); |
770 | printf ("error: unexpected EOF on TCP connection\n" ); |
771 | } |
772 | return false; |
773 | } |
774 | else if (ret < 0) |
775 | { |
776 | if (!eof_ok || errno != ECONNRESET) |
777 | { |
778 | support_record_failure (); |
779 | printf ("error: TCP read: %m\n" ); |
780 | } |
781 | return false; |
782 | } |
783 | buf += ret; |
784 | eof_ok = false; |
785 | } |
786 | return true; |
787 | } |
788 | |
789 | /* Write an array of iovecs. Terminate the process on failure. */ |
790 | static void |
791 | writev_fully (int fd, struct iovec *buffers, size_t count) |
792 | { |
793 | while (count > 0) |
794 | { |
795 | /* Skip zero-length write requests. */ |
796 | if (buffers->iov_len == 0) |
797 | { |
798 | ++buffers; |
799 | --count; |
800 | continue; |
801 | } |
802 | /* Try to rewrite the remaing buffers. */ |
803 | ssize_t ret = writev (fd, buffers, count); |
804 | if (ret < 0) |
805 | FAIL_EXIT1 ("writev: %m" ); |
806 | if (ret == 0) |
807 | FAIL_EXIT1 ("writev: invalid return value zero" ); |
808 | /* Find the buffers that were successfully written. */ |
809 | while (ret > 0) |
810 | { |
811 | if (count == 0) |
812 | FAIL_EXIT1 ("internal writev consistency failure" ); |
813 | /* Current buffer was partially written. */ |
814 | if (buffers->iov_len > (size_t) ret) |
815 | { |
816 | buffers->iov_base += ret; |
817 | buffers->iov_len -= ret; |
818 | ret = 0; |
819 | } |
820 | else |
821 | { |
822 | ret -= buffers->iov_len; |
823 | buffers->iov_len = 0; |
824 | ++buffers; |
825 | --count; |
826 | } |
827 | } |
828 | } |
829 | } |
830 | |
831 | /* Thread callback for handling a single established TCP connection to |
832 | a client. */ |
833 | static void * |
834 | server_thread_tcp_client (void *arg) |
835 | { |
836 | struct tcp_thread_closure *closure = arg; |
837 | |
838 | while (true) |
839 | { |
840 | /* Read packet length. */ |
841 | uint16_t query_length; |
842 | if (!read_fully (closure->client_socket, |
843 | &query_length, sizeof (query_length), true)) |
844 | break; |
845 | query_length = ntohs (query_length); |
846 | |
847 | /* Read the packet. */ |
848 | unsigned char *query_buffer = xmalloc (query_length); |
849 | read_fully (closure->client_socket, query_buffer, query_length, false); |
850 | |
851 | struct query_info qinfo; |
852 | parse_query (&qinfo, query_buffer, query_length); |
853 | if (test_verbose > 0) |
854 | { |
855 | if (test_verbose > 1) |
856 | printf ("info: UDP server %d: incoming query:" |
857 | " %d bytes, %s/%u/%u, tnxid=0x%02x%02x\n" , |
858 | closure->server_index, query_length, |
859 | qinfo.qname, qinfo.qclass, qinfo.qtype, |
860 | query_buffer[0], query_buffer[1]); |
861 | else |
862 | printf ("info: TCP server %d: incoming query:" |
863 | " %u bytes, %s/%u/%u\n" , |
864 | closure->server_index, query_length, |
865 | qinfo.qname, qinfo.qclass, qinfo.qtype); |
866 | } |
867 | |
868 | struct resolv_response_context ctx = |
869 | { |
870 | .query_buffer = query_buffer, |
871 | .query_length = query_length, |
872 | .server_index = closure->server_index, |
873 | .tcp = true, |
874 | .edns = qinfo.edns, |
875 | }; |
876 | struct resolv_response_builder *b = response_builder_allocate |
877 | (query_buffer, query_length); |
878 | closure->obj->config.response_callback |
879 | (&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype); |
880 | |
881 | if (b->drop) |
882 | { |
883 | if (test_verbose) |
884 | printf ("info: TCP server %d: dropping response to %s/%u/%u\n" , |
885 | closure->server_index, |
886 | qinfo.qname, qinfo.qclass, qinfo.qtype); |
887 | } |
888 | else |
889 | { |
890 | if (test_verbose) |
891 | printf ("info: TCP server %d: sending response: %zu bytes" |
892 | " (for %s/%u/%u)\n" , |
893 | closure->server_index, b->offset, |
894 | qinfo.qname, qinfo.qclass, qinfo.qtype); |
895 | uint16_t length = htons (b->offset); |
896 | size_t to_send = b->offset; |
897 | if (to_send < b->truncate_bytes) |
898 | to_send = 0; |
899 | else |
900 | to_send -= b->truncate_bytes; |
901 | struct iovec buffers[2] = |
902 | { |
903 | {&length, sizeof (length)}, |
904 | {b->buffer, to_send} |
905 | }; |
906 | writev_fully (closure->client_socket, buffers, 2); |
907 | } |
908 | bool close_flag = b->close; |
909 | response_builder_free (b); |
910 | free (query_buffer); |
911 | if (close_flag) |
912 | break; |
913 | } |
914 | |
915 | xclose (closure->client_socket); |
916 | free (closure); |
917 | return NULL; |
918 | } |
919 | |
920 | /* thread_callback for the TCP case. Accept connections and create a |
921 | new thread for each client. */ |
922 | static void |
923 | server_thread_tcp (struct resolv_test *obj, int server_index) |
924 | { |
925 | while (true) |
926 | { |
927 | /* Get the client conenction. */ |
928 | int client_socket = xaccept |
929 | (obj->servers[server_index].socket_tcp, NULL, NULL); |
930 | |
931 | /* Check for termination. */ |
932 | xpthread_mutex_lock (&obj->lock); |
933 | if (obj->termination_requested) |
934 | { |
935 | xpthread_mutex_unlock (&obj->lock); |
936 | xclose (client_socket); |
937 | break; |
938 | } |
939 | xpthread_mutex_unlock (&obj->lock); |
940 | |
941 | /* Spawn a new thread for handling this connection. */ |
942 | struct tcp_thread_closure *closure = xmalloc (sizeof (*closure)); |
943 | *closure = (struct tcp_thread_closure) |
944 | { |
945 | .obj = obj, |
946 | .server_index = server_index, |
947 | .client_socket = client_socket, |
948 | }; |
949 | |
950 | pthread_t thr |
951 | = xpthread_create (NULL, server_thread_tcp_client, closure); |
952 | /* TODO: We should keep track of this thread so that we can |
953 | block in resolv_test_end until it has exited. */ |
954 | xpthread_detach (thr); |
955 | } |
956 | } |
957 | |
958 | /* Create UDP and TCP server sockets. */ |
959 | static void |
960 | make_server_sockets (struct resolv_test_server *server) |
961 | { |
962 | while (true) |
963 | { |
964 | server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
965 | server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP); |
966 | |
967 | /* Pick the address for the UDP socket. */ |
968 | server->address = (struct sockaddr_in) |
969 | { |
970 | .sin_family = AF_INET, |
971 | .sin_addr = {.s_addr = htonl (INADDR_LOOPBACK)} |
972 | }; |
973 | xbind (server->socket_udp, |
974 | (struct sockaddr *)&server->address, sizeof (server->address)); |
975 | |
976 | /* Retrieve the address. */ |
977 | socklen_t addrlen = sizeof (server->address); |
978 | xgetsockname (server->socket_udp, |
979 | (struct sockaddr *)&server->address, &addrlen); |
980 | |
981 | /* Bind the TCP socket to the same address. */ |
982 | { |
983 | int on = 1; |
984 | xsetsockopt (server->socket_tcp, SOL_SOCKET, SO_REUSEADDR, |
985 | &on, sizeof (on)); |
986 | } |
987 | if (bind (server->socket_tcp, |
988 | (struct sockaddr *)&server->address, |
989 | sizeof (server->address)) != 0) |
990 | { |
991 | /* Port collision. The UDP bind succeeded, but the TCP BIND |
992 | failed. We assume here that the kernel will pick the |
993 | next local UDP address randomly. */ |
994 | if (errno == EADDRINUSE) |
995 | { |
996 | xclose (server->socket_udp); |
997 | xclose (server->socket_tcp); |
998 | continue; |
999 | } |
1000 | FAIL_EXIT1 ("TCP bind: %m" ); |
1001 | } |
1002 | xlisten (server->socket_tcp, 5); |
1003 | break; |
1004 | } |
1005 | } |
1006 | |
1007 | /* One-time initialization of NSS. */ |
1008 | static void |
1009 | resolv_redirect_once (void) |
1010 | { |
1011 | /* Only use nss_dns. */ |
1012 | __nss_configure_lookup ("hosts" , "dns" ); |
1013 | __nss_configure_lookup ("networks" , "dns" ); |
1014 | /* Enter a network namespace for isolation and firewall state |
1015 | cleanup. The tests will still work if these steps fail, but they |
1016 | may be less reliable. */ |
1017 | support_become_root (); |
1018 | support_enter_network_namespace (); |
1019 | } |
1020 | pthread_once_t resolv_redirect_once_var = PTHREAD_ONCE_INIT; |
1021 | |
1022 | void |
1023 | resolv_test_init (void) |
1024 | { |
1025 | /* Perform one-time initialization of NSS. */ |
1026 | xpthread_once (&resolv_redirect_once_var, resolv_redirect_once); |
1027 | } |
1028 | |
1029 | /* Copy the search path from CONFIG.search to the _res object. */ |
1030 | static void |
1031 | set_search_path (struct resolv_redirect_config config) |
1032 | { |
1033 | memset (_res.defdname, 0, sizeof (_res.defdname)); |
1034 | memset (_res.dnsrch, 0, sizeof (_res.dnsrch)); |
1035 | |
1036 | char *current = _res.defdname; |
1037 | char *end = current + sizeof (_res.defdname); |
1038 | |
1039 | for (unsigned int i = 0; |
1040 | i < sizeof (config.search) / sizeof (config.search[0]); ++i) |
1041 | { |
1042 | if (config.search[i] == NULL) |
1043 | continue; |
1044 | |
1045 | size_t length = strlen (config.search[i]) + 1; |
1046 | size_t remaining = end - current; |
1047 | TEST_VERIFY_EXIT (length <= remaining); |
1048 | memcpy (current, config.search[i], length); |
1049 | _res.dnsrch[i] = current; |
1050 | current += length; |
1051 | } |
1052 | } |
1053 | |
1054 | struct resolv_test * |
1055 | resolv_test_start (struct resolv_redirect_config config) |
1056 | { |
1057 | /* Apply configuration defaults. */ |
1058 | if (config.nscount == 0) |
1059 | config.nscount = resolv_max_test_servers; |
1060 | |
1061 | struct resolv_test *obj = xmalloc (sizeof (*obj)); |
1062 | *obj = (struct resolv_test) { |
1063 | .config = config, |
1064 | .lock = PTHREAD_MUTEX_INITIALIZER, |
1065 | }; |
1066 | |
1067 | resolv_test_init (); |
1068 | |
1069 | /* Create all the servers, to reserve the necessary ports. */ |
1070 | for (int server_index = 0; server_index < config.nscount; ++server_index) |
1071 | make_server_sockets (obj->servers + server_index); |
1072 | |
1073 | /* Start server threads. Disable the server ports, as |
1074 | requested. */ |
1075 | for (int server_index = 0; server_index < config.nscount; ++server_index) |
1076 | { |
1077 | struct resolv_test_server *server = obj->servers + server_index; |
1078 | if (config.servers[server_index].disable_udp) |
1079 | { |
1080 | xclose (server->socket_udp); |
1081 | server->socket_udp = -1; |
1082 | } |
1083 | else if (!config.single_thread_udp) |
1084 | server->thread_udp = start_server_thread (obj, server_index, |
1085 | server_thread_udp); |
1086 | if (config.servers[server_index].disable_tcp) |
1087 | { |
1088 | xclose (server->socket_tcp); |
1089 | server->socket_tcp = -1; |
1090 | } |
1091 | else |
1092 | server->thread_tcp = start_server_thread (obj, server_index, |
1093 | server_thread_tcp); |
1094 | } |
1095 | if (config.single_thread_udp) |
1096 | start_server_thread_udp_single (obj); |
1097 | |
1098 | int timeout = 1; |
1099 | |
1100 | /* Initialize libresolv. */ |
1101 | TEST_VERIFY_EXIT (res_init () == 0); |
1102 | |
1103 | /* Disable IPv6 name server addresses. The code below only |
1104 | overrides the IPv4 addresses. */ |
1105 | __res_iclose (&_res, true); |
1106 | _res._u._ext.nscount = 0; |
1107 | |
1108 | /* Redirect queries to the server socket. */ |
1109 | if (test_verbose) |
1110 | { |
1111 | printf ("info: old timeout value: %d\n" , _res.retrans); |
1112 | printf ("info: old retry attempt value: %d\n" , _res.retry); |
1113 | printf ("info: old _res.options: 0x%lx\n" , _res.options); |
1114 | printf ("info: old _res.nscount value: %d\n" , _res.nscount); |
1115 | printf ("info: old _res.ndots value: %d\n" , _res.ndots); |
1116 | } |
1117 | _res.retrans = timeout; |
1118 | _res.retry = 4; |
1119 | _res.nscount = config.nscount; |
1120 | _res.options = RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH; |
1121 | _res.ndots = 1; |
1122 | if (test_verbose) |
1123 | { |
1124 | printf ("info: new timeout value: %d\n" , _res.retrans); |
1125 | printf ("info: new retry attempt value: %d\n" , _res.retry); |
1126 | printf ("info: new _res.options: 0x%lx\n" , _res.options); |
1127 | printf ("info: new _res.nscount value: %d\n" , _res.nscount); |
1128 | printf ("info: new _res.ndots value: %d\n" , _res.ndots); |
1129 | } |
1130 | for (int server_index = 0; server_index < config.nscount; ++server_index) |
1131 | { |
1132 | _res.nsaddr_list[server_index] = obj->servers[server_index].address; |
1133 | if (test_verbose) |
1134 | { |
1135 | char buf[256]; |
1136 | TEST_VERIFY_EXIT |
1137 | (inet_ntop (AF_INET, &obj->servers[server_index].address.sin_addr, |
1138 | buf, sizeof (buf)) != NULL); |
1139 | printf ("info: server %d: %s/%u\n" , |
1140 | server_index, buf, |
1141 | htons (obj->servers[server_index].address.sin_port)); |
1142 | } |
1143 | } |
1144 | |
1145 | set_search_path (config); |
1146 | |
1147 | return obj; |
1148 | } |
1149 | |
1150 | void |
1151 | resolv_test_end (struct resolv_test *obj) |
1152 | { |
1153 | res_close (); |
1154 | |
1155 | xpthread_mutex_lock (&obj->lock); |
1156 | obj->termination_requested = true; |
1157 | xpthread_mutex_unlock (&obj->lock); |
1158 | |
1159 | /* Send trigger packets to unblock the server threads. */ |
1160 | for (int server_index = 0; server_index < obj->config.nscount; |
1161 | ++server_index) |
1162 | { |
1163 | if (!obj->config.servers[server_index].disable_udp) |
1164 | { |
1165 | int sock = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
1166 | xsendto (sock, "" , 1, 0, |
1167 | (struct sockaddr *) &obj->servers[server_index].address, |
1168 | sizeof (obj->servers[server_index].address)); |
1169 | xclose (sock); |
1170 | } |
1171 | if (!obj->config.servers[server_index].disable_tcp) |
1172 | { |
1173 | int sock = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP); |
1174 | xconnect (sock, |
1175 | (struct sockaddr *) &obj->servers[server_index].address, |
1176 | sizeof (obj->servers[server_index].address)); |
1177 | xclose (sock); |
1178 | } |
1179 | } |
1180 | |
1181 | if (obj->config.single_thread_udp) |
1182 | xpthread_join (obj->thread_udp_single); |
1183 | |
1184 | /* Wait for the server threads to terminate. */ |
1185 | for (int server_index = 0; server_index < obj->config.nscount; |
1186 | ++server_index) |
1187 | { |
1188 | if (!obj->config.servers[server_index].disable_udp) |
1189 | { |
1190 | if (!obj->config.single_thread_udp) |
1191 | xpthread_join (obj->servers[server_index].thread_udp); |
1192 | xclose (obj->servers[server_index].socket_udp); |
1193 | } |
1194 | if (!obj->config.servers[server_index].disable_tcp) |
1195 | { |
1196 | xpthread_join (obj->servers[server_index].thread_tcp); |
1197 | xclose (obj->servers[server_index].socket_tcp); |
1198 | } |
1199 | } |
1200 | |
1201 | free (obj); |
1202 | } |
1203 | |