]>
Commit | Line | Data |
---|---|---|
5840c75c | 1 | /* Test search/default domain name behavior. |
d614a753 | 2 | Copyright (C) 2016-2020 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 <resolv.h> | |
ceaa9889 | 20 | #include <stdlib.h> |
5840c75c FW |
21 | #include <string.h> |
22 | #include <support/check.h> | |
23 | #include <support/check_nss.h> | |
24 | #include <support/resolv_test.h> | |
25 | #include <support/support.h> | |
26 | #include <support/xmemstream.h> | |
27 | ||
28 | struct item | |
29 | { | |
30 | const char *name; | |
31 | int response; | |
32 | }; | |
33 | ||
34 | const struct item items[] = | |
35 | { | |
36 | {"hostname.usersys.example.com", 1}, | |
37 | {"hostname.corp.example.com", 1}, | |
38 | {"hostname.example.com", 1}, | |
39 | ||
40 | {"mail.corp.example.com", 1}, | |
41 | {"mail.example.com", 1}, | |
42 | ||
43 | {"file.corp.example.com", 2}, | |
44 | {"file.corp", 1}, | |
45 | {"file.example.com", 1}, | |
46 | {"servfail-usersys.usersys.example.com", -ns_r_servfail}, | |
47 | {"servfail-usersys.corp.example.com", 1}, | |
48 | {"servfail-usersys.example.com", 1}, | |
49 | {"servfail-corp.usersys.example.com", 1}, | |
50 | {"servfail-corp.corp.example.com", -ns_r_servfail}, | |
51 | {"servfail-corp.example.com", 1}, | |
52 | {"www.example.com", 1}, | |
53 | {"large.example.com", 200}, | |
54 | ||
55 | /* Test query amplification with a SERVFAIL response combined with | |
56 | a large RRset. */ | |
57 | {"large-servfail.usersys.example.com", -ns_r_servfail}, | |
58 | {"large-servfail.example.com", 2000}, | |
59 | {} | |
60 | }; | |
61 | ||
62 | enum | |
63 | { | |
64 | name_not_found = -1, | |
65 | name_no_data = -2 | |
66 | }; | |
67 | ||
68 | static int | |
69 | find_name (const char *name) | |
70 | { | |
71 | for (int i = 0; items[i].name != NULL; ++i) | |
72 | { | |
73 | if (strcmp (name, items[i].name) == 0) | |
74 | return i; | |
75 | } | |
76 | if (strcmp (name, "example.com") == 0 | |
77 | || strcmp (name, "usersys.example.com") == 0 | |
78 | || strcmp (name, "corp.example.com") == 0) | |
79 | return name_no_data; | |
80 | return name_not_found; | |
81 | } | |
82 | ||
83 | static int rcode_override_server_index = -1; | |
84 | static int rcode_override; | |
85 | ||
86 | static void | |
87 | response (const struct resolv_response_context *ctx, | |
88 | struct resolv_response_builder *b, | |
89 | const char *qname, uint16_t qclass, uint16_t qtype) | |
90 | { | |
91 | if (ctx->server_index == rcode_override_server_index) | |
92 | { | |
93 | struct resolv_response_flags flags = {.rcode = rcode_override}; | |
94 | resolv_response_init (b, flags); | |
95 | resolv_response_add_question (b, qname, qclass, qtype); | |
96 | return; | |
97 | } | |
98 | ||
99 | int index = find_name (qname); | |
100 | struct resolv_response_flags flags = {}; | |
101 | if (index == name_not_found) | |
102 | flags.rcode = ns_r_nxdomain; | |
103 | else if (index >= 0 && items[index].response < 0) | |
104 | flags.rcode = -items[index].response; | |
105 | else if (index >= 0 && items[index].response > 5 && !ctx->tcp) | |
106 | /* Force TCP if more than 5 addresses where requested. */ | |
107 | flags.tc = true; | |
108 | resolv_response_init (b, flags); | |
109 | resolv_response_add_question (b, qname, qclass, qtype); | |
110 | ||
111 | if (flags.tc || index < 0 || items[index].response < 0) | |
112 | return; | |
113 | ||
114 | resolv_response_section (b, ns_s_an); | |
115 | ||
116 | for (int i = 0; i < items[index].response; ++i) | |
117 | { | |
118 | resolv_response_open_record (b, qname, qclass, qtype, 0); | |
119 | ||
120 | switch (qtype) | |
121 | { | |
122 | case T_A: | |
123 | { | |
124 | char addr[4] = {10, index, i >> 8, i}; | |
125 | resolv_response_add_data (b, addr, sizeof (addr)); | |
126 | } | |
127 | break; | |
128 | case T_AAAA: | |
129 | { | |
130 | char addr[16] | |
131 | = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, | |
132 | 0, index + 1, (i + 1) >> 8, i + 1}; | |
133 | resolv_response_add_data (b, addr, sizeof (addr)); | |
134 | } | |
135 | break; | |
136 | default: | |
137 | support_record_failure (); | |
138 | printf ("error: unexpected QTYPE: %s/%u/%u\n", | |
139 | qname, qclass, qtype); | |
140 | } | |
141 | resolv_response_close_record (b); | |
142 | } | |
143 | } | |
144 | ||
145 | enum output_format | |
146 | { | |
147 | format_get, format_gai | |
148 | }; | |
149 | ||
150 | static void | |
151 | format_expected_1 (FILE *out, int family, enum output_format format, int index) | |
152 | { | |
153 | for (int i = 0; i < items[index].response; ++i) | |
154 | { | |
155 | char address[200]; | |
156 | switch (family) | |
157 | { | |
158 | case AF_INET: | |
159 | snprintf (address, sizeof (address), "10.%d.%d.%d", | |
160 | index, (i >> 8) & 0xff, i & 0xff); | |
161 | break; | |
162 | case AF_INET6: | |
163 | snprintf (address, sizeof (address), "2001:db8::%x:%x", | |
164 | index + 1, i + 1); | |
165 | break; | |
166 | default: | |
167 | FAIL_EXIT1 ("unreachable"); | |
168 | } | |
169 | ||
170 | switch (format) | |
171 | { | |
172 | case format_get: | |
173 | fprintf (out, "address: %s\n", address); | |
174 | break; | |
175 | case format_gai: | |
176 | fprintf (out, "address: STREAM/TCP %s 80\n", address); | |
177 | } | |
178 | } | |
179 | } | |
180 | ||
181 | static char * | |
182 | format_expected (const char *fqdn, int family, enum output_format format) | |
183 | { | |
184 | int index = find_name (fqdn); | |
185 | TEST_VERIFY_EXIT (index >= 0); | |
186 | struct xmemstream stream; | |
187 | xopen_memstream (&stream); | |
188 | ||
189 | TEST_VERIFY_EXIT (items[index].response >= 0); | |
190 | if (format == format_get) | |
191 | fprintf (stream.out, "name: %s\n", items[index].name); | |
192 | if (family == AF_INET || family == AF_UNSPEC) | |
193 | format_expected_1 (stream.out, AF_INET, format, index); | |
194 | if (family == AF_INET6 || family == AF_UNSPEC) | |
195 | format_expected_1 (stream.out, AF_INET6, format, index); | |
196 | ||
197 | xfclose_memstream (&stream); | |
198 | return stream.buffer; | |
199 | } | |
200 | ||
201 | static void | |
202 | do_get (const char *name, const char *fqdn, int family) | |
203 | { | |
204 | char *expected = format_expected (fqdn, family, format_get); | |
205 | if (family == AF_INET) | |
206 | { | |
207 | char *query = xasprintf ("gethostbyname (\"%s\")", name); | |
208 | check_hostent (query, gethostbyname (name), expected); | |
209 | free (query); | |
210 | } | |
211 | char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family); | |
212 | check_hostent (query, gethostbyname2 (name, family), expected); | |
213 | ||
214 | /* Test res_search. */ | |
215 | int qtype; | |
216 | switch (family) | |
217 | { | |
218 | case AF_INET: | |
219 | qtype = T_A; | |
220 | break; | |
221 | case AF_INET6: | |
222 | qtype = T_AAAA; | |
223 | break; | |
224 | default: | |
225 | qtype = -1; | |
226 | } | |
227 | if (qtype >= 0) | |
228 | { | |
229 | int sz = 512; | |
230 | unsigned char *response = xmalloc (sz); | |
231 | int ret = res_search (name, C_IN, qtype, response, sz); | |
232 | TEST_VERIFY_EXIT (ret >= 0); | |
233 | if (ret > sz) | |
234 | { | |
235 | /* Truncation. Retry with a larger buffer. */ | |
236 | sz = 65535; | |
237 | unsigned char *newptr = xrealloc (response, sz); | |
238 | response = newptr; | |
239 | ||
240 | ret = res_search (name, C_IN, qtype, response, sz); | |
241 | TEST_VERIFY_EXIT (ret >= 0); | |
242 | TEST_VERIFY_EXIT (ret < sz); | |
243 | } | |
244 | check_dns_packet (query, response, ret, expected); | |
245 | free (response); | |
246 | } | |
247 | ||
248 | free (query); | |
249 | free (expected); | |
250 | } | |
251 | ||
252 | static void | |
253 | do_gai (const char *name, const char *fqdn, int family) | |
254 | { | |
255 | struct addrinfo hints = | |
256 | { | |
257 | .ai_family = family, | |
258 | .ai_protocol = IPPROTO_TCP, | |
259 | .ai_socktype = SOCK_STREAM | |
260 | }; | |
261 | struct addrinfo *ai; | |
262 | char *query = xasprintf ("%s:80 [%d]", name, family); | |
263 | int ret = getaddrinfo (name, "80", &hints, &ai); | |
264 | char *expected = format_expected (fqdn, family, format_gai); | |
265 | check_addrinfo (query, ai, ret, expected); | |
266 | if (ret == 0) | |
267 | freeaddrinfo (ai); | |
268 | free (expected); | |
269 | free (query); | |
270 | } | |
271 | ||
272 | static void | |
273 | do_both (const char *name, const char *fqdn) | |
274 | { | |
275 | do_get (name, fqdn, AF_INET); | |
276 | do_get (name, fqdn, AF_INET6); | |
277 | do_gai (name, fqdn, AF_INET); | |
278 | do_gai (name, fqdn, AF_INET6); | |
279 | do_gai (name, fqdn, AF_UNSPEC); | |
280 | } | |
281 | ||
282 | static void | |
283 | do_test_all (bool unconnectable_server) | |
284 | { | |
285 | struct resolv_redirect_config config = | |
286 | { | |
287 | .response_callback = response, | |
288 | .search = {"usersys.example.com", "corp.example.com", "example.com"}, | |
289 | }; | |
290 | struct resolv_test *obj = resolv_test_start (config); | |
291 | ||
292 | if (unconnectable_server) | |
293 | { | |
294 | /* 255.255.255.255 results in an immediate connect failure. The | |
295 | next server will supply the answer instead. This is a | |
296 | triggering condition for bug 19791. */ | |
297 | _res.nsaddr_list[0].sin_addr.s_addr = -1; | |
298 | _res.nsaddr_list[0].sin_port = htons (53); | |
299 | } | |
300 | ||
301 | do_both ("file", "file.corp.example.com"); | |
302 | do_both ("www", "www.example.com"); | |
303 | do_both ("servfail-usersys", "servfail-usersys.corp.example.com"); | |
304 | do_both ("servfail-corp", "servfail-corp.usersys.example.com"); | |
305 | do_both ("large", "large.example.com"); | |
306 | do_both ("large-servfail", "large-servfail.example.com"); | |
307 | do_both ("file.corp", "file.corp"); | |
308 | ||
309 | /* Check that SERVFAIL and REFUSED responses do not alter the search | |
310 | path resolution. */ | |
311 | rcode_override_server_index = 0; | |
312 | rcode_override = ns_r_servfail; | |
313 | do_both ("hostname", "hostname.usersys.example.com"); | |
314 | do_both ("large", "large.example.com"); | |
315 | do_both ("large-servfail", "large-servfail.example.com"); | |
316 | rcode_override = ns_r_refused; | |
317 | do_both ("hostname", "hostname.usersys.example.com"); | |
318 | do_both ("large", "large.example.com"); | |
319 | do_both ("large-servfail", "large-servfail.example.com"); | |
320 | /* Likewise, but with an NXDOMAIN for the first search path | |
321 | entry. */ | |
322 | rcode_override = ns_r_servfail; | |
323 | do_both ("mail", "mail.corp.example.com"); | |
324 | rcode_override = ns_r_refused; | |
325 | do_both ("mail", "mail.corp.example.com"); | |
326 | /* Likewise, but with ndots handling. */ | |
327 | rcode_override = ns_r_servfail; | |
328 | do_both ("file.corp", "file.corp"); | |
329 | rcode_override = ns_r_refused; | |
330 | do_both ("file.corp", "file.corp"); | |
331 | ||
332 | resolv_test_end (obj); | |
333 | } | |
334 | ||
335 | static int | |
336 | do_test (void) | |
337 | { | |
338 | for (int unconnectable_server = 0; unconnectable_server < 2; | |
339 | ++unconnectable_server) | |
340 | do_test_all (unconnectable_server); | |
341 | return 0; | |
342 | } | |
343 | ||
344 | #include <support/test-driver.c> |