1 | /* Determine protocol families for which interfaces exist. Linux version. |
2 | Copyright (C) 2003-2022 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_increment_val (&nl_timestamp) == 0) |
76 | atomic_increment (&nl_timestamp); |
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_add (&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 | |
296 | void |
297 | attribute_hidden |
298 | __check_pf (bool *seen_ipv4, bool *seen_ipv6, |
299 | struct in6addrinfo **in6ai, size_t *in6ailen) |
300 | { |
301 | *in6ai = NULL; |
302 | *in6ailen = 0; |
303 | |
304 | struct cached_data *olddata = NULL; |
305 | struct cached_data *data = NULL; |
306 | |
307 | __libc_lock_lock (lock); |
308 | |
309 | if (cache_valid_p ()) |
310 | { |
311 | data = cache; |
312 | atomic_increment (&cache->usecnt); |
313 | } |
314 | else |
315 | { |
316 | int fd = __socket (PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); |
317 | |
318 | if (__glibc_likely (fd >= 0)) |
319 | { |
320 | struct sockaddr_nl nladdr; |
321 | memset (&nladdr, '\0', sizeof (nladdr)); |
322 | nladdr.nl_family = AF_NETLINK; |
323 | |
324 | socklen_t addr_len = sizeof (nladdr); |
325 | |
326 | if (__bind (fd, (struct sockaddr *) &nladdr, sizeof (nladdr)) == 0 |
327 | && __getsockname (fd, (struct sockaddr *) &nladdr, |
328 | &addr_len) == 0) |
329 | data = make_request (fd, nladdr.nl_pid); |
330 | |
331 | __close_nocancel_nostatus (fd); |
332 | } |
333 | |
334 | if (data != NULL) |
335 | { |
336 | olddata = cache; |
337 | cache = data; |
338 | } |
339 | } |
340 | |
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_add_zero (&olddata->usecnt, -1)) |
353 | free (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 | libc_freeres_fn (freecache) |
366 | { |
367 | if (cache) |
368 | __free_in6ai (cache->in6ai); |
369 | } |
370 | |
371 | void |
372 | __free_in6ai (struct in6addrinfo *ai) |
373 | { |
374 | if (ai != NULL) |
375 | { |
376 | struct cached_data *data = |
377 | (struct cached_data *) ((char *) ai |
378 | - offsetof (struct cached_data, in6ai)); |
379 | |
380 | if (atomic_add_zero (&data->usecnt, -1)) |
381 | { |
382 | __libc_lock_lock (lock); |
383 | |
384 | if (data->usecnt == 0) |
385 | /* Still unused. */ |
386 | free (data); |
387 | |
388 | __libc_lock_unlock (lock); |
389 | } |
390 | } |
391 | } |
392 | |