]>
Commit | Line | Data |
---|---|---|
1dc869d1 | 1 | /* Determine protocol families for which interfaces exist. Linux version. |
d4697bc9 | 2 | Copyright (C) 2003-2014 Free Software Foundation, Inc. |
1dc869d1 UD |
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 | |
59ba27a6 PE |
16 | License along with the GNU C Library; if not, see |
17 | <http://www.gnu.org/licenses/>. */ | |
1dc869d1 | 18 | |
b74a8c7f | 19 | #include <assert.h> |
1dc869d1 UD |
20 | #include <errno.h> |
21 | #include <ifaddrs.h> | |
22 | #include <netdb.h> | |
b74a8c7f | 23 | #include <stddef.h> |
1dc869d1 UD |
24 | #include <string.h> |
25 | #include <time.h> | |
26 | #include <unistd.h> | |
e054f494 | 27 | #include <stdint.h> |
1dc869d1 UD |
28 | #include <sys/socket.h> |
29 | ||
30 | #include <asm/types.h> | |
31 | #include <linux/netlink.h> | |
32 | #include <linux/rtnetlink.h> | |
33 | ||
3af48b5b | 34 | #include <not-cancel.h> |
6ddd37a4 | 35 | #include <kernel-features.h> |
3a2c0242 UD |
36 | #include <bits/libc-lock.h> |
37 | #include <atomic.h> | |
38 | #include <nscd/nscd-client.h> | |
1dc869d1 UD |
39 | |
40 | ||
457b559e UD |
41 | #ifndef IFA_F_HOMEADDRESS |
42 | # define IFA_F_HOMEADDRESS 0 | |
43 | #endif | |
de4d8563 UD |
44 | #ifndef IFA_F_OPTIMISTIC |
45 | # define IFA_F_OPTIMISTIC 0 | |
46 | #endif | |
3af48b5b UD |
47 | |
48 | ||
3a2c0242 UD |
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 | { | |
09f93bd3 | 61 | .usecnt = 1, /* Make sure we never try to delete this entry. */ |
3a2c0242 UD |
62 | .in6ailen = 0 |
63 | }; | |
64 | ||
2523c62b | 65 | libc_freeres_ptr (static struct cached_data *cache); |
3a2c0242 UD |
66 | __libc_lock_define_initialized (static, lock); |
67 | ||
68 | ||
69 | #ifdef 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 | ||
3cc3ef96 RM |
82 | static inline uint32_t |
83 | get_nl_timestamp (void) | |
84 | { | |
85 | #ifdef 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 | ||
3a2c0242 UD |
105 | |
106 | static struct cached_data * | |
107 | make_request (int fd, pid_t pid) | |
1dc869d1 | 108 | { |
b74a8c7f | 109 | struct req |
1dc869d1 UD |
110 | { |
111 | struct nlmsghdr nlh; | |
112 | struct rtgenmsg g; | |
b74a8c7f UD |
113 | /* struct rtgenmsg consists of a single byte. This means there |
114 | are three bytes of padding included in the REQ definition. | |
115 | We make them explicit here. */ | |
116 | char pad[3]; | |
1dc869d1 UD |
117 | } req; |
118 | struct sockaddr_nl nladdr; | |
119 | ||
120 | req.nlh.nlmsg_len = sizeof (req); | |
121 | req.nlh.nlmsg_type = RTM_GETADDR; | |
122 | req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; | |
123 | req.nlh.nlmsg_pid = 0; | |
124 | req.nlh.nlmsg_seq = time (NULL); | |
125 | req.g.rtgen_family = AF_UNSPEC; | |
126 | ||
b74a8c7f UD |
127 | assert (sizeof (req) - offsetof (struct req, pad) == 3); |
128 | memset (req.pad, '\0', sizeof (req.pad)); | |
129 | ||
1dc869d1 UD |
130 | memset (&nladdr, '\0', sizeof (nladdr)); |
131 | nladdr.nl_family = AF_NETLINK; | |
132 | ||
6cb988fa UD |
133 | #ifdef PAGE_SIZE |
134 | /* Help the compiler optimize out the malloc call if PAGE_SIZE | |
135 | is constant and smaller or equal to PTHREAD_STACK_MIN/4. */ | |
136 | const size_t buf_size = PAGE_SIZE; | |
137 | #else | |
138 | const size_t buf_size = __getpagesize (); | |
139 | #endif | |
140 | bool use_malloc = false; | |
141 | char *buf; | |
142 | ||
143 | if (__libc_use_alloca (buf_size)) | |
144 | buf = alloca (buf_size); | |
145 | else | |
146 | { | |
147 | buf = malloc (buf_size); | |
148 | if (buf != NULL) | |
149 | use_malloc = true; | |
150 | else | |
151 | goto out_fail; | |
152 | } | |
153 | ||
154 | struct iovec iov = { buf, buf_size }; | |
155 | ||
1dc869d1 UD |
156 | if (TEMP_FAILURE_RETRY (__sendto (fd, (void *) &req, sizeof (req), 0, |
157 | (struct sockaddr *) &nladdr, | |
158 | sizeof (nladdr))) < 0) | |
6cb988fa | 159 | goto out_fail; |
1dc869d1 | 160 | |
1dc869d1 | 161 | bool done = false; |
3af48b5b UD |
162 | struct in6ailist |
163 | { | |
164 | struct in6addrinfo info; | |
165 | struct in6ailist *next; | |
166 | } *in6ailist = NULL; | |
167 | size_t in6ailistlen = 0; | |
3a2c0242 UD |
168 | bool seen_ipv4 = false; |
169 | bool seen_ipv6 = false; | |
1dc869d1 UD |
170 | |
171 | do | |
172 | { | |
173 | struct msghdr msg = | |
174 | { | |
175 | (void *) &nladdr, sizeof (nladdr), | |
176 | &iov, 1, | |
177 | NULL, 0, | |
178 | 0 | |
179 | }; | |
180 | ||
181 | ssize_t read_len = TEMP_FAILURE_RETRY (__recvmsg (fd, &msg, 0)); | |
182 | if (read_len < 0) | |
6cb988fa | 183 | goto out_fail; |
1dc869d1 UD |
184 | |
185 | if (msg.msg_flags & MSG_TRUNC) | |
6cb988fa | 186 | goto out_fail; |
1dc869d1 UD |
187 | |
188 | struct nlmsghdr *nlmh; | |
189 | for (nlmh = (struct nlmsghdr *) buf; | |
190 | NLMSG_OK (nlmh, (size_t) read_len); | |
191 | nlmh = (struct nlmsghdr *) NLMSG_NEXT (nlmh, read_len)) | |
192 | { | |
25ce4c6b | 193 | if (nladdr.nl_pid != 0 || (pid_t) nlmh->nlmsg_pid != pid |
1dc869d1 UD |
194 | || nlmh->nlmsg_seq != req.nlh.nlmsg_seq) |
195 | continue; | |
196 | ||
197 | if (nlmh->nlmsg_type == RTM_NEWADDR) | |
198 | { | |
199 | struct ifaddrmsg *ifam = (struct ifaddrmsg *) NLMSG_DATA (nlmh); | |
df18fdb9 UD |
200 | struct rtattr *rta = IFA_RTA (ifam); |
201 | size_t len = nlmh->nlmsg_len - NLMSG_LENGTH (sizeof (*ifam)); | |
1dc869d1 | 202 | |
6f3914d5 UD |
203 | if (ifam->ifa_family != AF_INET |
204 | && ifam->ifa_family != AF_INET6) | |
205 | continue; | |
df18fdb9 | 206 | |
6f3914d5 UD |
207 | const void *local = NULL; |
208 | const void *address = NULL; | |
209 | while (RTA_OK (rta, len)) | |
210 | { | |
211 | switch (rta->rta_type) | |
df18fdb9 | 212 | { |
6f3914d5 UD |
213 | case IFA_LOCAL: |
214 | local = RTA_DATA (rta); | |
215 | break; | |
216 | ||
217 | case IFA_ADDRESS: | |
218 | address = RTA_DATA (rta); | |
219 | goto out; | |
df18fdb9 UD |
220 | } |
221 | ||
6f3914d5 UD |
222 | rta = RTA_NEXT (rta, len); |
223 | } | |
224 | ||
225 | if (local != NULL) | |
226 | { | |
227 | address = local; | |
228 | out: | |
ad586879 | 229 | if (ifam->ifa_family == AF_INET) |
df18fdb9 | 230 | { |
6f3914d5 | 231 | if (*(const in_addr_t *) address |
df18fdb9 | 232 | != htonl (INADDR_LOOPBACK)) |
3a2c0242 | 233 | seen_ipv4 = true; |
df18fdb9 | 234 | } |
6f3914d5 | 235 | else |
df18fdb9 | 236 | { |
6f3914d5 | 237 | if (!IN6_IS_ADDR_LOOPBACK (address)) |
3a2c0242 | 238 | seen_ipv6 = true; |
df18fdb9 | 239 | } |
6f3914d5 | 240 | } |
3af48b5b | 241 | |
6f3914d5 UD |
242 | struct in6ailist *newp = alloca (sizeof (*newp)); |
243 | newp->info.flags = (((ifam->ifa_flags | |
244 | & (IFA_F_DEPRECATED | |
245 | | IFA_F_OPTIMISTIC)) | |
246 | ? in6ai_deprecated : 0) | |
6f3914d5 UD |
247 | | ((ifam->ifa_flags |
248 | & IFA_F_HOMEADDRESS) | |
249 | ? in6ai_homeaddress : 0)); | |
250 | newp->info.prefixlen = ifam->ifa_prefixlen; | |
773e79b3 | 251 | newp->info.index = ifam->ifa_index; |
6f3914d5 UD |
252 | if (ifam->ifa_family == AF_INET) |
253 | { | |
254 | newp->info.addr[0] = 0; | |
255 | newp->info.addr[1] = 0; | |
256 | newp->info.addr[2] = htonl (0xffff); | |
257 | newp->info.addr[3] = *(const in_addr_t *) address; | |
1dc869d1 | 258 | } |
6f3914d5 UD |
259 | else |
260 | memcpy (newp->info.addr, address, sizeof (newp->info.addr)); | |
261 | newp->next = in6ailist; | |
262 | in6ailist = newp; | |
263 | ++in6ailistlen; | |
1dc869d1 UD |
264 | } |
265 | else if (nlmh->nlmsg_type == NLMSG_DONE) | |
266 | /* We found the end, leave the loop. */ | |
267 | done = true; | |
1dc869d1 UD |
268 | } |
269 | } | |
270 | while (! done); | |
271 | ||
3a2c0242 UD |
272 | struct cached_data *result; |
273 | if (seen_ipv6 && in6ailist != NULL) | |
3af48b5b | 274 | { |
3a2c0242 UD |
275 | result = malloc (sizeof (*result) |
276 | + in6ailistlen * sizeof (struct in6addrinfo)); | |
277 | if (result == NULL) | |
6cb988fa | 278 | goto out_fail; |
3af48b5b | 279 | |
3cc3ef96 | 280 | result->timestamp = get_nl_timestamp (); |
3a2c0242 UD |
281 | result->usecnt = 2; |
282 | result->seen_ipv4 = seen_ipv4; | |
283 | result->seen_ipv6 = true; | |
284 | result->in6ailen = in6ailistlen; | |
3af48b5b UD |
285 | |
286 | do | |
287 | { | |
3a2c0242 | 288 | result->in6ai[--in6ailistlen] = in6ailist->info; |
3af48b5b UD |
289 | in6ailist = in6ailist->next; |
290 | } | |
291 | while (in6ailist != NULL); | |
292 | } | |
3a2c0242 UD |
293 | else |
294 | { | |
a501a01e | 295 | atomic_add (&noai6ai_cached.usecnt, 2); |
3a2c0242 UD |
296 | noai6ai_cached.seen_ipv4 = seen_ipv4; |
297 | noai6ai_cached.seen_ipv6 = seen_ipv6; | |
298 | result = &noai6ai_cached; | |
299 | } | |
1dc869d1 | 300 | |
6cb988fa UD |
301 | if (use_malloc) |
302 | free (buf); | |
3a2c0242 | 303 | return result; |
6cb988fa UD |
304 | |
305 | out_fail: | |
306 | if (use_malloc) | |
307 | free (buf); | |
3a2c0242 | 308 | return NULL; |
1dc869d1 UD |
309 | } |
310 | ||
311 | ||
1dc869d1 UD |
312 | void |
313 | attribute_hidden | |
3af48b5b UD |
314 | __check_pf (bool *seen_ipv4, bool *seen_ipv6, |
315 | struct in6addrinfo **in6ai, size_t *in6ailen) | |
1dc869d1 | 316 | { |
3af48b5b UD |
317 | *in6ai = NULL; |
318 | *in6ailen = 0; | |
319 | ||
89b4b02f JM |
320 | struct cached_data *olddata = NULL; |
321 | struct cached_data *data = NULL; | |
1dc869d1 | 322 | |
89b4b02f | 323 | __libc_lock_lock (lock); |
3a2c0242 | 324 | |
3cc3ef96 | 325 | if (cache_valid_p ()) |
89b4b02f JM |
326 | { |
327 | data = cache; | |
328 | atomic_increment (&cache->usecnt); | |
329 | } | |
330 | else | |
331 | { | |
332 | int fd = __socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); | |
3a2c0242 | 333 | |
a1ffb40e | 334 | if (__glibc_likely (fd >= 0)) |
89b4b02f JM |
335 | { |
336 | struct sockaddr_nl nladdr; | |
337 | memset (&nladdr, '\0', sizeof (nladdr)); | |
338 | nladdr.nl_family = AF_NETLINK; | |
3a2c0242 | 339 | |
89b4b02f | 340 | socklen_t addr_len = sizeof (nladdr); |
3a2c0242 | 341 | |
89b4b02f JM |
342 | if (__bind (fd, (struct sockaddr *) &nladdr, sizeof (nladdr)) == 0 |
343 | && __getsockname (fd, (struct sockaddr *) &nladdr, | |
344 | &addr_len) == 0) | |
345 | data = make_request (fd, nladdr.nl_pid); | |
3a2c0242 | 346 | |
89b4b02f | 347 | close_not_cancel_no_status (fd); |
3a2c0242 | 348 | } |
1dc869d1 | 349 | |
3a2c0242 UD |
350 | if (data != NULL) |
351 | { | |
89b4b02f JM |
352 | olddata = cache; |
353 | cache = data; | |
636064eb | 354 | } |
1dc869d1 UD |
355 | } |
356 | ||
89b4b02f JM |
357 | __libc_lock_unlock (lock); |
358 | ||
359 | if (data != NULL) | |
1dc869d1 | 360 | { |
89b4b02f JM |
361 | /* It worked. */ |
362 | *seen_ipv4 = data->seen_ipv4; | |
363 | *seen_ipv6 = data->seen_ipv6; | |
364 | *in6ailen = data->in6ailen; | |
365 | *in6ai = data->in6ai; | |
366 | ||
367 | if (olddata != NULL && olddata->usecnt > 0 | |
368 | && atomic_add_zero (&olddata->usecnt, -1)) | |
369 | free (olddata); | |
370 | ||
1dc869d1 UD |
371 | return; |
372 | } | |
373 | ||
89b4b02f JM |
374 | /* We cannot determine what interfaces are available. Be |
375 | pessimistic. */ | |
376 | *seen_ipv4 = true; | |
377 | *seen_ipv6 = true; | |
1dc869d1 | 378 | } |
3a2c0242 UD |
379 | |
380 | ||
381 | void | |
382 | __free_in6ai (struct in6addrinfo *ai) | |
383 | { | |
384 | if (ai != NULL) | |
385 | { | |
386 | struct cached_data *data = | |
387 | (struct cached_data *) ((char *) ai | |
388 | - offsetof (struct cached_data, in6ai)); | |
389 | ||
390 | if (atomic_add_zero (&data->usecnt, -1)) | |
391 | { | |
392 | __libc_lock_lock (lock); | |
393 | ||
394 | if (data->usecnt == 0) | |
395 | /* Still unused. */ | |
396 | free (data); | |
397 | ||
398 | __libc_lock_unlock (lock); | |
399 | } | |
400 | } | |
401 | } |