1 | /* Determine protocol families for which interfaces exist. Linux version. |
2 | Copyright (C) 2003-2016 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 | <http://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 (NULL); |
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 | (void *) &nladdr, sizeof (nladdr), |
162 | &iov, 1, |
163 | NULL, 0, |
164 | 0 |
165 | }; |
166 | |
167 | ssize_t read_len = TEMP_FAILURE_RETRY (__recvmsg (fd, &msg, 0)); |
168 | __netlink_assert_response (fd, read_len); |
169 | if (read_len < 0) |
170 | goto out_fail; |
171 | |
172 | if (msg.msg_flags & MSG_TRUNC) |
173 | goto out_fail; |
174 | |
175 | struct nlmsghdr *nlmh; |
176 | for (nlmh = (struct nlmsghdr *) buf; |
177 | NLMSG_OK (nlmh, (size_t) read_len); |
178 | nlmh = (struct nlmsghdr *) NLMSG_NEXT (nlmh, read_len)) |
179 | { |
180 | if (nladdr.nl_pid != 0 || (pid_t) nlmh->nlmsg_pid != pid |
181 | || nlmh->nlmsg_seq != req.nlh.nlmsg_seq) |
182 | continue; |
183 | |
184 | if (nlmh->nlmsg_type == RTM_NEWADDR) |
185 | { |
186 | struct ifaddrmsg *ifam = (struct ifaddrmsg *) NLMSG_DATA (nlmh); |
187 | struct rtattr *rta = IFA_RTA (ifam); |
188 | size_t len = nlmh->nlmsg_len - NLMSG_LENGTH (sizeof (*ifam)); |
189 | |
190 | if (ifam->ifa_family != AF_INET |
191 | && ifam->ifa_family != AF_INET6) |
192 | continue; |
193 | |
194 | const void *local = NULL; |
195 | const void *address = NULL; |
196 | while (RTA_OK (rta, len)) |
197 | { |
198 | switch (rta->rta_type) |
199 | { |
200 | case IFA_LOCAL: |
201 | local = RTA_DATA (rta); |
202 | break; |
203 | |
204 | case IFA_ADDRESS: |
205 | address = RTA_DATA (rta); |
206 | goto out; |
207 | } |
208 | |
209 | rta = RTA_NEXT (rta, len); |
210 | } |
211 | |
212 | if (local != NULL) |
213 | { |
214 | address = local; |
215 | out: |
216 | if (ifam->ifa_family == AF_INET) |
217 | { |
218 | if (*(const in_addr_t *) address |
219 | != htonl (INADDR_LOOPBACK)) |
220 | seen_ipv4 = true; |
221 | } |
222 | else |
223 | { |
224 | if (!IN6_IS_ADDR_LOOPBACK (address)) |
225 | seen_ipv6 = true; |
226 | } |
227 | } |
228 | |
229 | if (result_len == 0 || result_len == result_cap) |
230 | { |
231 | result_cap = 2 * result_cap; |
232 | result = realloc (result, sizeof (*result) |
233 | + result_cap |
234 | * sizeof (struct in6addrinfo)); |
235 | } |
236 | |
237 | if (!result) |
238 | goto out_fail; |
239 | |
240 | struct in6addrinfo *info = &result->in6ai[result_len++]; |
241 | |
242 | info->flags = (((ifam->ifa_flags |
243 | & (IFA_F_DEPRECATED | IFA_F_OPTIMISTIC)) |
244 | ? in6ai_deprecated : 0) |
245 | | ((ifam->ifa_flags & IFA_F_HOMEADDRESS) |
246 | ? in6ai_homeaddress : 0)); |
247 | info->prefixlen = ifam->ifa_prefixlen; |
248 | info->index = ifam->ifa_index; |
249 | if (ifam->ifa_family == AF_INET) |
250 | { |
251 | info->addr[0] = 0; |
252 | info->addr[1] = 0; |
253 | info->addr[2] = htonl (0xffff); |
254 | info->addr[3] = *(const in_addr_t *) address; |
255 | } |
256 | else |
257 | memcpy (info->addr, address, sizeof (info->addr)); |
258 | } |
259 | else if (nlmh->nlmsg_type == NLMSG_DONE) |
260 | /* We found the end, leave the loop. */ |
261 | done = true; |
262 | } |
263 | } |
264 | while (! done); |
265 | |
266 | if (seen_ipv6 && result != NULL) |
267 | { |
268 | result->timestamp = get_nl_timestamp (); |
269 | result->usecnt = 2; |
270 | result->seen_ipv4 = seen_ipv4; |
271 | result->seen_ipv6 = true; |
272 | result->in6ailen = result_len; |
273 | } |
274 | else |
275 | { |
276 | free (result); |
277 | |
278 | atomic_add (&noai6ai_cached.usecnt, 2); |
279 | noai6ai_cached.seen_ipv4 = seen_ipv4; |
280 | noai6ai_cached.seen_ipv6 = seen_ipv6; |
281 | result = &noai6ai_cached; |
282 | } |
283 | |
284 | return result; |
285 | |
286 | out_fail: |
287 | |
288 | free (result); |
289 | return NULL; |
290 | } |
291 | |
292 | |
293 | void |
294 | attribute_hidden |
295 | __check_pf (bool *seen_ipv4, bool *seen_ipv6, |
296 | struct in6addrinfo **in6ai, size_t *in6ailen) |
297 | { |
298 | *in6ai = NULL; |
299 | *in6ailen = 0; |
300 | |
301 | struct cached_data *olddata = NULL; |
302 | struct cached_data *data = NULL; |
303 | |
304 | __libc_lock_lock (lock); |
305 | |
306 | if (cache_valid_p ()) |
307 | { |
308 | data = cache; |
309 | atomic_increment (&cache->usecnt); |
310 | } |
311 | else |
312 | { |
313 | int fd = __socket (PF_NETLINK, SOCK_RAW, 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, (struct sockaddr *) &nladdr, sizeof (nladdr)) == 0 |
324 | && __getsockname (fd, (struct sockaddr *) &nladdr, |
325 | &addr_len) == 0) |
326 | data = make_request (fd, nladdr.nl_pid); |
327 | |
328 | close_not_cancel_no_status (fd); |
329 | } |
330 | |
331 | if (data != NULL) |
332 | { |
333 | olddata = cache; |
334 | cache = data; |
335 | } |
336 | } |
337 | |
338 | __libc_lock_unlock (lock); |
339 | |
340 | if (data != NULL) |
341 | { |
342 | /* It worked. */ |
343 | *seen_ipv4 = data->seen_ipv4; |
344 | *seen_ipv6 = data->seen_ipv6; |
345 | *in6ailen = data->in6ailen; |
346 | *in6ai = data->in6ai; |
347 | |
348 | if (olddata != NULL && olddata->usecnt > 0 |
349 | && atomic_add_zero (&olddata->usecnt, -1)) |
350 | free (olddata); |
351 | |
352 | return; |
353 | } |
354 | |
355 | /* We cannot determine what interfaces are available. Be |
356 | pessimistic. */ |
357 | *seen_ipv4 = true; |
358 | *seen_ipv6 = true; |
359 | } |
360 | |
361 | /* Free the cache if it has been allocated. */ |
362 | libc_freeres_fn (freecache) |
363 | { |
364 | if (cache) |
365 | __free_in6ai (cache->in6ai); |
366 | } |
367 | |
368 | void |
369 | __free_in6ai (struct in6addrinfo *ai) |
370 | { |
371 | if (ai != NULL) |
372 | { |
373 | struct cached_data *data = |
374 | (struct cached_data *) ((char *) ai |
375 | - offsetof (struct cached_data, in6ai)); |
376 | |
377 | if (atomic_add_zero (&data->usecnt, -1)) |
378 | { |
379 | __libc_lock_lock (lock); |
380 | |
381 | if (data->usecnt == 0) |
382 | /* Still unused. */ |
383 | free (data); |
384 | |
385 | __libc_lock_unlock (lock); |
386 | } |
387 | } |
388 | } |
389 | |