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