1 | /* Test reverse DNS lookup. |
2 | Copyright (C) 2022-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 <arpa/inet.h> |
20 | #include <errno.h> |
21 | #include <netdb.h> |
22 | #include <stdbool.h> |
23 | #include <stdio.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <support/check.h> |
27 | #include <support/check_nss.h> |
28 | #include <support/next_to_fault.h> |
29 | #include <support/resolv_test.h> |
30 | #include <support/support.h> |
31 | |
32 | #include "tst-resolv-maybe_insert_sig.h" |
33 | |
34 | /* QNAME format: |
35 | |
36 | ADDRESSES.CNAMES...(lots of 0s)...8.b.d.0.1.0.0.2.ip6.arpa. |
37 | CNAMES|ADDRESSES.2.0.192.in-addr-arpa. |
38 | |
39 | For the IPv4 reverse lookup, the address count is in the lower |
40 | bits. |
41 | |
42 | CNAMES is the length of the CNAME chain, ADDRESSES is the number of |
43 | addresses in the response. The special value 15 means that there |
44 | are no addresses, and the RCODE is NXDOMAIN. */ |
45 | static void |
46 | response (const struct resolv_response_context *ctx, |
47 | struct resolv_response_builder *b, |
48 | const char *qname, uint16_t qclass, uint16_t qtype) |
49 | { |
50 | TEST_COMPARE (qclass, C_IN); |
51 | TEST_COMPARE (qtype, T_PTR); |
52 | |
53 | unsigned int addresses, cnames, bits; |
54 | char *tail; |
55 | if (strstr (haystack: qname, needle: "ip6.arpa" ) != NULL |
56 | && sscanf (s: qname, format: "%x.%x.%ms" , &addresses, &cnames, &tail) == 3) |
57 | TEST_COMPARE_STRING (tail, "\ |
58 | 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa" ); |
59 | else if (sscanf (s: qname, format: "%u.%ms" , &bits, &tail) == 2) |
60 | { |
61 | TEST_COMPARE_STRING (tail, "2.0.192.in-addr.arpa" ); |
62 | addresses = bits & 0x0f; |
63 | cnames = bits >> 4; |
64 | } |
65 | else |
66 | FAIL_EXIT1 ("invalid QNAME: %s" , qname); |
67 | free (ptr: tail); |
68 | |
69 | int rcode; |
70 | if (addresses == 15) |
71 | { |
72 | /* Special case: Use no addresses with NXDOMAIN response. */ |
73 | rcode = ns_r_nxdomain; |
74 | addresses = 0; |
75 | } |
76 | else |
77 | rcode = 0; |
78 | |
79 | struct resolv_response_flags flags = { .rcode = rcode }; |
80 | resolv_response_init (b, flags); |
81 | resolv_response_add_question (b, name: qname, class: qclass, type: qtype); |
82 | resolv_response_section (b, ns_s_an); |
83 | maybe_insert_sig (b, owner: qname); |
84 | |
85 | /* Provide the requested number of CNAME records. */ |
86 | char *previous_name = (char *) qname; |
87 | for (int unique = 0; unique < cnames; ++unique) |
88 | { |
89 | resolv_response_open_record (b, name: previous_name, class: qclass, T_CNAME, ttl: 60); |
90 | char *new_name = xasprintf (format: "%d.alias.example" , unique); |
91 | resolv_response_add_name (b, name: new_name); |
92 | resolv_response_close_record (b); |
93 | |
94 | maybe_insert_sig (b, owner: qname); |
95 | |
96 | if (previous_name != qname) |
97 | free (ptr: previous_name); |
98 | previous_name = new_name; |
99 | } |
100 | |
101 | for (int unique = 0; unique < addresses; ++unique) |
102 | { |
103 | resolv_response_open_record (b, name: previous_name, class: qclass, T_PTR, ttl: 60); |
104 | char *ptr = xasprintf (format: "unique-%d.cnames-%u.addresses-%u.example" , |
105 | unique, cnames, addresses); |
106 | resolv_response_add_name (b, name: ptr); |
107 | free (ptr: ptr); |
108 | resolv_response_close_record (b); |
109 | } |
110 | |
111 | if (previous_name != qname) |
112 | free (ptr: previous_name); |
113 | } |
114 | |
115 | /* Used to check that gethostbyaddr_r does not write past the buffer |
116 | end. */ |
117 | static struct support_next_to_fault ntf; |
118 | |
119 | /* Perform a gethostbyaddr call and check the result. */ |
120 | static void |
121 | check_gethostbyaddr (const char *address, const char *expected) |
122 | { |
123 | unsigned char bytes[16]; |
124 | unsigned int byteslen; |
125 | int family; |
126 | if (strchr (s: address, c: ':') != NULL) |
127 | { |
128 | family = AF_INET6; |
129 | byteslen = 16; |
130 | } |
131 | else |
132 | { |
133 | family = AF_INET; |
134 | byteslen = 4; |
135 | } |
136 | TEST_COMPARE (inet_pton (family, address, bytes), 1); |
137 | |
138 | struct hostent *e = gethostbyaddr (addr: bytes, len: byteslen, type: family); |
139 | check_hostent (query_description: address, e, expected); |
140 | |
141 | if (e == NULL) |
142 | return; |
143 | |
144 | /* Try gethostbyaddr_r with increasing sizes until success. First |
145 | compute a reasonable minimum buffer size, to avoid many pointless |
146 | attempts. */ |
147 | size_t minimum_size = strlen (s: e->h_name); |
148 | for (int i = 0; e->h_addr_list[i] != NULL; ++i) |
149 | minimum_size += e->h_length + sizeof (char *); |
150 | for (int i = 0; e->h_aliases[i] != NULL; ++i) |
151 | minimum_size += strlen (s: e->h_aliases[i]) + 1 + sizeof (char *); |
152 | |
153 | /* Gradually increase the size until success. */ |
154 | for (size_t size = minimum_size; size < ntf.length; ++size) |
155 | { |
156 | struct hostent result; |
157 | int herrno; |
158 | int ret = gethostbyaddr_r (addr: bytes, len: byteslen, type: family, result_buf: &result, |
159 | buf: ntf.buffer + ntf.length - size, buflen: size, |
160 | result: &e, h_errnop: &herrno); |
161 | if (ret == ERANGE) |
162 | /* Retry with larger size. */ |
163 | TEST_COMPARE (herrno, NETDB_INTERNAL); |
164 | else if (ret == 0) |
165 | { |
166 | TEST_VERIFY (size > minimum_size); |
167 | check_hostent (query_description: address, e, expected); |
168 | return; |
169 | } |
170 | else |
171 | FAIL_EXIT1 ("Unexpected gethostbyaddr_r failure: %d" , ret); |
172 | } |
173 | |
174 | FAIL_EXIT1 ("gethostbyaddr_r always failed for: %s" , address); |
175 | } |
176 | |
177 | /* Perform a getnameinfo call and check the result. */ |
178 | static void |
179 | check_getnameinfo (const char *address, const char *expected) |
180 | { |
181 | struct sockaddr_in sin = { }; |
182 | struct sockaddr_in6 sin6 = { }; |
183 | void *sa; |
184 | socklen_t salen; |
185 | if (strchr (s: address, c: ':') != NULL) |
186 | { |
187 | sin6.sin6_family = AF_INET6; |
188 | TEST_COMPARE (inet_pton (AF_INET6, address, &sin6.sin6_addr), 1); |
189 | sin6.sin6_port = htons (80); |
190 | sa = &sin6; |
191 | salen = sizeof (sin6); |
192 | } |
193 | else |
194 | { |
195 | sin.sin_family = AF_INET; |
196 | TEST_COMPARE (inet_pton (AF_INET, address, &sin.sin_addr), 1); |
197 | sin.sin_port = htons (80); |
198 | sa = &sin; |
199 | salen = sizeof (sin); |
200 | } |
201 | |
202 | char host[64]; |
203 | char service[64]; |
204 | int ret = getnameinfo (sa: sa, salen: salen, host: host, |
205 | hostlen: sizeof (host), serv: service, servlen: sizeof (service), |
206 | NI_NAMEREQD | NI_NUMERICSERV); |
207 | switch (ret) |
208 | { |
209 | case 0: |
210 | TEST_COMPARE_STRING (host, expected); |
211 | TEST_COMPARE_STRING (service, "80" ); |
212 | break; |
213 | case EAI_SYSTEM: |
214 | TEST_COMPARE_STRING (strerror (errno), expected); |
215 | break; |
216 | default: |
217 | TEST_COMPARE_STRING (gai_strerror (ret), expected); |
218 | } |
219 | } |
220 | |
221 | static int |
222 | do_test (void) |
223 | { |
224 | /* Some reasonably upper bound for the maximum response size. */ |
225 | ntf = support_next_to_fault_allocate (size: 4096); |
226 | |
227 | struct resolv_test *obj = resolv_test_start |
228 | ((struct resolv_redirect_config) |
229 | { |
230 | .response_callback = response |
231 | }); |
232 | |
233 | for (int do_insert_sig = 0; do_insert_sig < 2; ++do_insert_sig) |
234 | { |
235 | insert_sig = do_insert_sig; |
236 | |
237 | /* No PTR record, RCODE=0. */ |
238 | check_gethostbyaddr (address: "192.0.2.0" , expected: "error: NO_RECOVERY\n" ); |
239 | check_getnameinfo (address: "192.0.2.0" , expected: "Name or service not known" ); |
240 | check_gethostbyaddr (address: "192.0.2.16" , expected: "error: NO_RECOVERY\n" ); |
241 | check_getnameinfo (address: "192.0.2.16" , expected: "Name or service not known" ); |
242 | check_gethostbyaddr (address: "192.0.2.32" , expected: "error: NO_RECOVERY\n" ); |
243 | check_getnameinfo (address: "192.0.2.32" , expected: "Name or service not known" ); |
244 | check_gethostbyaddr (address: "2001:db8::" , expected: "error: NO_RECOVERY\n" ); |
245 | check_getnameinfo (address: "2001:db8::" , expected: "Name or service not known" ); |
246 | check_gethostbyaddr (address: "2001:db8::10" , expected: "error: NO_RECOVERY\n" ); |
247 | check_getnameinfo (address: "2001:db8::10" , expected: "Name or service not known" ); |
248 | check_gethostbyaddr (address: "2001:db8::20" , expected: "error: NO_RECOVERY\n" ); |
249 | check_getnameinfo (address: "2001:db8::20" , expected: "Name or service not known" ); |
250 | |
251 | /* No PTR record, NXDOMAIN. */ |
252 | check_gethostbyaddr (address: "192.0.2.15" , expected: "error: HOST_NOT_FOUND\n" ); |
253 | check_getnameinfo (address: "192.0.2.15" , expected: "Name or service not known" ); |
254 | check_gethostbyaddr (address: "192.0.2.31" , expected: "error: HOST_NOT_FOUND\n" ); |
255 | check_getnameinfo (address: "192.0.2.31" , expected: "Name or service not known" ); |
256 | check_gethostbyaddr (address: "192.0.2.47" , expected: "error: HOST_NOT_FOUND\n" ); |
257 | check_getnameinfo (address: "192.0.2.47" , expected: "Name or service not known" ); |
258 | check_gethostbyaddr (address: "2001:db8::f" , expected: "error: HOST_NOT_FOUND\n" ); |
259 | check_getnameinfo (address: "2001:db8::f" , expected: "Name or service not known" ); |
260 | check_gethostbyaddr (address: "2001:db8::1f" , expected: "error: HOST_NOT_FOUND\n" ); |
261 | check_getnameinfo (address: "2001:db8::1f" , expected: "Name or service not known" ); |
262 | check_gethostbyaddr (address: "2001:db8::2f" , expected: "error: HOST_NOT_FOUND\n" ); |
263 | check_getnameinfo (address: "2001:db8::2f" , expected: "Name or service not known" ); |
264 | |
265 | /* Actual response data. Only the first PTR record is returned. */ |
266 | check_gethostbyaddr (address: "192.0.2.1" , |
267 | expected: "name: unique-0.cnames-0.addresses-1.example\n" |
268 | "address: 192.0.2.1\n" ); |
269 | check_getnameinfo (address: "192.0.2.1" , |
270 | expected: "unique-0.cnames-0.addresses-1.example" ); |
271 | check_gethostbyaddr (address: "192.0.2.17" , |
272 | expected: "name: unique-0.cnames-1.addresses-1.example\n" |
273 | "address: 192.0.2.17\n" ); |
274 | check_getnameinfo (address: "192.0.2.17" , |
275 | expected: "unique-0.cnames-1.addresses-1.example" ); |
276 | check_gethostbyaddr (address: "192.0.2.18" , |
277 | expected: "name: unique-0.cnames-1.addresses-2.example\n" |
278 | "address: 192.0.2.18\n" ); |
279 | check_getnameinfo (address: "192.0.2.18" , |
280 | expected: "unique-0.cnames-1.addresses-2.example" ); |
281 | check_gethostbyaddr (address: "192.0.2.33" , |
282 | expected: "name: unique-0.cnames-2.addresses-1.example\n" |
283 | "address: 192.0.2.33\n" ); |
284 | check_getnameinfo (address: "192.0.2.33" , |
285 | expected: "unique-0.cnames-2.addresses-1.example" ); |
286 | check_gethostbyaddr (address: "192.0.2.34" , |
287 | expected: "name: unique-0.cnames-2.addresses-2.example\n" |
288 | "address: 192.0.2.34\n" ); |
289 | check_getnameinfo (address: "192.0.2.34" , |
290 | expected: "unique-0.cnames-2.addresses-2.example" ); |
291 | |
292 | /* Same for IPv6 addresses. */ |
293 | check_gethostbyaddr (address: "2001:db8::1" , |
294 | expected: "name: unique-0.cnames-0.addresses-1.example\n" |
295 | "address: 2001:db8::1\n" ); |
296 | check_getnameinfo (address: "2001:db8::1" , |
297 | expected: "unique-0.cnames-0.addresses-1.example" ); |
298 | check_gethostbyaddr (address: "2001:db8::11" , |
299 | expected: "name: unique-0.cnames-1.addresses-1.example\n" |
300 | "address: 2001:db8::11\n" ); |
301 | check_getnameinfo (address: "2001:db8::11" , |
302 | expected: "unique-0.cnames-1.addresses-1.example" ); |
303 | check_gethostbyaddr (address: "2001:db8::12" , |
304 | expected: "name: unique-0.cnames-1.addresses-2.example\n" |
305 | "address: 2001:db8::12\n" ); |
306 | check_getnameinfo (address: "2001:db8::12" , |
307 | expected: "unique-0.cnames-1.addresses-2.example" ); |
308 | check_gethostbyaddr (address: "2001:db8::21" , |
309 | expected: "name: unique-0.cnames-2.addresses-1.example\n" |
310 | "address: 2001:db8::21\n" ); |
311 | check_getnameinfo (address: "2001:db8::21" , |
312 | expected: "unique-0.cnames-2.addresses-1.example" ); |
313 | check_gethostbyaddr (address: "2001:db8::22" , |
314 | expected: "name: unique-0.cnames-2.addresses-2.example\n" |
315 | "address: 2001:db8::22\n" ); |
316 | check_getnameinfo (address: "2001:db8::22" , |
317 | expected: "unique-0.cnames-2.addresses-2.example" ); |
318 | } |
319 | |
320 | resolv_test_end (obj); |
321 | |
322 | support_next_to_fault_free (&ntf); |
323 | return 0; |
324 | } |
325 | |
326 | #include <support/test-driver.c> |
327 | |