1 | /* Parse /etc/hosts in multi mode with many addresses/aliases. |
2 | Copyright (C) 2017-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 | #include <dlfcn.h> |
20 | #include <errno.h> |
21 | #include <gnu/lib-names.h> |
22 | #include <netdb.h> |
23 | #include <nss.h> |
24 | #include <stdbool.h> |
25 | #include <stdlib.h> |
26 | #include <string.h> |
27 | #include <support/check.h> |
28 | #include <support/check_nss.h> |
29 | #include <support/namespace.h> |
30 | #include <support/support.h> |
31 | #include <support/test-driver.h> |
32 | #include <support/test-driver.h> |
33 | #include <support/xmemstream.h> |
34 | #include <support/xstdio.h> |
35 | #include <support/xunistd.h> |
36 | #include <sys/resource.h> |
37 | |
38 | struct support_chroot *chroot_env; |
39 | |
40 | static void |
41 | prepare (int argc, char **argv) |
42 | { |
43 | chroot_env = support_chroot_create |
44 | ((struct support_chroot_configuration) |
45 | { |
46 | .resolv_conf = "" , |
47 | .hosts = "" , /* See write_hosts below. */ |
48 | .host_conf = "multi on\n" , |
49 | }); |
50 | } |
51 | |
52 | /* Create the /etc/hosts file from outside the chroot. */ |
53 | static void |
54 | write_hosts (int count) |
55 | { |
56 | TEST_VERIFY (count > 0 && count <= 65535); |
57 | FILE *fp = xfopen (path: chroot_env->path_hosts, mode: "w" ); |
58 | fputs ("127.0.0.1 localhost localhost.localdomain\n" |
59 | "::1 localhost localhost.localdomain\n" , |
60 | fp); |
61 | for (int i = 0; i < count; ++i) |
62 | { |
63 | fprintf (fp, "10.4.%d.%d www4.example.com\n" , |
64 | (i / 256) & 0xff, i & 0xff); |
65 | fprintf (fp, "10.46.%d.%d www.example.com\n" , |
66 | (i / 256) & 0xff, i & 0xff); |
67 | fprintf (fp, "192.0.2.1 alias.example.com v4-%d.example.com\n" , i); |
68 | fprintf (fp, "2001:db8::6:%x www6.example.com\n" , i); |
69 | fprintf (fp, "2001:db8::46:%x www.example.com\n" , i); |
70 | fprintf (fp, "2001:db8::1 alias.example.com v6-%d.example.com\n" , i); |
71 | } |
72 | xfclose (fp); |
73 | } |
74 | |
75 | /* Parameters of a single test. */ |
76 | struct test_params |
77 | { |
78 | const char *name; /* Name to query. */ |
79 | const char *marker; /* Address marker for the name. */ |
80 | int count; /* Number of addresses/aliases. */ |
81 | int family; /* AF_INET, AF_INET_6 or AF_UNSPEC. */ |
82 | bool canonname; /* True if AI_CANONNAME should be enabled. */ |
83 | }; |
84 | |
85 | /* Expected result of gethostbyname/gethostbyname2. */ |
86 | static char * |
87 | expected_ghbn (const struct test_params *params) |
88 | { |
89 | TEST_VERIFY (params->family == AF_INET || params->family == AF_INET6); |
90 | |
91 | struct xmemstream expected; |
92 | xopen_memstream (stream: &expected); |
93 | if (strcmp (params->name, "alias.example.com" ) == 0) |
94 | { |
95 | fprintf (expected.out, "name: %s\n" , params->name); |
96 | char af; |
97 | if (params->family == AF_INET) |
98 | af = '4'; |
99 | else |
100 | af = '6'; |
101 | for (int i = 0; i < params->count; ++i) |
102 | fprintf (expected.out, "alias: v%c-%d.example.com\n" , af, i); |
103 | |
104 | for (int i = 0; i < params->count; ++i) |
105 | if (params->family == AF_INET) |
106 | fputs ("address: 192.0.2.1\n" , expected.out); |
107 | else |
108 | fputs ("address: 2001:db8::1\n" , expected.out); |
109 | } |
110 | else /* www/www4/www6 name. */ |
111 | { |
112 | bool do_ipv4 = params->family == AF_INET |
113 | && strncmp (params->name, "www6" , 4) != 0; |
114 | bool do_ipv6 = params->family == AF_INET6 |
115 | && strncmp (params->name, "www4" , 4) != 0; |
116 | if (do_ipv4 || do_ipv6) |
117 | { |
118 | fprintf (expected.out, "name: %s\n" , params->name); |
119 | if (do_ipv4) |
120 | for (int i = 0; i < params->count; ++i) |
121 | fprintf (expected.out, "address: 10.%s.%d.%d\n" , |
122 | params->marker, i / 256, i % 256); |
123 | if (do_ipv6) |
124 | for (int i = 0; i < params->count; ++i) |
125 | fprintf (expected.out, "address: 2001:db8::%s:%x\n" , |
126 | params->marker, i); |
127 | } |
128 | else |
129 | fputs ("error: HOST_NOT_FOUND\n" , expected.out); |
130 | } |
131 | xfclose_memstream (stream: &expected); |
132 | return expected.buffer; |
133 | } |
134 | |
135 | /* Expected result of getaddrinfo. */ |
136 | static char * |
137 | expected_gai (const struct test_params *params) |
138 | { |
139 | bool do_ipv4 = false; |
140 | bool do_ipv6 = false; |
141 | if (params->family == AF_UNSPEC) |
142 | do_ipv4 = do_ipv6 = true; |
143 | else if (params->family == AF_INET) |
144 | do_ipv4 = true; |
145 | else if (params->family == AF_INET6) |
146 | do_ipv6 = true; |
147 | |
148 | struct xmemstream expected; |
149 | xopen_memstream (stream: &expected); |
150 | if (strcmp (params->name, "alias.example.com" ) == 0) |
151 | { |
152 | if (params->canonname) |
153 | fprintf (expected.out, |
154 | "flags: AI_CANONNAME\n" |
155 | "canonname: %s\n" , |
156 | params->name); |
157 | |
158 | if (do_ipv4) |
159 | for (int i = 0; i < params->count; ++i) |
160 | fputs ("address: STREAM/TCP 192.0.2.1 80\n" , expected.out); |
161 | if (do_ipv6) |
162 | for (int i = 0; i < params->count; ++i) |
163 | fputs ("address: STREAM/TCP 2001:db8::1 80\n" , expected.out); |
164 | } |
165 | else /* www/www4/www6 name. */ |
166 | { |
167 | if (strncmp (params->name, "www4" , 4) == 0) |
168 | do_ipv6 = false; |
169 | else if (strncmp (params->name, "www6" , 4) == 0) |
170 | do_ipv4 = false; |
171 | /* Otherwise, we have www as the name, so we do both. */ |
172 | |
173 | if (do_ipv4 || do_ipv6) |
174 | { |
175 | if (params->canonname) |
176 | fprintf (expected.out, |
177 | "flags: AI_CANONNAME\n" |
178 | "canonname: %s\n" , |
179 | params->name); |
180 | |
181 | if (do_ipv4) |
182 | for (int i = 0; i < params->count; ++i) |
183 | fprintf (expected.out, "address: STREAM/TCP 10.%s.%d.%d 80\n" , |
184 | params->marker, i / 256, i % 256); |
185 | if (do_ipv6) |
186 | for (int i = 0; i < params->count; ++i) |
187 | fprintf (expected.out, |
188 | "address: STREAM/TCP 2001:db8::%s:%x 80\n" , |
189 | params->marker, i); |
190 | } |
191 | else |
192 | fputs ("error: Name or service not known\n" , expected.out); |
193 | } |
194 | xfclose_memstream (stream: &expected); |
195 | return expected.buffer; |
196 | } |
197 | |
198 | static void |
199 | run_gbhn_gai (struct test_params *params) |
200 | { |
201 | char *ctx = xasprintf (format: "name=%s marker=%s count=%d family=%d" , |
202 | params->name, params->marker, params->count, |
203 | params->family); |
204 | if (test_verbose > 0) |
205 | printf (format: "info: %s\n" , ctx); |
206 | |
207 | /* Check gethostbyname, gethostbyname2. */ |
208 | if (params->family == AF_INET) |
209 | { |
210 | char *expected = expected_ghbn (params); |
211 | check_hostent (query_description: ctx, gethostbyname (name: params->name), expected); |
212 | free (ptr: expected); |
213 | } |
214 | if (params->family != AF_UNSPEC) |
215 | { |
216 | char *expected = expected_ghbn (params); |
217 | check_hostent (query_description: ctx, gethostbyname2 (name: params->name, af: params->family), |
218 | expected); |
219 | free (ptr: expected); |
220 | } |
221 | |
222 | /* Check getaddrinfo. */ |
223 | for (int do_canonical = 0; do_canonical < 2; ++do_canonical) |
224 | { |
225 | params->canonname = do_canonical; |
226 | char *expected = expected_gai (params); |
227 | struct addrinfo hints = |
228 | { |
229 | .ai_family = params->family, |
230 | .ai_socktype = SOCK_STREAM, |
231 | .ai_protocol = IPPROTO_TCP, |
232 | }; |
233 | if (do_canonical) |
234 | hints.ai_flags |= AI_CANONNAME; |
235 | struct addrinfo *ai; |
236 | int ret = getaddrinfo (params->name, "80" , &hints, &ai); |
237 | check_addrinfo (query_description: ctx, ai, ret, expected); |
238 | if (ret == 0) |
239 | freeaddrinfo (ai); |
240 | free (ptr: expected); |
241 | } |
242 | |
243 | free (ptr: ctx); |
244 | } |
245 | |
246 | /* Callback for the subprocess which runs the test in a chroot. */ |
247 | static void |
248 | subprocess (void *closure) |
249 | { |
250 | struct test_params *params = closure; |
251 | |
252 | xchroot (path: chroot_env->path_chroot); |
253 | |
254 | static const int families[] = { AF_INET, AF_INET6, AF_UNSPEC, -1 }; |
255 | static const char *const names[] = |
256 | { |
257 | "www.example.com" , "www4.example.com" , "www6.example.com" , |
258 | "alias.example.com" , |
259 | NULL |
260 | }; |
261 | static const char *const names_marker[] = { "46" , "4" , "6" , "" }; |
262 | |
263 | for (int family_idx = 0; families[family_idx] >= 0; ++family_idx) |
264 | { |
265 | params->family = families[family_idx]; |
266 | for (int names_idx = 0; names[names_idx] != NULL; ++names_idx) |
267 | { |
268 | params->name = names[names_idx]; |
269 | params->marker = names_marker[names_idx]; |
270 | run_gbhn_gai (params); |
271 | } |
272 | } |
273 | } |
274 | |
275 | /* Run the test for a specific number of addresses/aliases. */ |
276 | static void |
277 | run_test (int count) |
278 | { |
279 | write_hosts (count); |
280 | |
281 | struct test_params params = |
282 | { |
283 | .count = count, |
284 | }; |
285 | |
286 | support_isolate_in_subprocess (callback: subprocess, closure: ¶ms); |
287 | } |
288 | |
289 | static int |
290 | do_test (void) |
291 | { |
292 | support_become_root (); |
293 | if (!support_can_chroot ()) |
294 | return EXIT_UNSUPPORTED; |
295 | |
296 | /* This test should not use gigabytes of memory. */ |
297 | { |
298 | struct rlimit limit; |
299 | if (getrlimit (RLIMIT_AS, rlimits: &limit) != 0) |
300 | { |
301 | printf (format: "getrlimit (RLIMIT_AS) failed: %m\n" ); |
302 | return 1; |
303 | } |
304 | long target = 200 * 1024 * 1024; |
305 | if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target) |
306 | { |
307 | limit.rlim_cur = target; |
308 | if (setrlimit (RLIMIT_AS, rlimits: &limit) != 0) |
309 | { |
310 | printf (format: "setrlimit (RLIMIT_AS) failed: %m\n" ); |
311 | return 1; |
312 | } |
313 | } |
314 | } |
315 | |
316 | __nss_configure_lookup (dbname: "hosts" , string: "files" ); |
317 | if (dlopen (LIBNSS_FILES_SO, RTLD_LAZY) == NULL) |
318 | FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s" , dlerror ()); |
319 | |
320 | /* Run the tests with a few different address/alias counts. */ |
321 | for (int count = 1; count <= 111; ++count) |
322 | run_test (count); |
323 | run_test (count: 1111); |
324 | run_test (count: 22222); |
325 | |
326 | support_chroot_free (chroot_env); |
327 | return 0; |
328 | } |
329 | |
330 | #define TIMEOUT 40 |
331 | #define PREPARE prepare |
332 | #include <support/test-driver.c> |
333 | |