1 | /* Determine protocol families for which interfaces exist. Linux version. |
2 | Copyright (C) 2003-2023 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 <assert.h> |
20 | #include <errno.h> |
21 | #include <ifaddrs.h> |
22 | #include <netdb.h> |
23 | #include <stddef.h> |
24 | #include <string.h> |
25 | #include <time.h> |
26 | #include <unistd.h> |
27 | #include <stdint.h> |
28 | #include <sys/socket.h> |
29 | |
30 | #include <asm/types.h> |
31 | #include <linux/netlink.h> |
32 | #include <linux/rtnetlink.h> |
33 | |
34 | #include <not-cancel.h> |
35 | #include <libc-lock.h> |
36 | #include <atomic.h> |
37 | #include <nscd/nscd-client.h> |
38 | |
39 | #include "netlinkaccess.h" |
40 | |
41 | #ifndef IFA_F_HOMEADDRESS |
42 | # define IFA_F_HOMEADDRESS 0 |
43 | #endif |
44 | #ifndef IFA_F_OPTIMISTIC |
45 | # define IFA_F_OPTIMISTIC 0 |
46 | #endif |
47 | |
48 | |
49 | struct cached_data |
50 | { |
51 | uint32_t timestamp; |
52 | uint32_t usecnt; |
53 | bool seen_ipv4; |
54 | bool seen_ipv6; |
55 | size_t in6ailen; |
56 | struct in6addrinfo in6ai[0]; |
57 | }; |
58 | |
59 | static struct cached_data noai6ai_cached = |
60 | { |
61 | .usecnt = 1, /* Make sure we never try to delete this entry. */ |
62 | .in6ailen = 0 |
63 | }; |
64 | |
65 | static struct cached_data *cache; |
66 | __libc_lock_define_initialized (static, lock); |
67 | |
68 | |
69 | #if IS_IN (nscd) |
70 | static uint32_t nl_timestamp; |
71 | |
72 | uint32_t |
73 | __bump_nl_timestamp (void) |
74 | { |
75 | if (atomic_fetch_add_relaxed (&nl_timestamp, 1) + 1 == 0) |
76 | atomic_fetch_add_relaxed (&nl_timestamp, 1); |
77 | |
78 | return nl_timestamp; |
79 | } |
80 | #endif |
81 | |
82 | static inline uint32_t |
83 | get_nl_timestamp (void) |
84 | { |
85 | #if IS_IN (nscd) |
86 | return nl_timestamp; |
87 | #elif defined USE_NSCD |
88 | return __nscd_get_nl_timestamp (); |
89 | #else |
90 | return 0; |
91 | #endif |
92 | } |
93 | |
94 | static inline bool |
95 | cache_valid_p (void) |
96 | { |
97 | if (cache != NULL) |
98 | { |
99 | uint32_t timestamp = get_nl_timestamp (); |
100 | return timestamp != 0 && cache->timestamp == timestamp; |
101 | } |
102 | return false; |
103 | } |
104 | |
105 | |
106 | static struct cached_data * |
107 | make_request (int fd, pid_t pid) |
108 | { |
109 | struct cached_data *result = NULL; |
110 | |
111 | size_t result_len = 0; |
112 | size_t result_cap = 32; |
113 | |
114 | struct req |
115 | { |
116 | struct nlmsghdr nlh; |
117 | struct rtgenmsg g; |
118 | /* struct rtgenmsg consists of a single byte. This means there |
119 | are three bytes of padding included in the REQ definition. |
120 | We make them explicit here. */ |
121 | char pad[3]; |
122 | } req; |
123 | struct sockaddr_nl nladdr; |
124 | |
125 | req.nlh.nlmsg_len = sizeof (req); |
126 | req.nlh.nlmsg_type = RTM_GETADDR; |
127 | req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; |
128 | req.nlh.nlmsg_pid = 0; |
129 | req.nlh.nlmsg_seq = time_now (); |
130 | req.g.rtgen_family = AF_UNSPEC; |
131 | |
132 | assert (sizeof (req) - offsetof (struct req, pad) == 3); |
133 | memset (req.pad, '\0', sizeof (req.pad)); |
134 | |
135 | memset (&nladdr, '\0', sizeof (nladdr)); |
136 | nladdr.nl_family = AF_NETLINK; |
137 | |
138 | #ifdef PAGE_SIZE |
139 | const size_t buf_size = PAGE_SIZE; |
140 | #else |
141 | const size_t buf_size = 4096; |
142 | #endif |
143 | char buf[buf_size]; |
144 | |
145 | struct iovec iov = { buf, buf_size }; |
146 | |
147 | if (TEMP_FAILURE_RETRY (__sendto (fd, (void *) &req, sizeof (req), 0, |
148 | (struct sockaddr *) &nladdr, |
149 | sizeof (nladdr))) < 0) |
150 | goto out_fail; |
151 | |
152 | bool done = false; |
153 | |
154 | bool seen_ipv4 = false; |
155 | bool seen_ipv6 = false; |
156 | |
157 | do |
158 | { |
159 | struct msghdr msg = |
160 | { |
161 | .msg_name = (void *) &nladdr, |
162 | .msg_namelen = sizeof (nladdr), |
163 | .msg_iov = &iov, |
164 | .msg_iovlen = 1, |
165 | .msg_control = NULL, |
166 | .msg_controllen = 0, |
167 | .msg_flags = 0 |
168 | }; |
169 | |
170 | ssize_t read_len = TEMP_FAILURE_RETRY (__recvmsg (fd, &msg, 0)); |
171 | __netlink_assert_response (fd, read_len); |
172 | if (read_len < 0) |
173 | goto out_fail; |
174 | |
175 | if (msg.msg_flags & MSG_TRUNC) |
176 | goto out_fail; |
177 | |
178 | struct nlmsghdr *nlmh; |
179 | for (nlmh = (struct nlmsghdr *) buf; |
180 | NLMSG_OK (nlmh, (size_t) read_len); |
181 | nlmh = (struct nlmsghdr *) NLMSG_NEXT (nlmh, read_len)) |
182 | { |
183 | if (nladdr.nl_pid != 0 || (pid_t) nlmh->nlmsg_pid != pid |
184 | || nlmh->nlmsg_seq != req.nlh.nlmsg_seq) |
185 | continue; |
186 | |
187 | if (nlmh->nlmsg_type == RTM_NEWADDR) |
188 | { |
189 | struct ifaddrmsg *ifam = (struct ifaddrmsg *) NLMSG_DATA (nlmh); |
190 | struct rtattr *rta = IFA_RTA (ifam); |
191 | size_t len = nlmh->nlmsg_len - NLMSG_LENGTH (sizeof (*ifam)); |
192 | |
193 | if (ifam->ifa_family != AF_INET |
194 | && ifam->ifa_family != AF_INET6) |
195 | continue; |
196 | |
197 | const void *local = NULL; |
198 | const void *address = NULL; |
199 | while (RTA_OK (rta, len)) |
200 | { |
201 | switch (rta->rta_type) |
202 | { |
203 | case IFA_LOCAL: |
204 | local = RTA_DATA (rta); |
205 | break; |
206 | |
207 | case IFA_ADDRESS: |
208 | address = RTA_DATA (rta); |
209 | goto out; |
210 | } |
211 | |
212 | rta = RTA_NEXT (rta, len); |
213 | } |
214 | |
215 | if (local != NULL) |
216 | { |
217 | address = local; |
218 | out: |
219 | if (ifam->ifa_family == AF_INET) |
220 | { |
221 | if (*(const in_addr_t *) address |
222 | != htonl (INADDR_LOOPBACK)) |
223 | seen_ipv4 = true; |
224 | } |
225 | else |
226 | { |
227 | if (!IN6_IS_ADDR_LOOPBACK (address)) |
228 | seen_ipv6 = true; |
229 | } |
230 | } |
231 | |
232 | if (result_len == 0 || result_len == result_cap) |
233 | { |
234 | result_cap = 2 * result_cap; |
235 | result = realloc (result, sizeof (*result) |
236 | + result_cap |
237 | * sizeof (struct in6addrinfo)); |
238 | } |
239 | |
240 | if (!result) |
241 | goto out_fail; |
242 | |
243 | struct in6addrinfo *info = &result->in6ai[result_len++]; |
244 | |
245 | info->flags = (((ifam->ifa_flags |
246 | & (IFA_F_DEPRECATED | IFA_F_OPTIMISTIC)) |
247 | ? in6ai_deprecated : 0) |
248 | | ((ifam->ifa_flags & IFA_F_HOMEADDRESS) |
249 | ? in6ai_homeaddress : 0)); |
250 | info->prefixlen = ifam->ifa_prefixlen; |
251 | info->index = ifam->ifa_index; |
252 | if (ifam->ifa_family == AF_INET) |
253 | { |
254 | info->addr[0] = 0; |
255 | info->addr[1] = 0; |
256 | info->addr[2] = htonl (0xffff); |
257 | info->addr[3] = *(const in_addr_t *) address; |
258 | } |
259 | else |
260 | memcpy (info->addr, address, sizeof (info->addr)); |
261 | } |
262 | else if (nlmh->nlmsg_type == NLMSG_DONE) |
263 | /* We found the end, leave the loop. */ |
264 | done = true; |
265 | } |
266 | } |
267 | while (! done); |
268 | |
269 | if (seen_ipv6 && result != NULL) |
270 | { |
271 | result->timestamp = get_nl_timestamp (); |
272 | result->usecnt = 2; |
273 | result->seen_ipv4 = seen_ipv4; |
274 | result->seen_ipv6 = true; |
275 | result->in6ailen = result_len; |
276 | } |
277 | else |
278 | { |
279 | free (result); |
280 | |
281 | atomic_fetch_add_relaxed (&noai6ai_cached.usecnt, 2); |
282 | noai6ai_cached.seen_ipv4 = seen_ipv4; |
283 | noai6ai_cached.seen_ipv6 = seen_ipv6; |
284 | result = &noai6ai_cached; |
285 | } |
286 | |
287 | return result; |
288 | |
289 | out_fail: |
290 | |
291 | free (result); |
292 | return NULL; |
293 | } |
294 | |
295 | #ifdef __EXCEPTIONS |
296 | static void |
297 | cancel_handler (void *arg __attribute__((unused))) |
298 | { |
299 | /* Release the lock. */ |
300 | __libc_lock_unlock (lock); |
301 | } |
302 | #endif |
303 | |
304 | void |
305 | attribute_hidden |
306 | __check_pf (bool *seen_ipv4, bool *seen_ipv6, |
307 | struct in6addrinfo **in6ai, size_t *in6ailen) |
308 | { |
309 | *in6ai = NULL; |
310 | *in6ailen = 0; |
311 | |
312 | struct cached_data *olddata = NULL; |
313 | struct cached_data *data = NULL; |
314 | |
315 | #ifdef __EXCEPTIONS |
316 | /* Make sure that lock is released when the thread is cancelled. */ |
317 | __libc_cleanup_push (cancel_handler, NULL); |
318 | #endif |
319 | __libc_lock_lock (lock); |
320 | |
321 | if (cache_valid_p ()) |
322 | { |
323 | data = cache; |
324 | atomic_fetch_add_relaxed (&cache->usecnt, 1); |
325 | } |
326 | else |
327 | { |
328 | int fd = __socket (PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); |
329 | |
330 | if (__glibc_likely (fd >= 0)) |
331 | { |
332 | struct sockaddr_nl nladdr; |
333 | memset (&nladdr, '\0', sizeof (nladdr)); |
334 | nladdr.nl_family = AF_NETLINK; |
335 | |
336 | socklen_t addr_len = sizeof (nladdr); |
337 | |
338 | if (__bind (fd, (struct sockaddr *) &nladdr, sizeof (nladdr)) == 0 |
339 | && __getsockname (fd, (struct sockaddr *) &nladdr, |
340 | &addr_len) == 0) |
341 | data = make_request (fd, nladdr.nl_pid); |
342 | |
343 | __close_nocancel_nostatus (fd); |
344 | } |
345 | |
346 | if (data != NULL) |
347 | { |
348 | olddata = cache; |
349 | cache = data; |
350 | } |
351 | } |
352 | |
353 | #ifdef __EXCEPTIONS |
354 | __libc_cleanup_pop (0); |
355 | #endif |
356 | __libc_lock_unlock (lock); |
357 | |
358 | if (data != NULL) |
359 | { |
360 | /* It worked. */ |
361 | *seen_ipv4 = data->seen_ipv4; |
362 | *seen_ipv6 = data->seen_ipv6; |
363 | *in6ailen = data->in6ailen; |
364 | *in6ai = data->in6ai; |
365 | |
366 | if (olddata != NULL && olddata->usecnt > 0 |
367 | && atomic_fetch_add_relaxed (&olddata->usecnt, -1) == 1) |
368 | free (olddata); |
369 | |
370 | return; |
371 | } |
372 | |
373 | /* We cannot determine what interfaces are available. Be |
374 | pessimistic. */ |
375 | *seen_ipv4 = true; |
376 | *seen_ipv6 = true; |
377 | } |
378 | |
379 | /* Free the cache if it has been allocated. */ |
380 | void |
381 | __check_pf_freemem (void) |
382 | { |
383 | if (cache) |
384 | __free_in6ai (cache->in6ai); |
385 | } |
386 | |
387 | void |
388 | __free_in6ai (struct in6addrinfo *ai) |
389 | { |
390 | if (ai != NULL) |
391 | { |
392 | struct cached_data *data = |
393 | (struct cached_data *) ((char *) ai |
394 | - offsetof (struct cached_data, in6ai)); |
395 | |
396 | if (atomic_fetch_add_relaxed (&data->usecnt, -1) == 1) |
397 | { |
398 | __libc_lock_lock (lock); |
399 | |
400 | if (data->usecnt == 0) |
401 | /* Still unused. */ |
402 | free (data); |
403 | |
404 | __libc_lock_unlock (lock); |
405 | } |
406 | } |
407 | } |
408 | |