]>
Commit | Line | Data |
---|---|---|
5840c75c | 1 | /* DNS test framework and libresolv redirection. |
688903eb | 2 | Copyright (C) 2016-2018 Free Software Foundation, Inc. |
5840c75c 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 <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> | |
ed3ea040 | 35 | #include <support/xunistd.h> |
9fe3c80c | 36 | #include <sys/uio.h> |
5840c75c FW |
37 | #include <unistd.h> |
38 | ||
ed3ea040 | 39 | /* Response builder. */ |
5840c75c FW |
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; | |
e14a2772 | 433 | struct resolv_edns_info edns; |
5840c75c FW |
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 | { | |
e14a2772 FW |
441 | HEADER hd; |
442 | _Static_assert (sizeof (hd) == 12, "DNS header size"); | |
443 | if (length < sizeof (hd)) | |
5840c75c | 444 | FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length); |
e14a2772 FW |
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), | |
5840c75c FW |
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); | |
e14a2772 FW |
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 | } | |
5840c75c FW |
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); | |
5af1e931 | 603 | size_t length = xrecvfrom (obj->servers[server_index].socket_udp, |
5840c75c FW |
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, | |
e14a2772 | 638 | .edns = qinfo.edns, |
5840c75c FW |
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, | |
e14a2772 | 874 | .edns = qinfo.edns, |
5840c75c FW |
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 | ||
ed3ea040 | 915 | xclose (closure->client_socket); |
5840c75c FW |
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); | |
ed3ea040 | 936 | xclose (client_socket); |
5840c75c FW |
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 | { | |
ed3ea040 FW |
996 | xclose (server->socket_udp); |
997 | xclose (server->socket_tcp); | |
5840c75c FW |
998 | continue; |
999 | } | |
1000 | FAIL_EXIT1 ("TCP bind: %m"); | |
1001 | } | |
1002 | xlisten (server->socket_tcp, 5); | |
1003 | break; | |
1004 | } | |
1005 | } | |
1006 | ||
cb3c27e8 FW |
1007 | /* Like make_server_sockets, but the caller supplies the address to |
1008 | use. */ | |
1009 | static void | |
1010 | make_server_sockets_for_address (struct resolv_test_server *server, | |
1011 | const struct sockaddr *addr) | |
1012 | { | |
1013 | server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
1014 | server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
1015 | ||
1016 | if (addr->sa_family == AF_INET) | |
1017 | server->address = *(const struct sockaddr_in *) addr; | |
1018 | else | |
1019 | /* We cannot store the server address in the socket. This should | |
1020 | not matter if disable_redirect is used. */ | |
1021 | server->address = (struct sockaddr_in) { .sin_family = 0, }; | |
1022 | ||
1023 | xbind (server->socket_udp, | |
1024 | (struct sockaddr *)&server->address, sizeof (server->address)); | |
1025 | xbind (server->socket_tcp, | |
1026 | (struct sockaddr *)&server->address, sizeof (server->address)); | |
1027 | xlisten (server->socket_tcp, 5); | |
1028 | } | |
1029 | ||
5840c75c FW |
1030 | /* One-time initialization of NSS. */ |
1031 | static void | |
1032 | resolv_redirect_once (void) | |
1033 | { | |
1034 | /* Only use nss_dns. */ | |
1035 | __nss_configure_lookup ("hosts", "dns"); | |
1036 | __nss_configure_lookup ("networks", "dns"); | |
1037 | /* Enter a network namespace for isolation and firewall state | |
1038 | cleanup. The tests will still work if these steps fail, but they | |
1039 | may be less reliable. */ | |
1040 | support_become_root (); | |
1041 | support_enter_network_namespace (); | |
1042 | } | |
1043 | pthread_once_t resolv_redirect_once_var = PTHREAD_ONCE_INIT; | |
1044 | ||
1045 | void | |
1046 | resolv_test_init (void) | |
1047 | { | |
1048 | /* Perform one-time initialization of NSS. */ | |
1049 | xpthread_once (&resolv_redirect_once_var, resolv_redirect_once); | |
1050 | } | |
1051 | ||
1052 | /* Copy the search path from CONFIG.search to the _res object. */ | |
1053 | static void | |
1054 | set_search_path (struct resolv_redirect_config config) | |
1055 | { | |
1056 | memset (_res.defdname, 0, sizeof (_res.defdname)); | |
1057 | memset (_res.dnsrch, 0, sizeof (_res.dnsrch)); | |
1058 | ||
1059 | char *current = _res.defdname; | |
1060 | char *end = current + sizeof (_res.defdname); | |
1061 | ||
1062 | for (unsigned int i = 0; | |
1063 | i < sizeof (config.search) / sizeof (config.search[0]); ++i) | |
1064 | { | |
1065 | if (config.search[i] == NULL) | |
1066 | continue; | |
1067 | ||
1068 | size_t length = strlen (config.search[i]) + 1; | |
1069 | size_t remaining = end - current; | |
1070 | TEST_VERIFY_EXIT (length <= remaining); | |
1071 | memcpy (current, config.search[i], length); | |
1072 | _res.dnsrch[i] = current; | |
1073 | current += length; | |
1074 | } | |
1075 | } | |
1076 | ||
1077 | struct resolv_test * | |
1078 | resolv_test_start (struct resolv_redirect_config config) | |
1079 | { | |
1080 | /* Apply configuration defaults. */ | |
1081 | if (config.nscount == 0) | |
1082 | config.nscount = resolv_max_test_servers; | |
1083 | ||
1084 | struct resolv_test *obj = xmalloc (sizeof (*obj)); | |
1085 | *obj = (struct resolv_test) { | |
1086 | .config = config, | |
1087 | .lock = PTHREAD_MUTEX_INITIALIZER, | |
1088 | }; | |
1089 | ||
cb3c27e8 FW |
1090 | if (!config.disable_redirect) |
1091 | resolv_test_init (); | |
5840c75c FW |
1092 | |
1093 | /* Create all the servers, to reserve the necessary ports. */ | |
1094 | for (int server_index = 0; server_index < config.nscount; ++server_index) | |
cb3c27e8 FW |
1095 | if (config.disable_redirect && config.server_address_overrides != NULL) |
1096 | make_server_sockets_for_address | |
1097 | (obj->servers + server_index, | |
1098 | config.server_address_overrides[server_index]); | |
1099 | else | |
1100 | make_server_sockets (obj->servers + server_index); | |
5840c75c FW |
1101 | |
1102 | /* Start server threads. Disable the server ports, as | |
1103 | requested. */ | |
1104 | for (int server_index = 0; server_index < config.nscount; ++server_index) | |
1105 | { | |
1106 | struct resolv_test_server *server = obj->servers + server_index; | |
1107 | if (config.servers[server_index].disable_udp) | |
1108 | { | |
ed3ea040 | 1109 | xclose (server->socket_udp); |
5840c75c FW |
1110 | server->socket_udp = -1; |
1111 | } | |
1112 | else if (!config.single_thread_udp) | |
1113 | server->thread_udp = start_server_thread (obj, server_index, | |
1114 | server_thread_udp); | |
1115 | if (config.servers[server_index].disable_tcp) | |
1116 | { | |
ed3ea040 | 1117 | xclose (server->socket_tcp); |
5840c75c FW |
1118 | server->socket_tcp = -1; |
1119 | } | |
1120 | else | |
1121 | server->thread_tcp = start_server_thread (obj, server_index, | |
1122 | server_thread_tcp); | |
1123 | } | |
1124 | if (config.single_thread_udp) | |
1125 | start_server_thread_udp_single (obj); | |
1126 | ||
cb3c27e8 FW |
1127 | if (config.disable_redirect) |
1128 | return obj; | |
1129 | ||
5840c75c FW |
1130 | int timeout = 1; |
1131 | ||
1132 | /* Initialize libresolv. */ | |
1133 | TEST_VERIFY_EXIT (res_init () == 0); | |
1134 | ||
1135 | /* Disable IPv6 name server addresses. The code below only | |
1136 | overrides the IPv4 addresses. */ | |
1137 | __res_iclose (&_res, true); | |
1138 | _res._u._ext.nscount = 0; | |
1139 | ||
1140 | /* Redirect queries to the server socket. */ | |
1141 | if (test_verbose) | |
1142 | { | |
1143 | printf ("info: old timeout value: %d\n", _res.retrans); | |
1144 | printf ("info: old retry attempt value: %d\n", _res.retry); | |
1145 | printf ("info: old _res.options: 0x%lx\n", _res.options); | |
1146 | printf ("info: old _res.nscount value: %d\n", _res.nscount); | |
1147 | printf ("info: old _res.ndots value: %d\n", _res.ndots); | |
1148 | } | |
1149 | _res.retrans = timeout; | |
1150 | _res.retry = 4; | |
1151 | _res.nscount = config.nscount; | |
1152 | _res.options = RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH; | |
1153 | _res.ndots = 1; | |
1154 | if (test_verbose) | |
1155 | { | |
1156 | printf ("info: new timeout value: %d\n", _res.retrans); | |
1157 | printf ("info: new retry attempt value: %d\n", _res.retry); | |
1158 | printf ("info: new _res.options: 0x%lx\n", _res.options); | |
1159 | printf ("info: new _res.nscount value: %d\n", _res.nscount); | |
1160 | printf ("info: new _res.ndots value: %d\n", _res.ndots); | |
1161 | } | |
1162 | for (int server_index = 0; server_index < config.nscount; ++server_index) | |
1163 | { | |
cb3c27e8 | 1164 | TEST_VERIFY_EXIT (obj->servers[server_index].address.sin_port != 0); |
5840c75c FW |
1165 | _res.nsaddr_list[server_index] = obj->servers[server_index].address; |
1166 | if (test_verbose) | |
1167 | { | |
1168 | char buf[256]; | |
1169 | TEST_VERIFY_EXIT | |
1170 | (inet_ntop (AF_INET, &obj->servers[server_index].address.sin_addr, | |
1171 | buf, sizeof (buf)) != NULL); | |
1172 | printf ("info: server %d: %s/%u\n", | |
1173 | server_index, buf, | |
1174 | htons (obj->servers[server_index].address.sin_port)); | |
1175 | } | |
1176 | } | |
1177 | ||
1178 | set_search_path (config); | |
1179 | ||
1180 | return obj; | |
1181 | } | |
1182 | ||
1183 | void | |
1184 | resolv_test_end (struct resolv_test *obj) | |
1185 | { | |
1186 | res_close (); | |
1187 | ||
1188 | xpthread_mutex_lock (&obj->lock); | |
1189 | obj->termination_requested = true; | |
1190 | xpthread_mutex_unlock (&obj->lock); | |
1191 | ||
1192 | /* Send trigger packets to unblock the server threads. */ | |
1193 | for (int server_index = 0; server_index < obj->config.nscount; | |
1194 | ++server_index) | |
1195 | { | |
1196 | if (!obj->config.servers[server_index].disable_udp) | |
1197 | { | |
1198 | int sock = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
1199 | xsendto (sock, "", 1, 0, | |
1200 | (struct sockaddr *) &obj->servers[server_index].address, | |
1201 | sizeof (obj->servers[server_index].address)); | |
ed3ea040 | 1202 | xclose (sock); |
5840c75c FW |
1203 | } |
1204 | if (!obj->config.servers[server_index].disable_tcp) | |
1205 | { | |
1206 | int sock = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
1207 | xconnect (sock, | |
1208 | (struct sockaddr *) &obj->servers[server_index].address, | |
1209 | sizeof (obj->servers[server_index].address)); | |
ed3ea040 | 1210 | xclose (sock); |
5840c75c FW |
1211 | } |
1212 | } | |
1213 | ||
1214 | if (obj->config.single_thread_udp) | |
1215 | xpthread_join (obj->thread_udp_single); | |
1216 | ||
1217 | /* Wait for the server threads to terminate. */ | |
1218 | for (int server_index = 0; server_index < obj->config.nscount; | |
1219 | ++server_index) | |
1220 | { | |
1221 | if (!obj->config.servers[server_index].disable_udp) | |
1222 | { | |
1223 | if (!obj->config.single_thread_udp) | |
1224 | xpthread_join (obj->servers[server_index].thread_udp); | |
ed3ea040 | 1225 | xclose (obj->servers[server_index].socket_udp); |
5840c75c FW |
1226 | } |
1227 | if (!obj->config.servers[server_index].disable_tcp) | |
1228 | { | |
1229 | xpthread_join (obj->servers[server_index].thread_tcp); | |
ed3ea040 | 1230 | xclose (obj->servers[server_index].socket_tcp); |
5840c75c FW |
1231 | } |
1232 | } | |
1233 | ||
1234 | free (obj); | |
1235 | } |