1 | /* Test basic nss_dns functionality with multiple threads. |
2 | Copyright (C) 2016-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* Unlike tst-resolv-basic, this test does not overwrite the _res |
20 | structure and relies on namespaces to achieve the redirection to |
21 | the test servers with a custom /etc/resolv.conf file. */ |
22 | |
23 | #include <dlfcn.h> |
24 | #include <errno.h> |
25 | #include <gnu/lib-names.h> |
26 | #include <netdb.h> |
27 | #include <resolv/resolv-internal.h> |
28 | #include <resolv/resolv_context.h> |
29 | #include <stdio.h> |
30 | #include <stdlib.h> |
31 | #include <string.h> |
32 | #include <support/check.h> |
33 | #include <support/namespace.h> |
34 | #include <support/resolv_test.h> |
35 | #include <support/support.h> |
36 | #include <support/temp_file.h> |
37 | #include <support/test-driver.h> |
38 | #include <support/xthread.h> |
39 | #include <support/xunistd.h> |
40 | |
41 | /* Each client thread sends this many queries. */ |
42 | enum { queries_per_thread = 500 }; |
43 | |
44 | /* Return a small positive number identifying this thread. */ |
45 | static int |
46 | get_thread_number (void) |
47 | { |
48 | static int __thread local; |
49 | if (local != 0) |
50 | return local; |
51 | static int global = 1; |
52 | local = __atomic_fetch_add (&global, 1, __ATOMIC_RELAXED); |
53 | return local; |
54 | } |
55 | |
56 | static void |
57 | response (const struct resolv_response_context *ctx, |
58 | struct resolv_response_builder *b, |
59 | const char *qname, uint16_t qclass, uint16_t qtype) |
60 | { |
61 | TEST_VERIFY_EXIT (qname != NULL); |
62 | |
63 | int counter = 0; |
64 | int thread = 0; |
65 | int dummy = 0; |
66 | TEST_VERIFY (sscanf (qname, "counter%d.thread%d.example.com%n" , |
67 | &counter, &thread, &dummy) == 2); |
68 | TEST_VERIFY (dummy > 0); |
69 | |
70 | struct resolv_response_flags flags = { 0 }; |
71 | resolv_response_init (b, flags); |
72 | resolv_response_add_question (b, name: qname, class: qclass, type: qtype); |
73 | |
74 | resolv_response_section (b, ns_s_an); |
75 | resolv_response_open_record (b, name: qname, class: qclass, type: qtype, ttl: 0); |
76 | switch (qtype) |
77 | { |
78 | case T_A: |
79 | { |
80 | char ipv4[4] = {10, 0, counter, thread}; |
81 | resolv_response_add_data (b, &ipv4, sizeof (ipv4)); |
82 | } |
83 | break; |
84 | case T_AAAA: |
85 | { |
86 | char ipv6[16] |
87 | = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, |
88 | counter, 0, thread, 0, 0}; |
89 | resolv_response_add_data (b, &ipv6, sizeof (ipv6)); |
90 | } |
91 | break; |
92 | default: |
93 | support_record_failure (); |
94 | printf (format: "error: unexpected QTYPE: %s/%u/%u\n" , |
95 | qname, qclass, qtype); |
96 | } |
97 | resolv_response_close_record (b); |
98 | } |
99 | |
100 | /* Check that the resolver configuration for this thread has an |
101 | extended resolver configuration. */ |
102 | static void |
103 | check_have_conf (void) |
104 | { |
105 | struct resolv_context *ctx = __resolv_context_get (); |
106 | TEST_VERIFY_EXIT (ctx != NULL); |
107 | TEST_VERIFY (ctx->conf != NULL); |
108 | __resolv_context_put (ctx); |
109 | } |
110 | |
111 | /* Verify that E matches the expected response for FAMILY and |
112 | COUNTER. */ |
113 | static void |
114 | check_hostent (const char *caller, const char *function, const char *qname, |
115 | int ret, struct hostent *e, int family, int counter) |
116 | { |
117 | if (ret != 0) |
118 | { |
119 | errno = ret; |
120 | support_record_failure (); |
121 | printf (format: "error: %s: %s for %s failed: %m\n" , caller, function, qname); |
122 | return; |
123 | } |
124 | |
125 | TEST_VERIFY_EXIT (e != NULL); |
126 | TEST_VERIFY (strcmp (qname, e->h_name) == 0); |
127 | TEST_VERIFY (e->h_addrtype == family); |
128 | TEST_VERIFY_EXIT (e->h_addr_list[0] != NULL); |
129 | TEST_VERIFY (e->h_addr_list[1] == NULL); |
130 | switch (family) |
131 | { |
132 | case AF_INET: |
133 | { |
134 | char addr[4] = {10, 0, counter, get_thread_number ()}; |
135 | TEST_VERIFY (e->h_length == sizeof (addr)); |
136 | TEST_VERIFY (memcmp (e->h_addr_list[0], addr, sizeof (addr)) == 0); |
137 | } |
138 | break; |
139 | case AF_INET6: |
140 | { |
141 | char addr[16] |
142 | = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, |
143 | 0, counter, 0, get_thread_number (), 0, 0}; |
144 | TEST_VERIFY (e->h_length == sizeof (addr)); |
145 | TEST_VERIFY (memcmp (e->h_addr_list[0], addr, sizeof (addr)) == 0); |
146 | } |
147 | break; |
148 | default: |
149 | FAIL_EXIT1 ("%s: invalid address family %d" , caller, family); |
150 | } |
151 | check_have_conf (); |
152 | } |
153 | |
154 | /* Check a getaddrinfo result. */ |
155 | static void |
156 | check_addrinfo (const char *caller, const char *qname, |
157 | int ret, struct addrinfo *ai, int family, int counter) |
158 | { |
159 | if (ret != 0) |
160 | { |
161 | support_record_failure (); |
162 | printf (format: "error: %s: getaddrinfo for %s failed: %s\n" , |
163 | caller, qname, gai_strerror (ecode: ret)); |
164 | return; |
165 | } |
166 | |
167 | TEST_VERIFY_EXIT (ai != NULL); |
168 | |
169 | /* Check that available data matches the requirements. */ |
170 | bool have_ipv4 = false; |
171 | bool have_ipv6 = false; |
172 | for (struct addrinfo *p = ai; p != NULL; p = p->ai_next) |
173 | { |
174 | TEST_VERIFY (p->ai_socktype == SOCK_STREAM); |
175 | TEST_VERIFY (p->ai_protocol == IPPROTO_TCP); |
176 | TEST_VERIFY_EXIT (p->ai_addr != NULL); |
177 | TEST_VERIFY (p->ai_addr->sa_family == p->ai_family); |
178 | |
179 | switch (p->ai_family) |
180 | { |
181 | case AF_INET: |
182 | { |
183 | TEST_VERIFY (!have_ipv4); |
184 | have_ipv4 = true; |
185 | struct sockaddr_in *sa = (struct sockaddr_in *) p->ai_addr; |
186 | TEST_VERIFY (p->ai_addrlen == sizeof (*sa)); |
187 | char addr[4] = {10, 0, counter, get_thread_number ()}; |
188 | TEST_VERIFY (memcmp (&sa->sin_addr, addr, sizeof (addr)) == 0); |
189 | TEST_VERIFY (ntohs (sa->sin_port) == 80); |
190 | } |
191 | break; |
192 | case AF_INET6: |
193 | { |
194 | TEST_VERIFY (!have_ipv6); |
195 | have_ipv6 = true; |
196 | struct sockaddr_in6 *sa = (struct sockaddr_in6 *) p->ai_addr; |
197 | TEST_VERIFY (p->ai_addrlen == sizeof (*sa)); |
198 | char addr[16] |
199 | = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, |
200 | 0, counter, 0, get_thread_number (), 0, 0}; |
201 | TEST_VERIFY (memcmp (&sa->sin6_addr, addr, sizeof (addr)) == 0); |
202 | TEST_VERIFY (ntohs (sa->sin6_port) == 80); |
203 | } |
204 | break; |
205 | default: |
206 | FAIL_EXIT1 ("%s: invalid address family %d" , caller, family); |
207 | } |
208 | } |
209 | |
210 | switch (family) |
211 | { |
212 | case AF_INET: |
213 | TEST_VERIFY (have_ipv4); |
214 | TEST_VERIFY (!have_ipv6); |
215 | break; |
216 | case AF_INET6: |
217 | TEST_VERIFY (!have_ipv4); |
218 | TEST_VERIFY (have_ipv6); |
219 | break; |
220 | case AF_UNSPEC: |
221 | TEST_VERIFY (have_ipv4); |
222 | TEST_VERIFY (have_ipv6); |
223 | break; |
224 | default: |
225 | FAIL_EXIT1 ("%s: invalid address family %d" , caller, family); |
226 | } |
227 | |
228 | check_have_conf (); |
229 | } |
230 | |
231 | /* This barrier ensures that all test threads begin their work |
232 | simultaneously. */ |
233 | static pthread_barrier_t barrier; |
234 | |
235 | /* Test gethostbyname2_r (if do_2 is false) or gethostbyname2_r with |
236 | AF_INET (if do_2 is true). */ |
237 | static void * |
238 | byname (bool do_2) |
239 | { |
240 | int this_thread = get_thread_number (); |
241 | xpthread_barrier_wait (barrier: &barrier); |
242 | for (int i = 0; i < queries_per_thread; ++i) |
243 | { |
244 | char qname[100]; |
245 | snprintf (s: qname, maxlen: sizeof (qname), format: "counter%d.thread%d.example.com" , |
246 | i, this_thread); |
247 | struct hostent storage; |
248 | char buf[1000]; |
249 | struct hostent *e = NULL; |
250 | int herrno; |
251 | int ret; |
252 | if (do_2) |
253 | ret = gethostbyname_r (name: qname, result_buf: &storage, buf: buf, buflen: sizeof (buf), |
254 | result: &e, h_errnop: &herrno); |
255 | else |
256 | ret = gethostbyname2_r (name: qname, AF_INET, result_buf: &storage, buf: buf, buflen: sizeof (buf), |
257 | result: &e, h_errnop: &herrno); |
258 | check_hostent (caller: __func__, function: do_2 ? "gethostbyname2_r" : "gethostbyname_r" , |
259 | qname, ret, e, AF_INET, counter: i); |
260 | } |
261 | check_have_conf (); |
262 | return NULL; |
263 | } |
264 | |
265 | /* Test gethostbyname_r. */ |
266 | static void * |
267 | thread_byname (void *closure) |
268 | { |
269 | return byname (false); |
270 | } |
271 | |
272 | /* Test gethostbyname2_r with AF_INET. */ |
273 | static void * |
274 | thread_byname2 (void *closure) |
275 | { |
276 | return byname (true); |
277 | } |
278 | |
279 | /* Test gethostbyname2_r with AF_INET6. */ |
280 | static void * |
281 | thread_byname2_af_inet6 (void *closure) |
282 | { |
283 | int this_thread = get_thread_number (); |
284 | xpthread_barrier_wait (barrier: &barrier); |
285 | for (int i = 0; i < queries_per_thread; ++i) |
286 | { |
287 | char qname[100]; |
288 | snprintf (s: qname, maxlen: sizeof (qname), format: "counter%d.thread%d.example.com" , |
289 | i, this_thread); |
290 | struct hostent storage; |
291 | char buf[1000]; |
292 | struct hostent *e = NULL; |
293 | int herrno; |
294 | int ret = gethostbyname2_r (name: qname, AF_INET6, result_buf: &storage, buf: buf, buflen: sizeof (buf), |
295 | result: &e, h_errnop: &herrno); |
296 | check_hostent (caller: __func__, function: "gethostbyname2_r" , qname, ret, e, AF_INET6, counter: i); |
297 | } |
298 | return NULL; |
299 | } |
300 | |
301 | /* Run getaddrinfo tests for FAMILY. */ |
302 | static void * |
303 | gai (int family) |
304 | { |
305 | int this_thread = get_thread_number (); |
306 | xpthread_barrier_wait (barrier: &barrier); |
307 | for (int i = 0; i < queries_per_thread; ++i) |
308 | { |
309 | char qname[100]; |
310 | snprintf (s: qname, maxlen: sizeof (qname), format: "counter%d.thread%d.example.com" , |
311 | i, this_thread); |
312 | struct addrinfo hints = |
313 | { |
314 | .ai_family = family, |
315 | .ai_socktype = SOCK_STREAM, |
316 | .ai_protocol = IPPROTO_TCP, |
317 | }; |
318 | struct addrinfo *ai; |
319 | int ret = getaddrinfo (name: qname, service: "80" , req: &hints, pai: &ai); |
320 | check_addrinfo (caller: __func__, qname, ret, ai, family, counter: i); |
321 | if (ret == 0) |
322 | freeaddrinfo (ai: ai); |
323 | } |
324 | return NULL; |
325 | } |
326 | |
327 | /* Test getaddrinfo with AF_INET. */ |
328 | static void * |
329 | thread_gai_inet (void *closure) |
330 | { |
331 | return gai (AF_INET); |
332 | } |
333 | |
334 | /* Test getaddrinfo with AF_INET6. */ |
335 | static void * |
336 | thread_gai_inet6 (void *closure) |
337 | { |
338 | return gai (AF_INET6); |
339 | } |
340 | |
341 | /* Test getaddrinfo with AF_UNSPEC. */ |
342 | static void * |
343 | thread_gai_unspec (void *closure) |
344 | { |
345 | return gai (AF_UNSPEC); |
346 | } |
347 | |
348 | /* Description of the chroot environment used to run the tests. */ |
349 | static struct support_chroot *chroot_env; |
350 | |
351 | /* Set up the chroot environment. */ |
352 | static void |
353 | prepare (int argc, char **argv) |
354 | { |
355 | chroot_env = support_chroot_create |
356 | ((struct support_chroot_configuration) |
357 | { |
358 | .resolv_conf = |
359 | "search example.com\n" |
360 | "nameserver 127.0.0.1\n" |
361 | "nameserver 127.0.0.2\n" |
362 | "nameserver 127.0.0.3\n" , |
363 | }); |
364 | } |
365 | |
366 | static int |
367 | do_test (void) |
368 | { |
369 | support_become_root (); |
370 | if (!support_enter_network_namespace ()) |
371 | return EXIT_UNSUPPORTED; |
372 | if (!support_can_chroot ()) |
373 | return EXIT_UNSUPPORTED; |
374 | |
375 | /* Load the shared object outside of the chroot. */ |
376 | TEST_VERIFY (dlopen (LIBNSS_DNS_SO, RTLD_LAZY) != NULL); |
377 | |
378 | xchroot (path: chroot_env->path_chroot); |
379 | TEST_VERIFY_EXIT (chdir ("/" ) == 0); |
380 | |
381 | struct sockaddr_in server_address = |
382 | { |
383 | .sin_family = AF_INET, |
384 | .sin_addr = { .s_addr = htonl (INADDR_LOOPBACK) }, |
385 | .sin_port = htons (53) |
386 | }; |
387 | const struct sockaddr *server_addresses[1] = |
388 | { (const struct sockaddr *) &server_address }; |
389 | |
390 | struct resolv_test *aux = resolv_test_start |
391 | ((struct resolv_redirect_config) |
392 | { |
393 | .response_callback = response, |
394 | .nscount = 1, |
395 | .disable_redirect = true, |
396 | .server_address_overrides = server_addresses, |
397 | }); |
398 | |
399 | enum { thread_count = 6 }; |
400 | xpthread_barrier_init (barrier: &barrier, NULL, count: thread_count + 1); |
401 | pthread_t threads[thread_count]; |
402 | typedef void *(*thread_func) (void *); |
403 | thread_func thread_funcs[thread_count] = |
404 | { |
405 | thread_byname, |
406 | thread_byname2, |
407 | thread_byname2_af_inet6, |
408 | thread_gai_inet, |
409 | thread_gai_inet6, |
410 | thread_gai_unspec, |
411 | }; |
412 | for (int i = 0; i < thread_count; ++i) |
413 | threads[i] = xpthread_create (NULL, thread_func: thread_funcs[i], NULL); |
414 | xpthread_barrier_wait (barrier: &barrier); /* Start the test threads. */ |
415 | for (int i = 0; i < thread_count; ++i) |
416 | xpthread_join (thr: threads[i]); |
417 | |
418 | resolv_test_end (aux); |
419 | support_chroot_free (chroot_env); |
420 | |
421 | return 0; |
422 | } |
423 | |
424 | #define PREPARE prepare |
425 | #include <support/test-driver.c> |
426 | |