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