]>
Commit | Line | Data |
---|---|---|
114167a2 | 1 | /* SPDX-License-Identifier: BSD-2-Clause */ |
8cc47ba2 | 2 | /* |
1a140c64 | 3 | * dhcpcd - IPv6 ND handling |
7a13e344 | 4 | * Copyright (c) 2006-2023 Roy Marples <roy@marples.name> |
91cd7324 RM |
5 | * All rights reserved |
6 | ||
7 | * Redistribution and use in source and binary forms, with or without | |
8 | * modification, are permitted provided that the following conditions | |
9 | * are met: | |
10 | * 1. Redistributions of source code must retain the above copyright | |
11 | * notice, this list of conditions and the following disclaimer. | |
12 | * 2. Redistributions in binary form must reproduce the above copyright | |
13 | * notice, this list of conditions and the following disclaimer in the | |
14 | * documentation and/or other materials provided with the distribution. | |
15 | * | |
16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
26 | * SUCH DAMAGE. | |
27 | */ | |
28 | ||
eebe9a18 | 29 | #include <sys/ioctl.h> |
91cd7324 RM |
30 | #include <sys/param.h> |
31 | #include <sys/socket.h> | |
32 | #include <net/if.h> | |
23b899e4 | 33 | #include <net/route.h> |
91cd7324 RM |
34 | #include <netinet/in.h> |
35 | #include <netinet/ip6.h> | |
36 | #include <netinet/icmp6.h> | |
37 | ||
cd09e583 | 38 | #include <assert.h> |
91cd7324 | 39 | #include <errno.h> |
e8c8e9b9 | 40 | #include <fcntl.h> |
91cd7324 RM |
41 | #include <stddef.h> |
42 | #include <stdlib.h> | |
43 | #include <string.h> | |
b586bfbf | 44 | #include <syslog.h> |
baece76a | 45 | #include <unistd.h> |
91cd7324 | 46 | |
3c9cea4b | 47 | #define ELOOP_QUEUE ELOOP_IPV6ND |
91cd7324 | 48 | #include "common.h" |
91cd7324 | 49 | #include "dhcpcd.h" |
a1b1f0a8 | 50 | #include "dhcp-common.h" |
d7555c12 | 51 | #include "dhcp6.h" |
91cd7324 | 52 | #include "eloop.h" |
a3ee6b23 | 53 | #include "if.h" |
eebe9a18 | 54 | #include "ipv6.h" |
e82129a4 | 55 | #include "ipv6nd.h" |
94d1ded9 | 56 | #include "logerr.h" |
65025848 | 57 | #include "privsep.h" |
9aa11487 | 58 | #include "route.h" |
294eff4d | 59 | #include "script.h" |
91cd7324 | 60 | |
d7555c12 RM |
61 | /* Debugging Router Solicitations is a lot of spam, so disable it */ |
62 | //#define DEBUG_RS | |
63 | ||
77450503 RM |
64 | #ifndef ND_RA_FLAG_HOME_AGENT |
65 | #define ND_RA_FLAG_HOME_AGENT 0x20 /* Home Agent flag in RA */ | |
66 | #endif | |
2b6cfac5 RM |
67 | #ifndef ND_RA_FLAG_PROXY |
68 | #define ND_RA_FLAG_PROXY 0x04 /* Proxy */ | |
69 | #endif | |
77450503 RM |
70 | #ifndef ND_OPT_PI_FLAG_ROUTER |
71 | #define ND_OPT_PI_FLAG_ROUTER 0x20 /* Router flag in PI */ | |
72 | #endif | |
73 | ||
f1cf924a DG |
74 | #ifndef ND_OPT_RI |
75 | #define ND_OPT_RI 24 | |
76 | struct nd_opt_ri { /* Route Information option RFC4191 */ | |
77 | uint8_t nd_opt_ri_type; | |
78 | uint8_t nd_opt_ri_len; | |
79 | uint8_t nd_opt_ri_prefixlen; | |
80 | uint8_t nd_opt_ri_flags_reserved; | |
81 | uint32_t nd_opt_ri_lifetime; | |
82 | struct in6_addr nd_opt_ri_prefix; | |
83 | }; | |
84 | __CTASSERT(sizeof(struct nd_opt_ri) == 24); | |
85 | #define OPT_RI_FLAG_PREFERENCE(flags) ((flags & 0x18) >> 3) | |
86 | #endif | |
87 | ||
91cd7324 RM |
88 | #ifndef ND_OPT_RDNSS |
89 | #define ND_OPT_RDNSS 25 | |
90 | struct nd_opt_rdnss { /* RDNSS option RFC 6106 */ | |
91 | uint8_t nd_opt_rdnss_type; | |
92 | uint8_t nd_opt_rdnss_len; | |
93 | uint16_t nd_opt_rdnss_reserved; | |
94 | uint32_t nd_opt_rdnss_lifetime; | |
5d619328 | 95 | /* followed by list of IP prefixes */ |
18fa35e1 RM |
96 | }; |
97 | __CTASSERT(sizeof(struct nd_opt_rdnss) == 8); | |
91cd7324 RM |
98 | #endif |
99 | ||
100 | #ifndef ND_OPT_DNSSL | |
101 | #define ND_OPT_DNSSL 31 | |
102 | struct nd_opt_dnssl { /* DNSSL option RFC 6106 */ | |
103 | uint8_t nd_opt_dnssl_type; | |
104 | uint8_t nd_opt_dnssl_len; | |
105 | uint16_t nd_opt_dnssl_reserved; | |
106 | uint32_t nd_opt_dnssl_lifetime; | |
107 | /* followed by list of DNS servers */ | |
18fa35e1 | 108 | }; |
1dc0ddcd | 109 | __CTASSERT(sizeof(struct nd_opt_dnssl) == 8); |
91cd7324 RM |
110 | #endif |
111 | ||
fd3e7f65 RM |
112 | /* Impossible options, so we can easily add extras */ |
113 | #define _ND_OPT_PREFIX_ADDR 255 + 1 | |
114 | ||
eebe9a18 RM |
115 | /* Minimal IPv6 MTU */ |
116 | #ifndef IPV6_MMTU | |
117 | #define IPV6_MMTU 1280 | |
118 | #endif | |
119 | ||
120 | #ifndef ND_RA_FLAG_RTPREF_HIGH | |
121 | #define ND_RA_FLAG_RTPREF_MASK 0x18 | |
122 | #define ND_RA_FLAG_RTPREF_HIGH 0x08 | |
123 | #define ND_RA_FLAG_RTPREF_MEDIUM 0x00 | |
124 | #define ND_RA_FLAG_RTPREF_LOW 0x18 | |
125 | #define ND_RA_FLAG_RTPREF_RSV 0x10 | |
126 | #endif | |
127 | ||
19005560 RM |
128 | #define EXPIRED_MAX 5 /* Remember 5 expired routers to avoid |
129 | logspam. */ | |
130 | ||
e82129a4 RM |
131 | #define MIN_RANDOM_FACTOR 500 /* millisecs */ |
132 | #define MAX_RANDOM_FACTOR 1500 /* millisecs */ | |
133 | #define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */ | |
134 | #define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */ | |
135 | ||
136 | #if BYTE_ORDER == BIG_ENDIAN | |
137 | #define IPV6_ADDR_INT32_ONE 1 | |
138 | #define IPV6_ADDR_INT16_MLL 0xff02 | |
139 | #elif BYTE_ORDER == LITTLE_ENDIAN | |
140 | #define IPV6_ADDR_INT32_ONE 0x01000000 | |
141 | #define IPV6_ADDR_INT16_MLL 0x02ff | |
142 | #endif | |
143 | ||
144 | /* Debugging Neighbor Solicitations is a lot of spam, so disable it */ | |
145 | //#define DEBUG_NS | |
146 | // | |
147 | ||
5d619328 | 148 | static void ipv6nd_handledata(void *, unsigned short); |
f1cf924a DG |
149 | static struct routeinfo *routeinfo_findalloc(struct ra *, const struct in6_addr *, uint8_t); |
150 | static void routeinfohead_free(struct routeinfohead *); | |
7cece083 | 151 | |
65e5b9f9 RM |
152 | /* |
153 | * Android ships buggy ICMP6 filter headers. | |
154 | * Supply our own until they fix their shit. | |
155 | * References: | |
156 | * https://android-review.googlesource.com/#/c/58438/ | |
157 | * http://code.google.com/p/android/issues/original?id=32621&seq=24 | |
158 | */ | |
159 | #ifdef __ANDROID__ | |
160 | #undef ICMP6_FILTER_WILLPASS | |
161 | #undef ICMP6_FILTER_WILLBLOCK | |
162 | #undef ICMP6_FILTER_SETPASS | |
163 | #undef ICMP6_FILTER_SETBLOCK | |
164 | #undef ICMP6_FILTER_SETPASSALL | |
165 | #undef ICMP6_FILTER_SETBLOCKALL | |
166 | #define ICMP6_FILTER_WILLPASS(type, filterp) \ | |
167 | ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0) | |
168 | #define ICMP6_FILTER_WILLBLOCK(type, filterp) \ | |
169 | ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0) | |
170 | #define ICMP6_FILTER_SETPASS(type, filterp) \ | |
171 | ((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31)))) | |
172 | #define ICMP6_FILTER_SETBLOCK(type, filterp) \ | |
173 | ((((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31)))) | |
174 | #define ICMP6_FILTER_SETPASSALL(filterp) \ | |
175 | memset(filterp, 0, sizeof(struct icmp6_filter)); | |
176 | #define ICMP6_FILTER_SETBLOCKALL(filterp) \ | |
177 | memset(filterp, 0xff, sizeof(struct icmp6_filter)); | |
178 | #endif | |
179 | ||
62247de8 RM |
180 | /* Support older systems with different defines */ |
181 | #if !defined(IPV6_RECVHOPLIMIT) && defined(IPV6_HOPLIMIT) | |
182 | #define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT | |
183 | #endif | |
184 | #if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) | |
185 | #define IPV6_RECVPKTINFO IPV6_PKTINFO | |
186 | #endif | |
187 | ||
f1df29d2 RM |
188 | /* Handy defines */ |
189 | #define ipv6nd_free_ra(ra) ipv6nd_freedrop_ra((ra), 0) | |
190 | #define ipv6nd_drop_ra(ra) ipv6nd_freedrop_ra((ra), 1) | |
191 | ||
2be15e88 RM |
192 | void |
193 | ipv6nd_printoptions(const struct dhcpcd_ctx *ctx, | |
194 | const struct dhcp_opt *opts, size_t opts_len) | |
195 | { | |
196 | size_t i, j; | |
197 | const struct dhcp_opt *opt, *opt2; | |
198 | int cols; | |
199 | ||
200 | for (i = 0, opt = ctx->nd_opts; | |
201 | i < ctx->nd_opts_len; i++, opt++) | |
202 | { | |
203 | for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) | |
204 | if (opt2->option == opt->option) | |
205 | break; | |
206 | if (j == opts_len) { | |
207 | cols = printf("%03d %s", opt->option, opt->var); | |
208 | dhcp_print_option_encoding(opt, cols); | |
209 | } | |
210 | } | |
211 | for (i = 0, opt = opts; i < opts_len; i++, opt++) { | |
212 | cols = printf("%03d %s", opt->option, opt->var); | |
213 | dhcp_print_option_encoding(opt, cols); | |
214 | } | |
215 | } | |
216 | ||
c5445ce8 RM |
217 | int |
218 | ipv6nd_open(bool recv) | |
91cd7324 | 219 | { |
49d6a036 | 220 | int fd, on; |
4eb7b489 | 221 | struct icmp6_filter filt; |
91cd7324 | 222 | |
c5445ce8 | 223 | fd = xsocket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_ICMPV6); |
49d6a036 | 224 | if (fd == -1) |
fbbb0875 RM |
225 | return -1; |
226 | ||
c5445ce8 RM |
227 | ICMP6_FILTER_SETBLOCKALL(&filt); |
228 | ||
cc431339 RM |
229 | /* RFC4861 4.1 */ |
230 | on = 255; | |
49d6a036 | 231 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, |
4eb7b489 | 232 | &on, sizeof(on)) == -1) |
fbbb0875 | 233 | goto eexit; |
91cd7324 | 234 | |
c5445ce8 RM |
235 | if (recv) { |
236 | on = 1; | |
237 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, | |
238 | &on, sizeof(on)) == -1) | |
239 | goto eexit; | |
49d6a036 | 240 | |
c5445ce8 RM |
241 | on = 1; |
242 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, | |
243 | &on, sizeof(on)) == -1) | |
244 | goto eexit; | |
245 | ||
246 | ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); | |
247 | ||
248 | #ifdef SO_RERROR | |
249 | on = 1; | |
250 | if (setsockopt(fd, SOL_SOCKET, SO_RERROR, | |
251 | &on, sizeof(on)) == -1) | |
252 | goto eexit; | |
253 | #endif | |
254 | } | |
91cd7324 | 255 | |
49d6a036 | 256 | if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, |
4eb7b489 | 257 | &filt, sizeof(filt)) == -1) |
fbbb0875 | 258 | goto eexit; |
fbbb0875 | 259 | |
49d6a036 | 260 | return fd; |
e82129a4 RM |
261 | |
262 | eexit: | |
49d6a036 | 263 | close(fd); |
e82129a4 RM |
264 | return -1; |
265 | } | |
266 | ||
b2edc303 | 267 | #ifdef __sun |
835f5ebf | 268 | int |
c5445ce8 | 269 | ipv6nd_openif(struct interface *ifp) |
b2edc303 | 270 | { |
49d6a036 | 271 | int fd; |
b2edc303 | 272 | struct ipv6_mreq mreq = { |
e275f240 | 273 | .ipv6mr_multiaddr = IN6ADDR_LINKLOCAL_ALLNODES_INIT, |
b2edc303 RM |
274 | .ipv6mr_interface = ifp->index |
275 | }; | |
276 | struct rs_state *state = RS_STATE(ifp); | |
6fcd7ff4 | 277 | uint_t ifindex = ifp->index; |
b2edc303 RM |
278 | |
279 | if (state->nd_fd != -1) | |
280 | return state->nd_fd; | |
281 | ||
11101f7b | 282 | fd = ipv6nd_open(true); |
49d6a036 | 283 | if (fd == -1) |
b2edc303 RM |
284 | return -1; |
285 | ||
49d6a036 | 286 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, |
6fcd7ff4 RM |
287 | &ifindex, sizeof(ifindex)) == -1) |
288 | { | |
49d6a036 | 289 | close(fd); |
6fcd7ff4 RM |
290 | return -1; |
291 | } | |
292 | ||
49d6a036 | 293 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, |
b2edc303 RM |
294 | &mreq, sizeof(mreq)) == -1) |
295 | { | |
49d6a036 | 296 | close(fd); |
b2edc303 RM |
297 | return -1; |
298 | } | |
299 | ||
5d619328 RM |
300 | if (eloop_event_add(ifp->ctx->eloop, fd, ELE_READ, |
301 | ipv6nd_handledata, ifp) == -1) | |
302 | { | |
303 | close(fd); | |
304 | return -1; | |
305 | } | |
306 | ||
49d6a036 | 307 | state->nd_fd = fd; |
49d6a036 | 308 | return fd; |
b2edc303 | 309 | } |
b2edc303 RM |
310 | #endif |
311 | ||
e82129a4 RM |
312 | static int |
313 | ipv6nd_makersprobe(struct interface *ifp) | |
91cd7324 | 314 | { |
ca15a0aa | 315 | struct rs_state *state; |
91cd7324 | 316 | struct nd_router_solicit *rs; |
91cd7324 | 317 | |
ca15a0aa RM |
318 | state = RS_STATE(ifp); |
319 | free(state->rs); | |
ba71fb8b RM |
320 | state->rslen = sizeof(*rs); |
321 | if (ifp->hwlen != 0) | |
322 | state->rslen += (size_t)ROUNDUP8(ifp->hwlen + 2); | |
10e17e3f | 323 | state->rs = calloc(1, state->rslen); |
ca15a0aa | 324 | if (state->rs == NULL) |
91cd7324 | 325 | return -1; |
2e704972 | 326 | rs = state->rs; |
91cd7324 | 327 | rs->nd_rs_type = ND_ROUTER_SOLICIT; |
2e704972 RM |
328 | //rs->nd_rs_code = 0; |
329 | //rs->nd_rs_cksum = 0; | |
330 | //rs->nd_rs_reserved = 0; | |
ba71fb8b RM |
331 | |
332 | if (ifp->hwlen != 0) { | |
333 | struct nd_opt_hdr *nd; | |
334 | ||
2e704972 | 335 | nd = (struct nd_opt_hdr *)(state->rs + 1); |
ba71fb8b RM |
336 | nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; |
337 | nd->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3); | |
338 | memcpy(nd + 1, ifp->hwaddr, ifp->hwlen); | |
339 | } | |
91cd7324 RM |
340 | return 0; |
341 | } | |
673e81e5 | 342 | |
91cd7324 | 343 | static void |
e82129a4 | 344 | ipv6nd_sendrsprobe(void *arg) |
91cd7324 RM |
345 | { |
346 | struct interface *ifp = arg; | |
5fed9d43 | 347 | struct rs_state *state = RS_STATE(ifp); |
4d53b9d9 RM |
348 | struct sockaddr_in6 dst = { |
349 | .sin6_family = AF_INET6, | |
350 | .sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT, | |
351 | .sin6_scope_id = ifp->index, | |
352 | }; | |
5fed9d43 | 353 | struct iovec iov = { .iov_base = state->rs, .iov_len = state->rslen }; |
29024d0b RM |
354 | union { |
355 | struct cmsghdr hdr; | |
356 | uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; | |
357 | } cmsgbuf = { .buf = { 0 } }; | |
5fed9d43 RM |
358 | struct msghdr msg = { |
359 | .msg_name = &dst, .msg_namelen = sizeof(dst), | |
360 | .msg_iov = &iov, .msg_iovlen = 1, | |
29024d0b | 361 | .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), |
5fed9d43 | 362 | }; |
91cd7324 | 363 | struct cmsghdr *cm; |
5fed9d43 | 364 | struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; |
b2edc303 | 365 | int s; |
11101f7b | 366 | #ifndef __sun |
c5445ce8 | 367 | struct dhcpcd_ctx *ctx = ifp->ctx; |
11101f7b | 368 | #endif |
91cd7324 | 369 | |
0e906716 | 370 | if (ipv6_linklocal(ifp) == NULL) { |
0e56d022 | 371 | logdebugx("%s: delaying Router Solicitation for LL address", |
5331b839 | 372 | ifp->name); |
e82129a4 | 373 | ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp); |
5331b839 RM |
374 | return; |
375 | } | |
376 | ||
4356c648 | 377 | #ifdef HAVE_SA_LEN |
4eb7b489 RM |
378 | dst.sin6_len = sizeof(dst); |
379 | #endif | |
91cd7324 RM |
380 | |
381 | /* Set the outbound interface */ | |
5fed9d43 | 382 | cm = CMSG_FIRSTHDR(&msg); |
8fc52ced RM |
383 | if (cm == NULL) /* unlikely */ |
384 | return; | |
91cd7324 RM |
385 | cm->cmsg_level = IPPROTO_IPV6; |
386 | cm->cmsg_type = IPV6_PKTINFO; | |
387 | cm->cmsg_len = CMSG_LEN(sizeof(pi)); | |
91cd7324 RM |
388 | memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); |
389 | ||
0e56d022 | 390 | logdebugx("%s: sending Router Solicitation", ifp->name); |
65025848 | 391 | #ifdef PRIVSEP |
e66b2912 | 392 | if (IN_PRIVSEP(ifp->ctx)) { |
471df5f6 | 393 | if (ps_inet_sendnd(ifp, &msg) == -1) |
65025848 RM |
394 | logerr(__func__); |
395 | goto sent; | |
396 | } | |
397 | #endif | |
b2edc303 | 398 | #ifdef __sun |
20c1eb5d RM |
399 | if (state->nd_fd == -1) { |
400 | if (ipv6nd_openif(ifp) == -1) { | |
401 | logerr(__func__); | |
402 | return; | |
403 | } | |
404 | } | |
b2edc303 RM |
405 | s = state->nd_fd; |
406 | #else | |
c5445ce8 RM |
407 | if (ctx->nd_fd == -1) { |
408 | ctx->nd_fd = ipv6nd_open(true); | |
409 | if (ctx->nd_fd == -1) { | |
410 | logerr(__func__); | |
411 | return; | |
412 | } | |
5d619328 RM |
413 | if (eloop_event_add(ctx->eloop, ctx->nd_fd, ELE_READ, |
414 | ipv6nd_handledata, ctx) == -1) | |
415 | logerr("%s: eloop_event_add", __func__); | |
c5445ce8 | 416 | } |
b2edc303 RM |
417 | s = ifp->ctx->nd_fd; |
418 | #endif | |
419 | if (sendmsg(s, &msg, 0) == -1) { | |
94d1ded9 | 420 | logerr(__func__); |
9299f1c6 RM |
421 | /* Allow IPv6ND to continue .... at most a few errors |
422 | * would be logged. | |
423 | * Generally the error is ENOBUFS when struggling to | |
424 | * associate with an access point. */ | |
83e82504 | 425 | } |
91cd7324 | 426 | |
65025848 RM |
427 | #ifdef PRIVSEP |
428 | sent: | |
429 | #endif | |
ca15a0aa | 430 | if (state->rsprobes++ < MAX_RTR_SOLICITATIONS) |
4eb7b489 RM |
431 | eloop_timeout_add_sec(ifp->ctx->eloop, |
432 | RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); | |
3e0c93a4 | 433 | else |
94d1ded9 | 434 | logwarnx("%s: no IPv6 Routers available", ifp->name); |
2f53bfd4 RM |
435 | } |
436 | ||
7b3d0126 | 437 | #ifdef ND6_ADVERTISE |
cd09e583 RM |
438 | static void |
439 | ipv6nd_sendadvertisement(void *arg) | |
440 | { | |
441 | struct ipv6_addr *ia = arg; | |
442 | struct interface *ifp = ia->iface; | |
443 | struct dhcpcd_ctx *ctx = ifp->ctx; | |
5fed9d43 RM |
444 | struct sockaddr_in6 dst = { |
445 | .sin6_family = AF_INET6, | |
4d53b9d9 | 446 | .sin6_addr = IN6ADDR_LINKLOCAL_ALLNODES_INIT, |
5fed9d43 RM |
447 | .sin6_scope_id = ifp->index, |
448 | }; | |
449 | struct iovec iov = { .iov_base = ia->na, .iov_len = ia->na_len }; | |
29024d0b RM |
450 | union { |
451 | struct cmsghdr hdr; | |
452 | uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; | |
453 | } cmsgbuf = { .buf = { 0 } }; | |
5fed9d43 RM |
454 | struct msghdr msg = { |
455 | .msg_name = &dst, .msg_namelen = sizeof(dst), | |
456 | .msg_iov = &iov, .msg_iovlen = 1, | |
29024d0b | 457 | .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), |
5fed9d43 | 458 | }; |
cd09e583 | 459 | struct cmsghdr *cm; |
5fed9d43 | 460 | struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; |
cd09e583 | 461 | const struct rs_state *state = RS_CSTATE(ifp); |
b2edc303 | 462 | int s; |
cd09e583 | 463 | |
76b513c6 | 464 | if (state == NULL || !if_is_link_up(ifp)) |
cd09e583 RM |
465 | goto freeit; |
466 | ||
cd09e583 RM |
467 | #ifdef SIN6_LEN |
468 | dst.sin6_len = sizeof(dst); | |
469 | #endif | |
cd09e583 | 470 | |
cd09e583 | 471 | /* Set the outbound interface. */ |
5fed9d43 | 472 | cm = CMSG_FIRSTHDR(&msg); |
cd09e583 RM |
473 | assert(cm != NULL); |
474 | cm->cmsg_level = IPPROTO_IPV6; | |
475 | cm->cmsg_type = IPV6_PKTINFO; | |
476 | cm->cmsg_len = CMSG_LEN(sizeof(pi)); | |
cd09e583 | 477 | memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); |
cd09e583 | 478 | logdebugx("%s: sending NA for %s", ifp->name, ia->saddr); |
65025848 RM |
479 | |
480 | #ifdef PRIVSEP | |
e66b2912 | 481 | if (IN_PRIVSEP(ifp->ctx)) { |
471df5f6 | 482 | if (ps_inet_sendnd(ifp, &msg) == -1) |
65025848 RM |
483 | logerr(__func__); |
484 | goto sent; | |
485 | } | |
486 | #endif | |
b2edc303 RM |
487 | #ifdef __sun |
488 | s = state->nd_fd; | |
489 | #else | |
490 | s = ctx->nd_fd; | |
491 | #endif | |
492 | if (sendmsg(s, &msg, 0) == -1) | |
cd09e583 RM |
493 | logerr(__func__); |
494 | ||
65025848 RM |
495 | #ifdef PRIVSEP |
496 | sent: | |
497 | #endif | |
cd09e583 RM |
498 | if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) { |
499 | eloop_timeout_add_sec(ctx->eloop, | |
500 | state->retrans / 1000, ipv6nd_sendadvertisement, ia); | |
501 | return; | |
502 | } | |
503 | ||
504 | freeit: | |
505 | free(ia->na); | |
506 | ia->na = NULL; | |
507 | ia->na_count = 0; | |
508 | } | |
509 | ||
510 | void | |
511 | ipv6nd_advertise(struct ipv6_addr *ia) | |
512 | { | |
513 | struct dhcpcd_ctx *ctx; | |
514 | struct interface *ifp; | |
515 | struct ipv6_state *state; | |
516 | struct ipv6_addr *iap, *iaf; | |
517 | struct nd_neighbor_advert *na; | |
518 | ||
519 | if (IN6_IS_ADDR_MULTICAST(&ia->addr)) | |
520 | return; | |
521 | ||
f95d685e RM |
522 | #ifdef __sun |
523 | if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX) | |
524 | return; | |
525 | #endif | |
526 | ||
cd09e583 | 527 | ctx = ia->iface->ctx; |
cd09e583 RM |
528 | /* Find the most preferred address to advertise. */ |
529 | iaf = NULL; | |
530 | TAILQ_FOREACH(ifp, ctx->ifaces, next) { | |
531 | state = IPV6_STATE(ifp); | |
76b513c6 | 532 | if (state == NULL || !if_is_link_up(ifp)) |
cd09e583 RM |
533 | continue; |
534 | ||
535 | TAILQ_FOREACH(iap, &state->addrs, next) { | |
536 | if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr)) | |
537 | continue; | |
538 | ||
539 | /* Cancel any current advertisement. */ | |
540 | eloop_timeout_delete(ctx->eloop, | |
541 | ipv6nd_sendadvertisement, iap); | |
542 | ||
543 | /* Don't advertise what we can't use. */ | |
544 | if (iap->prefix_vltime == 0 || | |
545 | iap->addr_flags & IN6_IFF_NOTUSEABLE) | |
546 | continue; | |
547 | ||
7d58ddf0 RM |
548 | if (iaf == NULL || |
549 | iaf->iface->metric > iap->iface->metric) | |
cd09e583 RM |
550 | iaf = iap; |
551 | } | |
552 | } | |
553 | if (iaf == NULL) | |
554 | return; | |
555 | ||
556 | /* Make the packet. */ | |
557 | ifp = iaf->iface; | |
558 | iaf->na_len = sizeof(*na); | |
559 | if (ifp->hwlen != 0) | |
560 | iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2); | |
561 | na = calloc(1, iaf->na_len); | |
562 | if (na == NULL) { | |
563 | logerr(__func__); | |
564 | return; | |
565 | } | |
566 | ||
567 | na->nd_na_type = ND_NEIGHBOR_ADVERT; | |
568 | na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE; | |
12b0db43 RM |
569 | #if defined(PRIVSEP) && (defined(__linux__) || defined(HAVE_PLEDGE)) |
570 | if (IN_PRIVSEP(ctx)) { | |
670f1d75 | 571 | if (ps_root_ip6forwarding(ctx, ifp->name) != 0) |
12b0db43 RM |
572 | na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; |
573 | } else | |
8ec63e6a | 574 | #endif |
670f1d75 | 575 | if (ip6_forwarding(ifp->name) != 0) |
cd09e583 RM |
576 | na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; |
577 | na->nd_na_target = ia->addr; | |
578 | ||
579 | if (ifp->hwlen != 0) { | |
580 | struct nd_opt_hdr *opt; | |
581 | ||
582 | opt = (struct nd_opt_hdr *)(na + 1); | |
583 | opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; | |
584 | opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3); | |
585 | memcpy(opt + 1, ifp->hwaddr, ifp->hwlen); | |
586 | } | |
587 | ||
588 | iaf->na_count = 0; | |
589 | free(iaf->na); | |
590 | iaf->na = na; | |
591 | eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf); | |
592 | ipv6nd_sendadvertisement(iaf); | |
593 | } | |
bfef3fd3 RM |
594 | #elif !defined(SMALL) |
595 | #warning kernel does not support userland sending ND6 advertisements | |
7b3d0126 | 596 | #endif /* ND6_ADVERTISE */ |
cd09e583 | 597 | |
cf94e2dd RM |
598 | static void |
599 | ipv6nd_expire(void *arg) | |
2f53bfd4 | 600 | { |
cf94e2dd | 601 | struct interface *ifp = arg; |
2f53bfd4 | 602 | struct ra *rap; |
2f53bfd4 | 603 | |
cc9d9bf8 | 604 | if (ifp->ctx->ra_routers == NULL) |
aa9fc372 RM |
605 | return; |
606 | ||
cc9d9bf8 | 607 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { |
3e0c93a4 RM |
608 | if (rap->iface == ifp && rap->willexpire) |
609 | rap->doexpire = true; | |
2f53bfd4 | 610 | } |
cf94e2dd RM |
611 | ipv6nd_expirera(ifp); |
612 | } | |
613 | ||
614 | void | |
615 | ipv6nd_startexpire(struct interface *ifp) | |
616 | { | |
3e0c93a4 RM |
617 | struct ra *rap; |
618 | ||
619 | if (ifp->ctx->ra_routers == NULL) | |
620 | return; | |
cf94e2dd | 621 | |
3e0c93a4 RM |
622 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { |
623 | if (rap->iface == ifp) | |
624 | rap->willexpire = true; | |
625 | } | |
626 | eloop_q_timeout_add_sec(ifp->ctx->eloop, ELOOP_IPV6RA_EXPIRE, | |
627 | RTR_CARRIER_EXPIRE, ipv6nd_expire, ifp); | |
91cd7324 RM |
628 | } |
629 | ||
db58572f | 630 | int |
f1cf924a | 631 | ipv6nd_rtpref(uint8_t flags) |
437bf2a8 RM |
632 | { |
633 | ||
f1cf924a | 634 | switch (flags & ND_RA_FLAG_RTPREF_MASK) { |
437bf2a8 RM |
635 | case ND_RA_FLAG_RTPREF_HIGH: |
636 | return RTPREF_HIGH; | |
637 | case ND_RA_FLAG_RTPREF_MEDIUM: | |
638 | case ND_RA_FLAG_RTPREF_RSV: | |
639 | return RTPREF_MEDIUM; | |
640 | case ND_RA_FLAG_RTPREF_LOW: | |
641 | return RTPREF_LOW; | |
642 | default: | |
f1cf924a | 643 | logerrx("%s: impossible RA flag %x", __func__, flags); |
437bf2a8 RM |
644 | return RTPREF_INVALID; |
645 | } | |
646 | /* NOTREACHED */ | |
647 | } | |
648 | ||
649 | static void | |
650 | ipv6nd_sortrouters(struct dhcpcd_ctx *ctx) | |
651 | { | |
652 | struct ra_head sorted_routers = TAILQ_HEAD_INITIALIZER(sorted_routers); | |
653 | struct ra *ra1, *ra2; | |
654 | ||
437bf2a8 RM |
655 | while ((ra1 = TAILQ_FIRST(ctx->ra_routers)) != NULL) { |
656 | TAILQ_REMOVE(ctx->ra_routers, ra1, next); | |
657 | TAILQ_FOREACH(ra2, &sorted_routers, next) { | |
3e0c93a4 | 658 | if (ra1->iface->metric > ra2->iface->metric) |
437bf2a8 RM |
659 | continue; |
660 | if (ra1->expired && !ra2->expired) | |
661 | continue; | |
3e0c93a4 RM |
662 | if (ra1->willexpire && !ra2->willexpire) |
663 | continue; | |
437bf2a8 RM |
664 | if (ra1->lifetime == 0 && ra2->lifetime != 0) |
665 | continue; | |
666 | if (!ra1->isreachable && ra2->reachable) | |
667 | continue; | |
f1cf924a | 668 | if (ipv6nd_rtpref(ra1->flags) <= ipv6nd_rtpref(ra2->flags)) |
437bf2a8 RM |
669 | continue; |
670 | /* All things being equal, prefer older routers. */ | |
f706d872 RM |
671 | /* We don't need to check time, becase newer |
672 | * routers are always added to the tail and then | |
673 | * sorted. */ | |
437bf2a8 RM |
674 | TAILQ_INSERT_BEFORE(ra2, ra1, next); |
675 | break; | |
676 | } | |
677 | if (ra2 == NULL) | |
678 | TAILQ_INSERT_TAIL(&sorted_routers, ra1, next); | |
679 | } | |
680 | ||
681 | TAILQ_CONCAT(ctx->ra_routers, &sorted_routers, next); | |
682 | } | |
683 | ||
12f62371 | 684 | static void |
8d885c0f | 685 | ipv6nd_applyra(struct interface *ifp) |
12f62371 RM |
686 | { |
687 | struct ra *rap; | |
688 | struct rs_state *state = RS_STATE(ifp); | |
8d885c0f RM |
689 | struct ra defra = { |
690 | .iface = ifp, | |
691 | .hoplimit = IPV6_DEFHLIM , | |
692 | .reachable = REACHABLE_TIME, | |
693 | .retrans = RETRANS_TIMER, | |
694 | }; | |
695 | ||
696 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { | |
4b0d4d30 | 697 | if (rap->iface == ifp) |
12f62371 RM |
698 | break; |
699 | } | |
700 | ||
8d885c0f | 701 | /* If we have no Router Advertisement, then set default values. */ |
4b0d4d30 | 702 | if (rap == NULL || rap->expired || rap->willexpire) |
8d885c0f | 703 | rap = &defra; |
12f62371 RM |
704 | |
705 | state->retrans = rap->retrans; | |
5b704581 | 706 | if (if_applyra(rap) == -1 && errno != ENOENT) |
12f62371 RM |
707 | logerr(__func__); |
708 | } | |
709 | ||
cdd3c5b0 RM |
710 | /* |
711 | * Neighbour reachability. | |
712 | * | |
713 | * RFC 4681 6.2.5 says when a node is no longer a router it MUST | |
714 | * send a RA with a zero lifetime. | |
715 | * All OS's I know of set the NA router flag if they are a router | |
716 | * or not and disregard that they are actively advertising or | |
717 | * shutting down. If the interface is disabled, it cant't send a NA at all. | |
718 | * | |
719 | * As such we CANNOT rely on the NA Router flag and MUST use | |
720 | * unreachability or receive a RA with a lifetime of zero to remove | |
721 | * the node as a default router. | |
722 | */ | |
4385f630 | 723 | void |
cdd3c5b0 | 724 | ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, bool reachable) |
72c37f5f | 725 | { |
4385f630 RM |
726 | struct ra *rap, *rapr; |
727 | ||
728 | if (ctx->ra_routers == NULL) | |
729 | return; | |
730 | ||
731 | TAILQ_FOREACH(rap, ctx->ra_routers, next) { | |
732 | if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) | |
733 | break; | |
734 | } | |
72c37f5f | 735 | |
0228659a | 736 | if (rap == NULL || rap->expired || rap->isreachable == reachable) |
07b28b41 RM |
737 | return; |
738 | ||
437bf2a8 RM |
739 | rap->isreachable = reachable; |
740 | loginfox("%s: %s is %s", rap->iface->name, rap->sfrom, | |
741 | reachable ? "reachable again" : "unreachable"); | |
742 | ||
743 | /* See if we can install a reachable default router. */ | |
744 | ipv6nd_sortrouters(ctx); | |
8d885c0f | 745 | ipv6nd_applyra(rap->iface); |
437bf2a8 | 746 | rt_build(ctx, AF_INET6); |
964b60fe | 747 | |
158e298d RM |
748 | if (reachable) |
749 | return; | |
750 | ||
964b60fe RM |
751 | /* If we have no reachable default routers, try and solicit one. */ |
752 | TAILQ_FOREACH(rapr, ctx->ra_routers, next) { | |
753 | if (rap == rapr || rap->iface != rapr->iface) | |
754 | continue; | |
755 | if (rapr->isreachable && !rapr->expired && rapr->lifetime) | |
756 | break; | |
757 | } | |
758 | ||
759 | if (rapr == NULL) | |
b316c5bf | 760 | ipv6nd_startrs(rap->iface); |
72c37f5f | 761 | } |
a3ee6b23 | 762 | |
edb0ed37 RM |
763 | const struct ipv6_addr * |
764 | ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr, | |
c4d7d69a | 765 | unsigned int flags) |
edb0ed37 RM |
766 | { |
767 | struct ra *rap; | |
768 | struct ipv6_addr *ap; | |
769 | ||
cc9d9bf8 | 770 | if (ifp->ctx->ra_routers == NULL) |
edb0ed37 RM |
771 | return NULL; |
772 | ||
cc9d9bf8 | 773 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { |
edb0ed37 RM |
774 | if (rap->iface != ifp) |
775 | continue; | |
776 | TAILQ_FOREACH(ap, &rap->addrs, next) { | |
5b1f21d1 | 777 | if (ipv6_findaddrmatch(ap, addr, flags)) |
edb0ed37 RM |
778 | return ap; |
779 | } | |
780 | } | |
781 | return NULL; | |
782 | } | |
5b1f21d1 | 783 | |
f3047040 RM |
784 | struct ipv6_addr * |
785 | ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, | |
c4d7d69a | 786 | unsigned int flags) |
376e8b80 RM |
787 | { |
788 | struct ra *rap; | |
789 | struct ipv6_addr *ap; | |
790 | ||
cc9d9bf8 | 791 | if (ctx->ra_routers == NULL) |
f3047040 | 792 | return NULL; |
fe6c1b9d | 793 | |
cc9d9bf8 | 794 | TAILQ_FOREACH(rap, ctx->ra_routers, next) { |
376e8b80 | 795 | TAILQ_FOREACH(ap, &rap->addrs, next) { |
5b1f21d1 | 796 | if (ipv6_findaddrmatch(ap, addr, flags)) |
f3047040 | 797 | return ap; |
376e8b80 RM |
798 | } |
799 | } | |
f3047040 | 800 | return NULL; |
376e8b80 RM |
801 | } |
802 | ||
c099165a RM |
803 | static struct ipv6_addr * |
804 | ipv6nd_rapfindprefix(struct ra *rap, | |
805 | const struct in6_addr *pfx, uint8_t pfxlen) | |
806 | { | |
807 | struct ipv6_addr *ia; | |
808 | ||
809 | TAILQ_FOREACH(ia, &rap->addrs, next) { | |
810 | if (ia->prefix_vltime == 0) | |
811 | continue; | |
812 | if (ia->prefix_len == pfxlen && | |
813 | IN6_ARE_ADDR_EQUAL(&ia->prefix, pfx)) | |
814 | break; | |
815 | } | |
816 | return ia; | |
817 | } | |
818 | ||
819 | struct ipv6_addr * | |
820 | ipv6nd_iffindprefix(struct interface *ifp, | |
821 | const struct in6_addr *pfx, uint8_t pfxlen) | |
822 | { | |
823 | struct ra *rap; | |
824 | struct ipv6_addr *ia; | |
825 | ||
826 | ia = NULL; | |
827 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { | |
828 | if (rap->iface != ifp) | |
829 | continue; | |
830 | ia = ipv6nd_rapfindprefix(rap, pfx, pfxlen); | |
831 | if (ia != NULL) | |
832 | break; | |
833 | } | |
834 | return ia; | |
835 | } | |
836 | ||
2be15e88 | 837 | static void |
81af2589 | 838 | ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra) |
eebe9a18 RM |
839 | { |
840 | ||
4eb7b489 RM |
841 | eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); |
842 | eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); | |
9a593d97 | 843 | if (remove_ra) |
cc9d9bf8 | 844 | TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next); |
81af2589 | 845 | ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL); |
f1cf924a | 846 | routeinfohead_free(&rap->rinfos); |
eebe9a18 | 847 | free(rap->data); |
eebe9a18 RM |
848 | free(rap); |
849 | } | |
850 | ||
f1df29d2 | 851 | static void |
2be15e88 RM |
852 | ipv6nd_freedrop_ra(struct ra *rap, int drop) |
853 | { | |
854 | ||
855 | ipv6nd_removefreedrop_ra(rap, 1, drop); | |
856 | } | |
857 | ||
eebe9a18 | 858 | ssize_t |
e82129a4 | 859 | ipv6nd_free(struct interface *ifp) |
eebe9a18 | 860 | { |
ca15a0aa | 861 | struct rs_state *state; |
eebe9a18 | 862 | struct ra *rap, *ran; |
4eb7b489 | 863 | struct dhcpcd_ctx *ctx; |
eebe9a18 RM |
864 | ssize_t n; |
865 | ||
ca15a0aa | 866 | state = RS_STATE(ifp); |
a9d78def RM |
867 | if (state == NULL) |
868 | return 0; | |
869 | ||
b2edc303 RM |
870 | ctx = ifp->ctx; |
871 | #ifdef __sun | |
872 | eloop_event_delete(ctx->eloop, state->nd_fd); | |
873 | close(state->nd_fd); | |
874 | #endif | |
a9d78def RM |
875 | free(state->rs); |
876 | free(state); | |
877 | ifp->if_data[IF_DATA_IPV6ND] = NULL; | |
eebe9a18 | 878 | n = 0; |
cc9d9bf8 | 879 | TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { |
eebe9a18 | 880 | if (rap->iface == ifp) { |
e82129a4 | 881 | ipv6nd_free_ra(rap); |
eebe9a18 | 882 | n++; |
91cd7324 | 883 | } |
eebe9a18 | 884 | } |
a9d78def | 885 | |
b2edc303 | 886 | #ifndef __sun |
a9d78def RM |
887 | /* If we don't have any more IPv6 enabled interfaces, |
888 | * close the global socket and release resources */ | |
4eb7b489 | 889 | TAILQ_FOREACH(ifp, ctx->ifaces, next) { |
a9d78def RM |
890 | if (RS_STATE(ifp)) |
891 | break; | |
892 | } | |
893 | if (ifp == NULL) { | |
cc9d9bf8 RM |
894 | if (ctx->nd_fd != -1) { |
895 | eloop_event_delete(ctx->eloop, ctx->nd_fd); | |
896 | close(ctx->nd_fd); | |
897 | ctx->nd_fd = -1; | |
a9d78def | 898 | } |
a9d78def | 899 | } |
b2edc303 | 900 | #endif |
a9d78def | 901 | |
eebe9a18 RM |
902 | return n; |
903 | } | |
904 | ||
9b4d745e | 905 | static void |
e82129a4 | 906 | ipv6nd_scriptrun(struct ra *rap) |
a8df1b28 | 907 | { |
9b4d745e | 908 | int hasdns, hasaddress; |
d5690e93 | 909 | struct ipv6_addr *ap; |
a8df1b28 | 910 | |
e2c4a256 | 911 | hasaddress = 0; |
a8df1b28 | 912 | /* If all addresses have completed DAD run the script */ |
a8df1b28 | 913 | TAILQ_FOREACH(ap, &rap->addrs, next) { |
de67b951 RM |
914 | if ((ap->flags & (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) == |
915 | (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) | |
a824f281 | 916 | { |
e2c4a256 | 917 | hasaddress = 1; |
d5690e93 | 918 | if (!(ap->flags & IPV6_AF_DADCOMPLETED) && |
03274c9c RM |
919 | ipv6_iffindaddr(ap->iface, &ap->addr, |
920 | IN6_IFF_TENTATIVE)) | |
d5690e93 RM |
921 | ap->flags |= IPV6_AF_DADCOMPLETED; |
922 | if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) { | |
0e56d022 | 923 | logdebugx("%s: waiting for Router Advertisement" |
d5690e93 RM |
924 | " DAD to complete", |
925 | rap->iface->name); | |
9b4d745e | 926 | return; |
d5690e93 | 927 | } |
d8194bcd | 928 | } |
a8df1b28 RM |
929 | } |
930 | ||
931 | /* If we don't require RDNSS then set hasdns = 1 so we fork */ | |
932 | if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS)) | |
933 | hasdns = 1; | |
934 | else { | |
2be15e88 | 935 | hasdns = rap->hasdns; |
a8df1b28 RM |
936 | } |
937 | ||
938 | script_runreason(rap->iface, "ROUTERADVERT"); | |
e2c4a256 RM |
939 | if (hasdns && (hasaddress || |
940 | !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))) | |
9b4d745e | 941 | dhcpcd_daemonise(rap->iface->ctx); |
a8df1b28 RM |
942 | #if 0 |
943 | else if (options & DHCPCD_DAEMONISE && | |
944 | !(options & DHCPCD_DAEMONISED) && new_data) | |
94d1ded9 | 945 | logwarnx("%s: did not fork due to an absent" |
a8df1b28 RM |
946 | " RDNSS option in the RA", |
947 | ifp->name); | |
a8df1b28 RM |
948 | #endif |
949 | } | |
950 | ||
3ed12ab8 RM |
951 | static void |
952 | ipv6nd_addaddr(void *arg) | |
953 | { | |
954 | struct ipv6_addr *ap = arg; | |
955 | ||
0b3255ac | 956 | ipv6_addaddr(ap, NULL); |
3ed12ab8 RM |
957 | } |
958 | ||
a0011b99 RM |
959 | int |
960 | ipv6nd_dadcompleted(const struct interface *ifp) | |
961 | { | |
962 | const struct ra *rap; | |
963 | const struct ipv6_addr *ap; | |
964 | ||
cc9d9bf8 | 965 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { |
a0011b99 RM |
966 | if (rap->iface != ifp) |
967 | continue; | |
968 | TAILQ_FOREACH(ap, &rap->addrs, next) { | |
969 | if (ap->flags & IPV6_AF_AUTOCONF && | |
de67b951 | 970 | ap->flags & IPV6_AF_ADDED && |
a0011b99 | 971 | !(ap->flags & IPV6_AF_DADCOMPLETED)) |
de67b951 | 972 | return 0; |
a0011b99 RM |
973 | } |
974 | } | |
975 | return 1; | |
976 | } | |
977 | ||
d8194bcd | 978 | static void |
e82129a4 | 979 | ipv6nd_dadcallback(void *arg) |
d8194bcd | 980 | { |
65ae27ee | 981 | struct ipv6_addr *ia = arg, *rapap; |
d8194bcd RM |
982 | struct interface *ifp; |
983 | struct ra *rap; | |
984 | int wascompleted, found; | |
3ed12ab8 RM |
985 | char buf[INET6_ADDRSTRLEN]; |
986 | const char *p; | |
4f5b9dd2 | 987 | int dadcounter; |
d8194bcd | 988 | |
65ae27ee RM |
989 | ifp = ia->iface; |
990 | wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); | |
991 | ia->flags |= IPV6_AF_DADCOMPLETED; | |
62094f1b | 992 | if (ia->addr_flags & IN6_IFF_DUPLICATED) { |
65ae27ee RM |
993 | ia->dadcounter++; |
994 | logwarnx("%s: DAD detected %s", ifp->name, ia->saddr); | |
d8194bcd | 995 | |
3ed12ab8 RM |
996 | /* Try and make another stable private address. |
997 | * Because ap->dadcounter is always increamented, | |
998 | * a different address is generated. */ | |
999 | /* XXX Cache DAD counter per prefix/id/ssid? */ | |
524217db RM |
1000 | if (ifp->options->options & DHCPCD_SLAACPRIVATE && |
1001 | IA6_CANAUTOCONF(ia)) | |
1002 | { | |
858d217d RM |
1003 | unsigned int delay; |
1004 | ||
65ae27ee | 1005 | if (ia->dadcounter >= IDGEN_RETRIES) { |
94d1ded9 | 1006 | logerrx("%s: unable to obtain a" |
fd89860f RM |
1007 | " stable private address", |
1008 | ifp->name); | |
1009 | goto try_script; | |
1010 | } | |
9efdc92f | 1011 | loginfox("%s: deleting address %s", |
65ae27ee RM |
1012 | ifp->name, ia->saddr); |
1013 | if (if_address6(RTM_DELADDR, ia) == -1 && | |
3ed12ab8 | 1014 | errno != EADDRNOTAVAIL && errno != ENXIO) |
94d1ded9 | 1015 | logerr(__func__); |
65ae27ee RM |
1016 | dadcounter = ia->dadcounter; |
1017 | if (ipv6_makestableprivate(&ia->addr, | |
1018 | &ia->prefix, ia->prefix_len, | |
4f5b9dd2 | 1019 | ifp, &dadcounter) == -1) |
3ed12ab8 | 1020 | { |
94d1ded9 | 1021 | logerr("ipv6_makestableprivate"); |
3ed12ab8 RM |
1022 | return; |
1023 | } | |
65ae27ee RM |
1024 | ia->dadcounter = dadcounter; |
1025 | ia->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); | |
1026 | ia->flags |= IPV6_AF_NEW; | |
1027 | p = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); | |
3ed12ab8 | 1028 | if (p) |
65ae27ee RM |
1029 | snprintf(ia->saddr, |
1030 | sizeof(ia->saddr), | |
3ed12ab8 | 1031 | "%s/%d", |
65ae27ee | 1032 | p, ia->prefix_len); |
3ed12ab8 | 1033 | else |
65ae27ee | 1034 | ia->saddr[0] = '\0'; |
858d217d RM |
1035 | delay = arc4random_uniform(IDGEN_DELAY * MSEC_PER_SEC); |
1036 | eloop_timeout_add_msec(ifp->ctx->eloop, delay, | |
65ae27ee | 1037 | ipv6nd_addaddr, ia); |
3ed12ab8 RM |
1038 | return; |
1039 | } | |
1040 | } | |
d8194bcd | 1041 | |
fd89860f | 1042 | try_script: |
3ed12ab8 | 1043 | if (!wascompleted) { |
cc9d9bf8 | 1044 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { |
d8194bcd RM |
1045 | if (rap->iface != ifp) |
1046 | continue; | |
1047 | wascompleted = 1; | |
e92ca600 | 1048 | found = 0; |
d8194bcd | 1049 | TAILQ_FOREACH(rapap, &rap->addrs, next) { |
a824f281 | 1050 | if (rapap->flags & IPV6_AF_AUTOCONF && |
de67b951 | 1051 | rapap->flags & IPV6_AF_ADDED && |
a824f281 RM |
1052 | (rapap->flags & IPV6_AF_DADCOMPLETED) == 0) |
1053 | { | |
d8194bcd RM |
1054 | wascompleted = 0; |
1055 | break; | |
1056 | } | |
65ae27ee | 1057 | if (rapap == ia) |
d8194bcd RM |
1058 | found = 1; |
1059 | } | |
1060 | ||
4f422dd3 | 1061 | if (wascompleted && found) { |
0e56d022 | 1062 | logdebugx("%s: Router Advertisement DAD " |
94d1ded9 | 1063 | "completed", |
d8194bcd | 1064 | rap->iface->name); |
9b4d745e | 1065 | ipv6nd_scriptrun(rap); |
d8194bcd RM |
1066 | } |
1067 | } | |
7b3d0126 | 1068 | #ifdef ND6_ADVERTISE |
cd09e583 | 1069 | ipv6nd_advertise(ia); |
7b3d0126 | 1070 | #endif |
d8194bcd RM |
1071 | } |
1072 | } | |
1073 | ||
56658009 RM |
1074 | static struct ipv6_addr * |
1075 | ipv6nd_findmarkstale(struct ra *rap, struct ipv6_addr *ia, bool mark) | |
1076 | { | |
1077 | struct dhcpcd_ctx *ctx = ia->iface->ctx; | |
1078 | struct ra *rap2; | |
1079 | struct ipv6_addr *ia2; | |
1080 | ||
1081 | TAILQ_FOREACH(rap2, ctx->ra_routers, next) { | |
1082 | if (rap2 == rap || | |
1083 | rap2->iface != rap->iface || | |
1084 | rap2->expired) | |
1085 | continue; | |
1086 | TAILQ_FOREACH(ia2, &rap2->addrs, next) { | |
1087 | if (!IN6_ARE_ADDR_EQUAL(&ia->prefix, &ia2->prefix)) | |
1088 | continue; | |
1089 | if (!(ia2->flags & IPV6_AF_STALE)) | |
1090 | return ia2; | |
1091 | if (mark) | |
1092 | ia2->prefix_pltime = 0; | |
1093 | } | |
1094 | } | |
1095 | return NULL; | |
1096 | } | |
1097 | ||
6a765a4f RM |
1098 | #ifndef DHCP6 |
1099 | /* If DHCPv6 is compiled out, supply a shim to provide an error message | |
1100 | * if IPv6RA requests DHCPv6. */ | |
a1b1f0a8 RM |
1101 | enum DH6S { |
1102 | DH6S_REQUEST, | |
1103 | DH6S_INFORM, | |
1104 | }; | |
6a765a4f RM |
1105 | static int |
1106 | dhcp6_start(__unused struct interface *ifp, __unused enum DH6S init_state) | |
1107 | { | |
1108 | ||
1109 | errno = ENOTSUP; | |
1110 | return -1; | |
1111 | } | |
1112 | #endif | |
1113 | ||
aae24feb | 1114 | static void |
5fed9d43 RM |
1115 | ipv6nd_handlera(struct dhcpcd_ctx *ctx, |
1116 | const struct sockaddr_in6 *from, const char *sfrom, | |
1117 | struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit) | |
91cd7324 | 1118 | { |
2be15e88 | 1119 | size_t i, olen; |
7be4b9b3 | 1120 | struct nd_router_advert *nd_ra; |
55a59017 RM |
1121 | struct nd_opt_hdr ndo; |
1122 | struct nd_opt_prefix_info pi; | |
1123 | struct nd_opt_mtu mtu; | |
1124 | struct nd_opt_rdnss rdnss; | |
f1cf924a DG |
1125 | struct nd_opt_ri ri; |
1126 | struct routeinfo *rinfo; | |
2be15e88 | 1127 | uint8_t *p; |
56658009 | 1128 | struct ra *rap; |
7878d124 | 1129 | struct in6_addr pi_prefix; |
56658009 | 1130 | struct ipv6_addr *ia; |
2be15e88 | 1131 | struct dhcp_opt *dho; |
e1d81235 | 1132 | bool new_rap, new_data, has_address; |
c15437c1 | 1133 | uint32_t old_lifetime; |
91f281ab | 1134 | int ifmtu; |
b586bfbf | 1135 | int loglevel; |
77450503 | 1136 | unsigned int flags; |
727cd92a | 1137 | #ifdef IPV6_MANAGETEMPADDR |
3f3a2bb8 | 1138 | bool new_ia; |
727cd92a | 1139 | #endif |
91cd7324 | 1140 | |
2012891b | 1141 | if (ifp == NULL || RS_STATE(ifp) == NULL) { |
a708c891 | 1142 | #ifdef DEBUG_RS |
5fed9d43 | 1143 | logdebugx("RA for unexpected interface from %s", sfrom); |
a708c891 RM |
1144 | #endif |
1145 | return; | |
1146 | } | |
1147 | ||
34457fe6 | 1148 | if (len < sizeof(struct nd_router_advert)) { |
5fed9d43 | 1149 | logerrx("IPv6 RA packet too short from %s", sfrom); |
91cd7324 RM |
1150 | return; |
1151 | } | |
1152 | ||
a708c891 RM |
1153 | /* RFC 4861 7.1.2 */ |
1154 | if (hoplimit != 255) { | |
5fed9d43 | 1155 | logerrx("invalid hoplimit(%d) in RA from %s", hoplimit, sfrom); |
91cd7324 RM |
1156 | return; |
1157 | } | |
5fed9d43 RM |
1158 | if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { |
1159 | logerrx("RA from non local address %s", sfrom); | |
4c6a8bec RM |
1160 | return; |
1161 | } | |
a708c891 | 1162 | |
4c6a8bec RM |
1163 | if (!(ifp->options->options & DHCPCD_IPV6RS)) { |
1164 | #ifdef DEBUG_RS | |
5fed9d43 | 1165 | logerrx("%s: unexpected RA from %s", ifp->name, sfrom); |
d7555c12 | 1166 | #endif |
91cd7324 RM |
1167 | return; |
1168 | } | |
0e906716 | 1169 | |
e7a30a46 | 1170 | /* We could receive a RA before we sent a RS*/ |
0e906716 RM |
1171 | if (ipv6_linklocal(ifp) == NULL) { |
1172 | #ifdef DEBUG_RS | |
0e56d022 | 1173 | logdebugx("%s: received RA from %s (no link-local)", |
5fed9d43 | 1174 | ifp->name, sfrom); |
0e906716 RM |
1175 | #endif |
1176 | return; | |
1177 | } | |
1178 | ||
5fed9d43 | 1179 | if (ipv6_iffindaddr(ifp, &from->sin6_addr, IN6_IFF_TENTATIVE)) { |
0e56d022 | 1180 | logdebugx("%s: ignoring RA from ourself %s", |
5fed9d43 | 1181 | ifp->name, sfrom); |
29211f25 RM |
1182 | return; |
1183 | } | |
1184 | ||
4b0d4d30 RM |
1185 | /* |
1186 | * Because we preserve RA's and expire them quickly after | |
1187 | * carrier up, it's important to reset the kernels notion of | |
1188 | * reachable timers back to default values before applying | |
1189 | * new RA values. | |
1190 | */ | |
1191 | TAILQ_FOREACH(rap, ctx->ra_routers, next) { | |
1192 | if (ifp == rap->iface) | |
1193 | break; | |
1194 | } | |
cda00908 | 1195 | if (rap != NULL && rap->willexpire) |
4b0d4d30 | 1196 | ipv6nd_applyra(ifp); |
4b0d4d30 | 1197 | |
4eb7b489 | 1198 | TAILQ_FOREACH(rap, ctx->ra_routers, next) { |
fe292175 | 1199 | if (ifp == rap->iface && |
5fed9d43 | 1200 | IN6_ARE_ADDR_EQUAL(&rap->from, &from->sin6_addr)) |
91cd7324 RM |
1201 | break; |
1202 | } | |
46caaa5e | 1203 | |
e42bbc9b | 1204 | nd_ra = (struct nd_router_advert *)icp; |
e42bbc9b | 1205 | |
46caaa5e RM |
1206 | /* We don't want to spam the log with the fact we got an RA every |
1207 | * 30 seconds or so, so only spam the log if it's different. */ | |
ee70f4ab | 1208 | if (rap == NULL || (rap->data_len != len || |
46caaa5e RM |
1209 | memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0)) |
1210 | { | |
1211 | if (rap) { | |
1212 | free(rap->data); | |
1213 | rap->data_len = 0; | |
1214 | } | |
2aca3a18 | 1215 | new_data = true; |
d7555c12 | 1216 | } else |
2aca3a18 | 1217 | new_data = false; |
91cd7324 | 1218 | if (rap == NULL) { |
10e17e3f RM |
1219 | rap = calloc(1, sizeof(*rap)); |
1220 | if (rap == NULL) { | |
94d1ded9 | 1221 | logerr(__func__); |
10e17e3f RM |
1222 | return; |
1223 | } | |
eebe9a18 | 1224 | rap->iface = ifp; |
5fed9d43 RM |
1225 | rap->from = from->sin6_addr; |
1226 | strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom)); | |
eebe9a18 | 1227 | TAILQ_INIT(&rap->addrs); |
f1cf924a | 1228 | TAILQ_INIT(&rap->rinfos); |
2aca3a18 | 1229 | new_rap = true; |
964b60fe | 1230 | rap->isreachable = true; |
eebe9a18 | 1231 | } else |
2aca3a18 | 1232 | new_rap = false; |
46caaa5e | 1233 | if (rap->data_len == 0) { |
28382337 RM |
1234 | rap->data = malloc(len); |
1235 | if (rap->data == NULL) { | |
94d1ded9 | 1236 | logerr(__func__); |
28382337 RM |
1237 | if (new_rap) |
1238 | free(rap); | |
1239 | return; | |
1240 | } | |
46caaa5e RM |
1241 | memcpy(rap->data, icp, len); |
1242 | rap->data_len = len; | |
91cd7324 RM |
1243 | } |
1244 | ||
19e75b95 RM |
1245 | /* We could change the debug level based on new_data, but some |
1246 | * routers like to decrease the advertised valid and preferred times | |
1247 | * in accordance with the own prefix times which would result in too | |
1248 | * much needless log spam. */ | |
21b9ce9f RM |
1249 | if (rap->willexpire) |
1250 | new_data = true; | |
32638886 | 1251 | loglevel = new_rap || rap->willexpire || !rap->isreachable ? |
61b2e192 | 1252 | LOG_INFO : LOG_DEBUG; |
b586bfbf SN |
1253 | logmessage(loglevel, "%s: Router Advertisement from %s", |
1254 | ifp->name, rap->sfrom); | |
19e75b95 | 1255 | |
f5c3ca19 | 1256 | clock_gettime(CLOCK_MONOTONIC, &rap->acquired); |
eebe9a18 | 1257 | rap->flags = nd_ra->nd_ra_flags_reserved; |
c15437c1 | 1258 | old_lifetime = rap->lifetime; |
7be4b9b3 | 1259 | rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); |
12f62371 RM |
1260 | if (nd_ra->nd_ra_curhoplimit != 0) |
1261 | rap->hoplimit = nd_ra->nd_ra_curhoplimit; | |
1262 | else | |
1263 | rap->hoplimit = IPV6_DEFHLIM; | |
1264 | if (nd_ra->nd_ra_reachable != 0) { | |
ea112ab2 RM |
1265 | rap->reachable = ntohl(nd_ra->nd_ra_reachable); |
1266 | if (rap->reachable > MAX_REACHABLE_TIME) | |
1267 | rap->reachable = 0; | |
12f62371 RM |
1268 | } else |
1269 | rap->reachable = REACHABLE_TIME; | |
1270 | if (nd_ra->nd_ra_retransmit != 0) | |
1271 | rap->retrans = ntohl(nd_ra->nd_ra_retransmit); | |
1272 | else | |
1273 | rap->retrans = RETRANS_TIMER; | |
3e0c93a4 | 1274 | rap->expired = rap->willexpire = rap->doexpire = false; |
964b60fe RM |
1275 | rap->hasdns = false; |
1276 | rap->isreachable = true; | |
e1d81235 | 1277 | has_address = false; |
0b0aed18 | 1278 | rap->mtu = 0; |
91cd7324 | 1279 | |
b4c49a9f | 1280 | #ifdef IPV6_AF_TEMPORARY |
a15bffa6 | 1281 | ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY); |
b4c49a9f | 1282 | #endif |
3f3a2bb8 RM |
1283 | TAILQ_FOREACH(ia, &rap->addrs, next) { |
1284 | ia->flags |= IPV6_AF_STALE; | |
9adc479c RM |
1285 | } |
1286 | ||
91cd7324 RM |
1287 | len -= sizeof(struct nd_router_advert); |
1288 | p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); | |
8fc52ced | 1289 | for (; len > 0; p += olen, len -= olen) { |
55a59017 | 1290 | if (len < sizeof(ndo)) { |
94d1ded9 | 1291 | logerrx("%s: short option", ifp->name); |
91cd7324 RM |
1292 | break; |
1293 | } | |
55a59017 RM |
1294 | memcpy(&ndo, p, sizeof(ndo)); |
1295 | olen = (size_t)ndo.nd_opt_len * 8; | |
91cd7324 | 1296 | if (olen == 0) { |
94d1ded9 | 1297 | logerrx("%s: zero length option", ifp->name); |
91cd7324 RM |
1298 | break; |
1299 | } | |
1300 | if (olen > len) { | |
94d1ded9 | 1301 | logerrx("%s: option length exceeds message", |
03274c9c | 1302 | ifp->name); |
91cd7324 RM |
1303 | break; |
1304 | } | |
1305 | ||
2be15e88 | 1306 | if (has_option_mask(ifp->options->rejectmasknd, |
55a59017 | 1307 | ndo.nd_opt_type)) |
2be15e88 | 1308 | { |
cc9d9bf8 RM |
1309 | for (i = 0, dho = ctx->nd_opts; |
1310 | i < ctx->nd_opts_len; | |
2be15e88 RM |
1311 | i++, dho++) |
1312 | { | |
55a59017 | 1313 | if (dho->option == ndo.nd_opt_type) |
2be15e88 RM |
1314 | break; |
1315 | } | |
a708c891 | 1316 | if (dho != NULL) |
94d1ded9 | 1317 | logwarnx("%s: reject RA (option %s) from %s", |
5fed9d43 | 1318 | ifp->name, dho->var, rap->sfrom); |
2be15e88 | 1319 | else |
94d1ded9 | 1320 | logwarnx("%s: reject RA (option %d) from %s", |
5fed9d43 | 1321 | ifp->name, ndo.nd_opt_type, rap->sfrom); |
2be15e88 RM |
1322 | if (new_rap) |
1323 | ipv6nd_removefreedrop_ra(rap, 0, 0); | |
1324 | else | |
1325 | ipv6nd_free_ra(rap); | |
1326 | return; | |
1327 | } | |
1328 | ||
55a59017 | 1329 | if (has_option_mask(ifp->options->nomasknd, ndo.nd_opt_type)) |
2be15e88 RM |
1330 | continue; |
1331 | ||
55a59017 | 1332 | switch (ndo.nd_opt_type) { |
91cd7324 | 1333 | case ND_OPT_PREFIX_INFORMATION: |
41b8ff09 RM |
1334 | { |
1335 | uint32_t vltime, pltime; | |
1336 | ||
b586bfbf | 1337 | loglevel = new_data ? LOG_ERR : LOG_DEBUG; |
55a59017 | 1338 | if (ndo.nd_opt_len != 4) { |
77450503 RM |
1339 | logmessage(loglevel, |
1340 | "%s: invalid option len for prefix", | |
91cd7324 | 1341 | ifp->name); |
c448a53a | 1342 | continue; |
91cd7324 | 1343 | } |
55a59017 RM |
1344 | memcpy(&pi, p, sizeof(pi)); |
1345 | if (pi.nd_opt_pi_prefix_len > 128) { | |
77450503 RM |
1346 | logmessage(loglevel, "%s: invalid prefix len", |
1347 | ifp->name); | |
c448a53a | 1348 | continue; |
91cd7324 | 1349 | } |
7878d124 | 1350 | /* nd_opt_pi_prefix is not aligned. */ |
03274c9c RM |
1351 | memcpy(&pi_prefix, &pi.nd_opt_pi_prefix, |
1352 | sizeof(pi_prefix)); | |
7878d124 RM |
1353 | if (IN6_IS_ADDR_MULTICAST(&pi_prefix) || |
1354 | IN6_IS_ADDR_LINKLOCAL(&pi_prefix)) | |
91cd7324 | 1355 | { |
77450503 RM |
1356 | logmessage(loglevel, "%s: invalid prefix in RA", |
1357 | ifp->name); | |
c448a53a | 1358 | continue; |
91cd7324 | 1359 | } |
41b8ff09 RM |
1360 | |
1361 | vltime = ntohl(pi.nd_opt_pi_valid_time); | |
1362 | pltime = ntohl(pi.nd_opt_pi_preferred_time); | |
1363 | if (pltime > vltime) { | |
77450503 RM |
1364 | logmessage(loglevel, "%s: pltime > vltime", |
1365 | ifp->name); | |
1366 | continue; | |
1367 | } | |
1368 | ||
f97dde9d RM |
1369 | flags = IPV6_AF_RAPFX; |
1370 | /* If no flags are set, that means the prefix is | |
1371 | * available via the router. */ | |
77450503 RM |
1372 | if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) |
1373 | flags |= IPV6_AF_ONLINK; | |
1374 | if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO && | |
1375 | rap->iface->options->options & | |
1376 | DHCPCD_IPV6RA_AUTOCONF) | |
1377 | flags |= IPV6_AF_AUTOCONF; | |
1378 | if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ROUTER) | |
1379 | flags |= IPV6_AF_ROUTER; | |
77450503 | 1380 | |
c099165a RM |
1381 | ia = ipv6nd_rapfindprefix(rap, |
1382 | &pi_prefix, pi.nd_opt_pi_prefix_len); | |
3f3a2bb8 | 1383 | if (ia == NULL) { |
3f3a2bb8 | 1384 | ia = ipv6_newaddr(rap->iface, |
f97dde9d | 1385 | &pi_prefix, pi.nd_opt_pi_prefix_len, flags); |
3f3a2bb8 | 1386 | if (ia == NULL) |
61564d34 | 1387 | break; |
41b8ff09 | 1388 | |
3f3a2bb8 | 1389 | ia->prefix = pi_prefix; |
41b8ff09 RM |
1390 | ia->created = ia->acquired = rap->acquired; |
1391 | ia->prefix_vltime = vltime; | |
1392 | ia->prefix_pltime = pltime; | |
1393 | ||
d292d54e | 1394 | if (flags & IPV6_AF_AUTOCONF) |
3f3a2bb8 | 1395 | ia->dadcallback = ipv6nd_dadcallback; |
41b8ff09 | 1396 | |
3f3a2bb8 | 1397 | TAILQ_INSERT_TAIL(&rap->addrs, ia, next); |
a1f7b32c | 1398 | |
727cd92a | 1399 | #ifdef IPV6_MANAGETEMPADDR |
a1f7b32c RM |
1400 | /* New address to dhcpcd RA handling. |
1401 | * If the address already exists and a valid | |
1402 | * temporary address also exists then | |
1403 | * extend the existing one rather than | |
1404 | * create a new one */ | |
d292d54e | 1405 | if (flags & IPV6_AF_AUTOCONF && |
3f3a2bb8 | 1406 | ipv6_iffindaddr(ifp, &ia->addr, |
5119f4f3 | 1407 | IN6_IFF_NOTUSEABLE) && |
3f3a2bb8 RM |
1408 | ipv6_settemptime(ia, 0)) |
1409 | new_ia = false; | |
a1f7b32c | 1410 | else |
3f3a2bb8 | 1411 | new_ia = true; |
727cd92a | 1412 | #endif |
41b8ff09 | 1413 | |
a1f7b32c | 1414 | } else { |
41b8ff09 RM |
1415 | uint32_t rmtime; |
1416 | ||
1417 | /* | |
1418 | * RFC 4862 5.5.3.e | |
1419 | * Don't terminate existing connections. | |
1420 | * This means that to actually remove the | |
1421 | * existing prefix, the RA needs to stop | |
1422 | * broadcasting the prefix and just let it | |
1423 | * expire in 2 hours. | |
1424 | * It might want to broadcast it to reduce | |
1425 | * the vltime if it was greater than 2 hours | |
1426 | * to start with/ | |
1427 | */ | |
1428 | ia->prefix_pltime = pltime; | |
1429 | if (ia->prefix_vltime) { | |
1430 | uint32_t elapsed; | |
1431 | ||
1432 | elapsed = (uint32_t)eloop_timespec_diff( | |
1433 | &rap->acquired, &ia->acquired, | |
1434 | NULL); | |
1435 | rmtime = ia->prefix_vltime - elapsed; | |
1436 | if (rmtime > ia->prefix_vltime) | |
1437 | rmtime = 0; | |
1438 | } else | |
1439 | rmtime = 0; | |
1440 | if (vltime > MIN_EXTENDED_VLTIME || | |
1441 | vltime > rmtime) | |
1442 | ia->prefix_vltime = vltime; | |
1443 | else if (rmtime <= MIN_EXTENDED_VLTIME) | |
1444 | /* No SEND support from RFC 3971 so | |
1445 | * leave vltime alone */ | |
1446 | ia->prefix_vltime = rmtime; | |
1447 | else | |
1448 | ia->prefix_vltime = MIN_EXTENDED_VLTIME; | |
1449 | ||
1450 | /* Ensure pltime still fits */ | |
1451 | if (pltime < ia->prefix_vltime) | |
1452 | ia->prefix_pltime = pltime; | |
1453 | else | |
1454 | ia->prefix_pltime = ia->prefix_vltime; | |
1455 | ||
77450503 | 1456 | ia->flags |= flags; |
3f3a2bb8 RM |
1457 | ia->flags &= ~IPV6_AF_STALE; |
1458 | ia->acquired = rap->acquired; | |
41b8ff09 RM |
1459 | |
1460 | #ifdef IPV6_MANAGETEMPADDR | |
1461 | new_ia = false; | |
1462 | #endif | |
a1f7b32c | 1463 | } |
41b8ff09 | 1464 | |
3f3a2bb8 RM |
1465 | if (ia->prefix_vltime != 0 && |
1466 | ia->flags & IPV6_AF_AUTOCONF) | |
e1d81235 | 1467 | has_address = true; |
a1f7b32c | 1468 | |
727cd92a | 1469 | #ifdef IPV6_MANAGETEMPADDR |
a1f7b32c | 1470 | /* RFC4941 Section 3.3.3 */ |
3f3a2bb8 | 1471 | if (ia->flags & IPV6_AF_AUTOCONF && |
628167b1 | 1472 | ia->iface->options->options & DHCPCD_SLAACTEMP && |
3f3a2bb8 | 1473 | IA6_CANAUTOCONF(ia)) |
a1f7b32c | 1474 | { |
3f3a2bb8 RM |
1475 | if (!new_ia) { |
1476 | if (ipv6_settemptime(ia, 1) == NULL) | |
1477 | new_ia = true; | |
a1f7b32c | 1478 | } |
3f3a2bb8 RM |
1479 | if (new_ia && ia->prefix_pltime) { |
1480 | if (ipv6_createtempaddr(ia, | |
1481 | &ia->acquired) == NULL) | |
94d1ded9 | 1482 | logerr("ipv6_createtempaddr"); |
a1f7b32c RM |
1483 | } |
1484 | } | |
727cd92a | 1485 | #endif |
91cd7324 | 1486 | break; |
41b8ff09 | 1487 | } |
91cd7324 RM |
1488 | |
1489 | case ND_OPT_MTU: | |
19005560 | 1490 | if (len < sizeof(mtu)) { |
b586bfbf | 1491 | logmessage(loglevel, "%s: short MTU option", ifp->name); |
19005560 RM |
1492 | break; |
1493 | } | |
55a59017 RM |
1494 | memcpy(&mtu, p, sizeof(mtu)); |
1495 | mtu.nd_opt_mtu_mtu = ntohl(mtu.nd_opt_mtu_mtu); | |
1496 | if (mtu.nd_opt_mtu_mtu < IPV6_MMTU) { | |
b586bfbf | 1497 | logmessage(loglevel, "%s: invalid MTU %d", |
55a59017 | 1498 | ifp->name, mtu.nd_opt_mtu_mtu); |
eebe9a18 RM |
1499 | break; |
1500 | } | |
91f281ab RM |
1501 | ifmtu = if_getmtu(ifp); |
1502 | if (ifmtu == -1) | |
1503 | logerr("if_getmtu"); | |
1504 | else if (mtu.nd_opt_mtu_mtu > (uint32_t)ifmtu) { | |
b586bfbf | 1505 | logmessage(loglevel, "%s: advertised MTU %d" |
91f281ab RM |
1506 | " is greater than link MTU %d", |
1507 | ifp->name, mtu.nd_opt_mtu_mtu, ifmtu); | |
1508 | rap->mtu = (uint32_t)ifmtu; | |
1509 | } else | |
1510 | rap->mtu = mtu.nd_opt_mtu_mtu; | |
91cd7324 | 1511 | break; |
91cd7324 | 1512 | case ND_OPT_RDNSS: |
19005560 | 1513 | if (len < sizeof(rdnss)) { |
b586bfbf | 1514 | logmessage(loglevel, "%s: short RDNSS option", ifp->name); |
19005560 RM |
1515 | break; |
1516 | } | |
55a59017 RM |
1517 | memcpy(&rdnss, p, sizeof(rdnss)); |
1518 | if (rdnss.nd_opt_rdnss_lifetime && | |
1519 | rdnss.nd_opt_rdnss_len > 1) | |
2be15e88 | 1520 | rap->hasdns = 1; |
55a59017 | 1521 | break; |
f1cf924a DG |
1522 | case ND_OPT_RI: |
1523 | if (ndo.nd_opt_len > 3) { | |
1524 | logmessage(loglevel, "%s: invalid route info option", | |
1525 | ifp->name); | |
1526 | break; | |
1527 | } | |
1528 | memset(&ri, 0, sizeof(ri)); | |
1529 | memcpy(&ri, p, olen); /* may be smaller than sizeof(ri), pad with zero */ | |
1530 | if(ri.nd_opt_ri_prefixlen > 128) { | |
1531 | logmessage(loglevel, "%s: invalid route info prefix length", | |
1532 | ifp->name); | |
1533 | break; | |
1534 | } | |
1535 | ||
1536 | /* rfc4191 3.1 - RI for ::/0 applies to default route */ | |
1537 | if(ri.nd_opt_ri_prefixlen == 0) { | |
1538 | rap->lifetime = ntohl(ri.nd_opt_ri_lifetime); | |
1539 | ||
1540 | /* Update preference leaving other flags intact */ | |
1541 | rap->flags = ((rap->flags & (~ (unsigned int)ND_RA_FLAG_RTPREF_MASK)) | |
1542 | | ri.nd_opt_ri_flags_reserved) & 0xff; | |
1543 | ||
1544 | break; | |
1545 | } | |
1546 | ||
1547 | /* Update existing route info instead of rebuilding all routes so that | |
1548 | previously announced but now absent routes can stay alive. To kill a | |
1549 | route early, an RI with lifetime=0 needs to be received (rfc4191 3.1)*/ | |
1550 | rinfo = routeinfo_findalloc(rap, &ri.nd_opt_ri_prefix, ri.nd_opt_ri_prefixlen); | |
1551 | if(rinfo == NULL) { | |
1552 | logerr(__func__); | |
1553 | break; | |
1554 | } | |
1555 | ||
1556 | /* Update/initialize other route info params */ | |
1557 | rinfo->flags = ri.nd_opt_ri_flags_reserved; | |
1558 | rinfo->lifetime = ntohl(ri.nd_opt_ri_lifetime); | |
1559 | rinfo->acquired = rap->acquired; | |
1560 | ||
1561 | break; | |
17b0dbad RM |
1562 | default: |
1563 | continue; | |
91cd7324 | 1564 | } |
2be15e88 | 1565 | } |
91cd7324 | 1566 | |
cc9d9bf8 RM |
1567 | for (i = 0, dho = ctx->nd_opts; |
1568 | i < ctx->nd_opts_len; | |
2be15e88 RM |
1569 | i++, dho++) |
1570 | { | |
1571 | if (has_option_mask(ifp->options->requiremasknd, | |
1572 | dho->option)) | |
1573 | { | |
94d1ded9 | 1574 | logwarnx("%s: reject RA (no option %s) from %s", |
5fed9d43 | 1575 | ifp->name, dho->var, rap->sfrom); |
2be15e88 RM |
1576 | if (new_rap) |
1577 | ipv6nd_removefreedrop_ra(rap, 0, 0); | |
1578 | else | |
1579 | ipv6nd_free_ra(rap); | |
1580 | return; | |
fd3e7f65 | 1581 | } |
91cd7324 RM |
1582 | } |
1583 | ||
a287b9f1 RM |
1584 | TAILQ_FOREACH(ia, &rap->addrs, next) { |
1585 | if (!(ia->flags & IPV6_AF_STALE) || ia->prefix_pltime == 0) | |
1586 | continue; | |
56658009 | 1587 | if (ipv6nd_findmarkstale(rap, ia, false) != NULL) |
8d9b31b3 | 1588 | continue; |
56658009 | 1589 | ipv6nd_findmarkstale(rap, ia, true); |
a287b9f1 | 1590 | logdebugx("%s: %s: became stale", ifp->name, ia->saddr); |
1fd49243 RM |
1591 | /* Technically this violates RFC 4861 6.3.4, |
1592 | * but we need a mechanism to tell the kernel to | |
1593 | * try and prefer other addresses. */ | |
a287b9f1 RM |
1594 | ia->prefix_pltime = 0; |
1595 | } | |
1596 | ||
f1cf924a DG |
1597 | if (!new_rap && rap->lifetime == 0 && old_lifetime != 0) |
1598 | logwarnx("%s: %s: no longer a default router (lifetime = 0)", | |
1599 | ifp->name, rap->sfrom); | |
1600 | ||
2489dc55 | 1601 | if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp)) |
e1d81235 RM |
1602 | logwarnx("%s: no global addresses for default route", |
1603 | ifp->name); | |
1604 | ||
eebe9a18 | 1605 | if (new_rap) |
437bf2a8 RM |
1606 | TAILQ_INSERT_TAIL(ctx->ra_routers, rap, next); |
1607 | if (new_data) | |
1608 | ipv6nd_sortrouters(ifp->ctx); | |
2be15e88 | 1609 | |
4eb7b489 | 1610 | if (ifp->ctx->options & DHCPCD_TEST) { |
294eff4d | 1611 | script_runreason(ifp, "TEST"); |
d7555c12 | 1612 | goto handle_flag; |
b88df421 | 1613 | } |
e9dfc241 RM |
1614 | |
1615 | if (!(ifp->options->options & DHCPCD_CONFIGURE)) | |
1616 | goto run; | |
1617 | ||
8d885c0f | 1618 | ipv6nd_applyra(ifp); |
7529fdf1 | 1619 | ipv6_addaddrs(&rap->addrs); |
727cd92a | 1620 | #ifdef IPV6_MANAGETEMPADDR |
f5c3ca19 | 1621 | ipv6_addtempaddrs(ifp, &rap->acquired); |
727cd92a | 1622 | #endif |
9aa11487 | 1623 | rt_build(ifp->ctx, AF_INET6); |
e9dfc241 RM |
1624 | |
1625 | run: | |
9b4d745e | 1626 | ipv6nd_scriptrun(rap); |
61dd6cf9 | 1627 | |
4eb7b489 RM |
1628 | eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); |
1629 | eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */ | |
eebe9a18 | 1630 | |
d7555c12 | 1631 | handle_flag: |
f6794c78 RM |
1632 | if (!(ifp->options->options & DHCPCD_DHCP6)) |
1633 | goto nodhcp6; | |
6a765a4f RM |
1634 | /* Only log a DHCPv6 start error if compiled in or debugging is enabled. */ |
1635 | #ifdef DHCP6 | |
94d1ded9 | 1636 | #define LOG_DHCP6 logerr |
6a765a4f | 1637 | #else |
94d1ded9 | 1638 | #define LOG_DHCP6 logdebug |
6a765a4f | 1639 | #endif |
d7555c12 | 1640 | if (rap->flags & ND_RA_FLAG_MANAGED) { |
385479d2 | 1641 | if (new_data && dhcp6_start(ifp, DH6S_REQUEST) == -1) |
94d1ded9 | 1642 | LOG_DHCP6("dhcp6_start: %s", ifp->name); |
d7555c12 | 1643 | } else if (rap->flags & ND_RA_FLAG_OTHER) { |
4f422dd3 | 1644 | if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1) |
94d1ded9 | 1645 | LOG_DHCP6("dhcp6_start: %s", ifp->name); |
d7555c12 | 1646 | } else { |
a1b1f0a8 | 1647 | #ifdef DHCP6 |
4f422dd3 | 1648 | if (new_data) |
0e56d022 | 1649 | logdebugx("%s: No DHCPv6 instruction in RA", ifp->name); |
a1b1f0a8 | 1650 | #endif |
f6794c78 | 1651 | nodhcp6: |
4eb7b489 RM |
1652 | if (ifp->ctx->options & DHCPCD_TEST) { |
1653 | eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); | |
a9d78def RM |
1654 | return; |
1655 | } | |
d7555c12 | 1656 | } |
35308011 RM |
1657 | |
1658 | /* Expire should be called last as the rap object could be destroyed */ | |
e82129a4 | 1659 | ipv6nd_expirera(ifp); |
eebe9a18 RM |
1660 | } |
1661 | ||
e1d81235 RM |
1662 | bool |
1663 | ipv6nd_hasralifetime(const struct interface *ifp, bool lifetime) | |
eebe9a18 RM |
1664 | { |
1665 | const struct ra *rap; | |
1666 | ||
cc9d9bf8 RM |
1667 | if (ifp->ctx->ra_routers) { |
1668 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) | |
3e0c93a4 RM |
1669 | if (rap->iface == ifp && |
1670 | !rap->expired && | |
e1d81235 RM |
1671 | (!lifetime ||rap->lifetime)) |
1672 | return true; | |
2433e54d | 1673 | } |
e1d81235 | 1674 | return false; |
91cd7324 RM |
1675 | } |
1676 | ||
e1d81235 | 1677 | bool |
3e0c93a4 | 1678 | ipv6nd_hasradhcp(const struct interface *ifp, bool managed) |
047235d7 RM |
1679 | { |
1680 | const struct ra *rap; | |
1681 | ||
cc9d9bf8 RM |
1682 | if (ifp->ctx->ra_routers) { |
1683 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { | |
047235d7 | 1684 | if (rap->iface == ifp && |
3e0c93a4 RM |
1685 | !rap->expired && !rap->willexpire && |
1686 | ((managed && rap->flags & ND_RA_FLAG_MANAGED) || | |
1687 | (!managed && rap->flags & ND_RA_FLAG_OTHER))) | |
e1d81235 | 1688 | return true; |
047235d7 RM |
1689 | } |
1690 | } | |
e1d81235 | 1691 | return false; |
047235d7 RM |
1692 | } |
1693 | ||
2be15e88 RM |
1694 | static const uint8_t * |
1695 | ipv6nd_getoption(struct dhcpcd_ctx *ctx, | |
1696 | size_t *os, unsigned int *code, size_t *len, | |
1697 | const uint8_t *od, size_t ol, struct dhcp_opt **oopt) | |
1698 | { | |
55a59017 | 1699 | struct nd_opt_hdr ndo; |
2be15e88 RM |
1700 | size_t i; |
1701 | struct dhcp_opt *opt; | |
1702 | ||
1703 | if (od) { | |
55a59017 | 1704 | *os = sizeof(ndo); |
2be15e88 RM |
1705 | if (ol < *os) { |
1706 | errno = EINVAL; | |
1707 | return NULL; | |
1708 | } | |
55a59017 | 1709 | memcpy(&ndo, od, sizeof(ndo)); |
81e9fc13 | 1710 | i = (size_t)(ndo.nd_opt_len * 8); |
55a59017 | 1711 | if (i > ol) { |
2be15e88 RM |
1712 | errno = EINVAL; |
1713 | return NULL; | |
1714 | } | |
55a59017 RM |
1715 | *len = i; |
1716 | *code = ndo.nd_opt_type; | |
1717 | } | |
2be15e88 RM |
1718 | |
1719 | for (i = 0, opt = ctx->nd_opts; | |
1720 | i < ctx->nd_opts_len; i++, opt++) | |
1721 | { | |
1722 | if (opt->option == *code) { | |
1723 | *oopt = opt; | |
1724 | break; | |
1725 | } | |
1726 | } | |
1727 | ||
55a59017 RM |
1728 | if (od) |
1729 | return od + sizeof(ndo); | |
2be15e88 RM |
1730 | return NULL; |
1731 | } | |
1732 | ||
91cd7324 | 1733 | ssize_t |
c8521994 | 1734 | ipv6nd_env(FILE *fp, const struct interface *ifp) |
91cd7324 | 1735 | { |
55a59017 | 1736 | size_t i, j, n, len, olen; |
2be15e88 | 1737 | struct ra *rap; |
c8521994 | 1738 | char ndprefix[32]; |
2be15e88 | 1739 | struct dhcp_opt *opt; |
55a59017 RM |
1740 | uint8_t *p; |
1741 | struct nd_opt_hdr ndo; | |
2be15e88 | 1742 | struct ipv6_addr *ia; |
f5c3ca19 | 1743 | struct timespec now; |
77450503 | 1744 | int pref; |
eebe9a18 | 1745 | |
f5c3ca19 | 1746 | clock_gettime(CLOCK_MONOTONIC, &now); |
2be15e88 | 1747 | i = n = 0; |
cc9d9bf8 | 1748 | TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { |
964b60fe | 1749 | if (rap->iface != ifp || rap->expired) |
eebe9a18 | 1750 | continue; |
0d593c43 | 1751 | i++; |
c8521994 RM |
1752 | snprintf(ndprefix, sizeof(ndprefix), "nd%zu", i); |
1753 | if (efprintf(fp, "%s_from=%s", ndprefix, rap->sfrom) == -1) | |
1754 | return -1; | |
b295db4d RM |
1755 | if (efprintf(fp, "%s_acquired=%lld", ndprefix, |
1756 | (long long)rap->acquired.tv_sec) == -1) | |
c8521994 | 1757 | return -1; |
b295db4d RM |
1758 | if (efprintf(fp, "%s_now=%lld", ndprefix, |
1759 | (long long)now.tv_sec) == -1) | |
c8521994 | 1760 | return -1; |
77450503 RM |
1761 | if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1) |
1762 | return -1; | |
f1cf924a | 1763 | pref = ipv6nd_rtpref(rap->flags); |
2b6cfac5 | 1764 | if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix, |
77450503 RM |
1765 | rap->flags & ND_RA_FLAG_MANAGED ? "M" : "", |
1766 | rap->flags & ND_RA_FLAG_OTHER ? "O" : "", | |
1767 | rap->flags & ND_RA_FLAG_HOME_AGENT ? "H" : "", | |
2b6cfac5 RM |
1768 | pref == RTPREF_HIGH ? "h" : pref == RTPREF_LOW ? "l" : "", |
1769 | rap->flags & ND_RA_FLAG_PROXY ? "P" : "") == -1) | |
77450503 RM |
1770 | return -1; |
1771 | if (efprintf(fp, "%s_lifetime=%u", ndprefix, rap->lifetime) == -1) | |
1772 | return -1; | |
2be15e88 RM |
1773 | |
1774 | /* Zero our indexes */ | |
c8521994 RM |
1775 | for (j = 0, opt = rap->iface->ctx->nd_opts; |
1776 | j < rap->iface->ctx->nd_opts_len; | |
1777 | j++, opt++) | |
1778 | dhcp_zero_index(opt); | |
1779 | for (j = 0, opt = rap->iface->options->nd_override; | |
1780 | j < rap->iface->options->nd_override_len; | |
1781 | j++, opt++) | |
1782 | dhcp_zero_index(opt); | |
449df9c8 | 1783 | |
2be15e88 RM |
1784 | /* Unlike DHCP, ND6 options *may* occur more than once. |
1785 | * There is also no provision for option concatenation | |
1786 | * unlike DHCP. */ | |
55a59017 RM |
1787 | len = rap->data_len - sizeof(struct nd_router_advert); |
1788 | for (p = rap->data + sizeof(struct nd_router_advert); | |
1789 | len >= sizeof(ndo); | |
1790 | p += olen, len -= olen) | |
2be15e88 | 1791 | { |
55a59017 | 1792 | memcpy(&ndo, p, sizeof(ndo)); |
81e9fc13 | 1793 | olen = (size_t)(ndo.nd_opt_len * 8); |
55a59017 | 1794 | if (olen > len) { |
2be15e88 | 1795 | errno = EINVAL; |
91cd7324 | 1796 | break; |
91cd7324 | 1797 | } |
2be15e88 | 1798 | if (has_option_mask(rap->iface->options->nomasknd, |
55a59017 | 1799 | ndo.nd_opt_type)) |
28382337 | 1800 | continue; |
2be15e88 RM |
1801 | for (j = 0, opt = rap->iface->options->nd_override; |
1802 | j < rap->iface->options->nd_override_len; | |
1803 | j++, opt++) | |
55a59017 | 1804 | if (opt->option == ndo.nd_opt_type) |
2be15e88 RM |
1805 | break; |
1806 | if (j == rap->iface->options->nd_override_len) { | |
1807 | for (j = 0, opt = rap->iface->ctx->nd_opts; | |
1808 | j < rap->iface->ctx->nd_opts_len; | |
1809 | j++, opt++) | |
55a59017 | 1810 | if (opt->option == ndo.nd_opt_type) |
2be15e88 RM |
1811 | break; |
1812 | if (j == rap->iface->ctx->nd_opts_len) | |
1813 | opt = NULL; | |
1814 | } | |
c8521994 RM |
1815 | if (opt == NULL) |
1816 | continue; | |
1817 | dhcp_envoption(rap->iface->ctx, fp, | |
1818 | ndprefix, rap->iface->name, | |
1819 | opt, ipv6nd_getoption, | |
1820 | p + sizeof(ndo), olen - sizeof(ndo)); | |
2be15e88 RM |
1821 | } |
1822 | ||
1823 | /* We need to output the addresses we actually made | |
1824 | * from the prefix information options as well. */ | |
1825 | j = 0; | |
1826 | TAILQ_FOREACH(ia, &rap->addrs, next) { | |
19005560 | 1827 | if (!(ia->flags & IPV6_AF_AUTOCONF) || |
f5c3ca19 | 1828 | #ifdef IPV6_AF_TEMPORARY |
19005560 | 1829 | ia->flags & IPV6_AF_TEMPORARY || |
f5c3ca19 | 1830 | #endif |
19005560 RM |
1831 | !(ia->flags & IPV6_AF_ADDED) || |
1832 | ia->prefix_vltime == 0) | |
2be15e88 | 1833 | continue; |
c8521994 | 1834 | if (efprintf(fp, "%s_addr%zu=%s", |
3390cf07 | 1835 | ndprefix, ++j, ia->saddr) == -1) |
c8521994 | 1836 | return -1; |
91cd7324 RM |
1837 | } |
1838 | } | |
c8521994 | 1839 | return 1; |
91cd7324 RM |
1840 | } |
1841 | ||
a8df1b28 | 1842 | void |
90149620 | 1843 | ipv6nd_handleifa(int cmd, struct ipv6_addr *addr, pid_t pid) |
a8df1b28 RM |
1844 | { |
1845 | struct ra *rap; | |
a8df1b28 | 1846 | |
d3826f33 RM |
1847 | /* IPv6 init may not have happened yet if we are learning |
1848 | * existing addresses when dhcpcd starts. */ | |
cc9d9bf8 | 1849 | if (addr->iface->ctx->ra_routers == NULL) |
d3826f33 RM |
1850 | return; |
1851 | ||
cc9d9bf8 | 1852 | TAILQ_FOREACH(rap, addr->iface->ctx->ra_routers, next) { |
e83c4813 | 1853 | if (rap->iface != addr->iface) |
a8df1b28 | 1854 | continue; |
90149620 | 1855 | ipv6_handleifa_addrs(cmd, &rap->addrs, addr, pid); |
a8df1b28 RM |
1856 | } |
1857 | } | |
1858 | ||
91cd7324 | 1859 | void |
e82129a4 | 1860 | ipv6nd_expirera(void *arg) |
91cd7324 RM |
1861 | { |
1862 | struct interface *ifp; | |
eebe9a18 | 1863 | struct ra *rap, *ran; |
8fde4abc RM |
1864 | struct timespec now; |
1865 | uint32_t elapsed; | |
964b60fe | 1866 | bool expired, valid; |
6d189cb6 | 1867 | struct ipv6_addr *ia; |
f1cf924a | 1868 | struct routeinfo *rinfo, *rinfob; |
19005560 RM |
1869 | size_t len, olen; |
1870 | uint8_t *p; | |
1871 | struct nd_opt_hdr ndo; | |
1872 | #if 0 | |
1873 | struct nd_opt_prefix_info pi; | |
a1b1f0a8 | 1874 | #endif |
19005560 RM |
1875 | struct nd_opt_dnssl dnssl; |
1876 | struct nd_opt_rdnss rdnss; | |
826d1f25 | 1877 | unsigned int next = 0, ltime; |
19005560 | 1878 | size_t nexpired = 0; |
91cd7324 RM |
1879 | |
1880 | ifp = arg; | |
b3f1735b | 1881 | clock_gettime(CLOCK_MONOTONIC, &now); |
a1b1f0a8 | 1882 | expired = false; |
91cd7324 | 1883 | |
cc9d9bf8 | 1884 | TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { |
964b60fe | 1885 | if (rap->iface != ifp || rap->expired) |
eebe9a18 | 1886 | continue; |
964b60fe | 1887 | valid = false; |
f1cf924a DG |
1888 | /* lifetime may be set to infinite by rfc4191 route information */ |
1889 | if (rap->lifetime && rap->lifetime != ND6_INFINITE_LIFETIME) { | |
8fde4abc RM |
1890 | elapsed = (uint32_t)eloop_timespec_diff(&now, |
1891 | &rap->acquired, NULL); | |
9efd0777 | 1892 | if (elapsed >= rap->lifetime || rap->doexpire) { |
4f422dd3 | 1893 | if (!rap->expired) { |
94d1ded9 | 1894 | logwarnx("%s: %s: router expired", |
4f422dd3 | 1895 | ifp->name, rap->sfrom); |
2f53bfd4 | 1896 | rap->lifetime = 0; |
964b60fe | 1897 | expired = true; |
4f422dd3 RM |
1898 | } |
1899 | } else { | |
a1b1f0a8 | 1900 | valid = true; |
8fde4abc | 1901 | ltime = rap->lifetime - elapsed; |
826d1f25 RM |
1902 | if (next == 0 || ltime < next) |
1903 | next = ltime; | |
35308011 | 1904 | } |
35308011 RM |
1905 | } |
1906 | ||
6d189cb6 RM |
1907 | /* Not every prefix is tied to an address which |
1908 | * the kernel can expire, so we need to handle it ourself. | |
1909 | * Also, some OS don't support address lifetimes (Solaris). */ | |
1910 | TAILQ_FOREACH(ia, &rap->addrs, next) { | |
680ed015 | 1911 | if (ia->prefix_vltime == 0) |
6d189cb6 | 1912 | continue; |
3e0c93a4 RM |
1913 | if (ia->prefix_vltime == ND6_INFINITE_LIFETIME && |
1914 | !rap->doexpire) | |
1915 | { | |
964b60fe | 1916 | valid = true; |
680ed015 RM |
1917 | continue; |
1918 | } | |
8fde4abc RM |
1919 | elapsed = (uint32_t)eloop_timespec_diff(&now, |
1920 | &ia->acquired, NULL); | |
9efd0777 | 1921 | if (elapsed >= ia->prefix_vltime || rap->doexpire) { |
6d189cb6 | 1922 | if (ia->flags & IPV6_AF_ADDED) { |
b895d40d RM |
1923 | logwarnx("%s: expired %s %s", |
1924 | ia->iface->name, | |
1925 | ia->flags & IPV6_AF_AUTOCONF ? | |
1926 | "address" : "prefix", | |
1927 | ia->saddr); | |
6d189cb6 RM |
1928 | if (if_address6(RTM_DELADDR, ia)== -1 && |
1929 | errno != EADDRNOTAVAIL && | |
1930 | errno != ENXIO) | |
94d1ded9 | 1931 | logerr(__func__); |
6d189cb6 RM |
1932 | } |
1933 | ia->prefix_vltime = ia->prefix_pltime = 0; | |
1934 | ia->flags &= | |
1935 | ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); | |
a1b1f0a8 | 1936 | expired = true; |
6d189cb6 | 1937 | } else { |
964b60fe | 1938 | valid = true; |
8fde4abc | 1939 | ltime = ia->prefix_vltime - elapsed; |
826d1f25 RM |
1940 | if (next == 0 || ltime < next) |
1941 | next = ltime; | |
6d189cb6 RM |
1942 | } |
1943 | } | |
1944 | ||
f1cf924a DG |
1945 | /* Expire route information */ |
1946 | TAILQ_FOREACH_SAFE(rinfo, &rap->rinfos, next, rinfob) { | |
1947 | if (rinfo->lifetime == ND6_INFINITE_LIFETIME && | |
1948 | !rap->doexpire) | |
1949 | continue; | |
1950 | elapsed = (uint32_t)eloop_timespec_diff(&now, | |
1951 | &rinfo->acquired, NULL); | |
1952 | if (elapsed >= rinfo->lifetime || rap->doexpire) { | |
1953 | logwarnx("%s: expired route %s", | |
1954 | rap->iface->name, rinfo->sprefix); | |
1955 | TAILQ_REMOVE(&rap->rinfos, rinfo, next); | |
1956 | } | |
1957 | } | |
1958 | ||
19005560 | 1959 | /* Work out expiry for ND options */ |
8fde4abc RM |
1960 | elapsed = (uint32_t)eloop_timespec_diff(&now, |
1961 | &rap->acquired, NULL); | |
19005560 RM |
1962 | len = rap->data_len - sizeof(struct nd_router_advert); |
1963 | for (p = rap->data + sizeof(struct nd_router_advert); | |
1964 | len >= sizeof(ndo); | |
1965 | p += olen, len -= olen) | |
1966 | { | |
1967 | memcpy(&ndo, p, sizeof(ndo)); | |
1968 | olen = (size_t)(ndo.nd_opt_len * 8); | |
1969 | if (olen > len) { | |
1970 | errno = EINVAL; | |
1971 | break; | |
1972 | } | |
d4e41f4b | 1973 | |
19005560 RM |
1974 | if (has_option_mask(rap->iface->options->nomasknd, |
1975 | ndo.nd_opt_type)) | |
1976 | continue; | |
1977 | ||
1978 | switch (ndo.nd_opt_type) { | |
1979 | /* Prefix info is already checked in the above loop. */ | |
1980 | #if 0 | |
1981 | case ND_OPT_PREFIX_INFORMATION: | |
1982 | if (len < sizeof(pi)) | |
1983 | break; | |
1984 | memcpy(&pi, p, sizeof(pi)); | |
1985 | ltime = pi.nd_opt_pi_valid_time; | |
1986 | break; | |
a1b1f0a8 | 1987 | #endif |
19005560 RM |
1988 | case ND_OPT_DNSSL: |
1989 | if (len < sizeof(dnssl)) | |
a3a4b5e3 | 1990 | continue; |
19005560 RM |
1991 | memcpy(&dnssl, p, sizeof(dnssl)); |
1992 | ltime = dnssl.nd_opt_dnssl_lifetime; | |
1993 | break; | |
1994 | case ND_OPT_RDNSS: | |
1995 | if (len < sizeof(rdnss)) | |
a3a4b5e3 | 1996 | continue; |
19005560 RM |
1997 | memcpy(&rdnss, p, sizeof(rdnss)); |
1998 | ltime = rdnss.nd_opt_rdnss_lifetime; | |
1999 | break; | |
2000 | default: | |
2001 | continue; | |
2002 | } | |
2003 | ||
2004 | if (ltime == 0) | |
2005 | continue; | |
3e0c93a4 RM |
2006 | if (rap->doexpire) { |
2007 | expired = true; | |
2008 | continue; | |
2009 | } | |
19005560 | 2010 | if (ltime == ND6_INFINITE_LIFETIME) { |
964b60fe | 2011 | valid = true; |
19005560 RM |
2012 | continue; |
2013 | } | |
2014 | ||
826d1f25 | 2015 | ltime = ntohl(ltime); |
9efd0777 | 2016 | if (elapsed >= ltime) { |
19005560 RM |
2017 | expired = true; |
2018 | continue; | |
2019 | } | |
2020 | ||
826d1f25 | 2021 | valid = true; |
8fde4abc | 2022 | ltime -= elapsed; |
826d1f25 RM |
2023 | if (next == 0 || ltime < next) |
2024 | next = ltime; | |
19005560 RM |
2025 | } |
2026 | ||
964b60fe | 2027 | if (valid) |
19005560 | 2028 | continue; |
d4e41f4b | 2029 | |
964b60fe RM |
2030 | /* Router has expired. Let's not keep a lot of them. */ |
2031 | rap->expired = true; | |
19005560 | 2032 | if (++nexpired > EXPIRED_MAX) |
e82129a4 | 2033 | ipv6nd_free_ra(rap); |
91cd7324 RM |
2034 | } |
2035 | ||
826d1f25 RM |
2036 | if (next != 0) |
2037 | eloop_timeout_add_sec(ifp->ctx->eloop, | |
2038 | next, ipv6nd_expirera, ifp); | |
e82129a4 | 2039 | if (expired) { |
3e0c93a4 RM |
2040 | logwarnx("%s: part of a Router Advertisement expired", |
2041 | ifp->name); | |
8d885c0f RM |
2042 | ipv6nd_sortrouters(ifp->ctx); |
2043 | ipv6nd_applyra(ifp); | |
9aa11487 | 2044 | rt_build(ifp->ctx, AF_INET6); |
e82129a4 RM |
2045 | script_runreason(ifp, "ROUTERADVERT"); |
2046 | } | |
2047 | } | |
2048 | ||
2049 | void | |
2050 | ipv6nd_drop(struct interface *ifp) | |
2051 | { | |
9a593d97 | 2052 | struct ra *rap, *ran; |
964b60fe | 2053 | bool expired = false; |
e82129a4 | 2054 | |
cc9d9bf8 | 2055 | if (ifp->ctx->ra_routers == NULL) |
2433e54d RM |
2056 | return; |
2057 | ||
4eb7b489 | 2058 | eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); |
9a593d97 | 2059 | TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { |
e82129a4 | 2060 | if (rap->iface == ifp) { |
964b60fe | 2061 | rap->expired = expired = true; |
9a593d97 | 2062 | ipv6nd_drop_ra(rap); |
e82129a4 RM |
2063 | } |
2064 | } | |
eebe9a18 | 2065 | if (expired) { |
8d885c0f | 2066 | ipv6nd_applyra(ifp); |
9aa11487 | 2067 | rt_build(ifp->ctx, AF_INET6); |
389a250b | 2068 | if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP) |
15fc1181 | 2069 | script_runreason(ifp, "ROUTERADVERT"); |
e82129a4 RM |
2070 | } |
2071 | } | |
a9d78def | 2072 | |
65025848 | 2073 | void |
c548c5b3 RM |
2074 | ipv6nd_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg) |
2075 | { | |
2076 | struct sockaddr_in6 *from = (struct sockaddr_in6 *)msg->msg_name; | |
2077 | char sfrom[INET6_ADDRSTRLEN]; | |
2078 | int hoplimit = 0; | |
2079 | struct icmp6_hdr *icp; | |
2080 | struct interface *ifp; | |
2081 | size_t len = msg->msg_iov[0].iov_len; | |
2082 | ||
2083 | inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom)); | |
2084 | if ((size_t)len < sizeof(struct icmp6_hdr)) { | |
2085 | logerrx("IPv6 ICMP packet too short from %s", sfrom); | |
2086 | return; | |
2087 | } | |
2088 | ||
c548c5b3 RM |
2089 | ifp = if_findifpfromcmsg(ctx, msg, &hoplimit); |
2090 | if (ifp == NULL) { | |
2091 | logerr(__func__); | |
2092 | return; | |
2093 | } | |
c548c5b3 RM |
2094 | |
2095 | /* Don't do anything if the user hasn't configured it. */ | |
2096 | if (ifp->active != IF_ACTIVE_USER || | |
2097 | !(ifp->options->options & DHCPCD_IPV6)) | |
2098 | return; | |
2099 | ||
2100 | icp = (struct icmp6_hdr *)msg->msg_iov[0].iov_base; | |
2101 | if (icp->icmp6_code == 0) { | |
2102 | switch(icp->icmp6_type) { | |
2103 | case ND_ROUTER_ADVERT: | |
2104 | ipv6nd_handlera(ctx, from, sfrom, | |
2105 | ifp, icp, (size_t)len, hoplimit); | |
2106 | return; | |
2107 | } | |
2108 | } | |
2109 | ||
2110 | logerrx("invalid IPv6 type %d or code %d from %s", | |
2111 | icp->icmp6_type, icp->icmp6_code, sfrom); | |
2112 | } | |
2113 | ||
e82129a4 | 2114 | static void |
5d619328 | 2115 | ipv6nd_handledata(void *arg, unsigned short events) |
e82129a4 | 2116 | { |
cc9d9bf8 | 2117 | struct dhcpcd_ctx *ctx; |
49d6a036 | 2118 | int fd; |
5fed9d43 | 2119 | struct sockaddr_in6 from; |
032bb2e1 RM |
2120 | union { |
2121 | struct icmp6_hdr hdr; | |
2122 | uint8_t buf[64 * 1024]; /* Maximum ICMPv6 size */ | |
2123 | } iovbuf; | |
5fed9d43 | 2124 | struct iovec iov = { |
032bb2e1 | 2125 | .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf), |
5fed9d43 | 2126 | }; |
e14045ca RM |
2127 | union { |
2128 | struct cmsghdr hdr; | |
2129 | uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + | |
2130 | CMSG_SPACE(sizeof(int))]; | |
2131 | } cmsgbuf = { .buf = { 0 } }; | |
5fed9d43 RM |
2132 | struct msghdr msg = { |
2133 | .msg_name = &from, .msg_namelen = sizeof(from), | |
2134 | .msg_iov = &iov, .msg_iovlen = 1, | |
e14045ca | 2135 | .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), |
5fed9d43 | 2136 | }; |
e82129a4 | 2137 | ssize_t len; |
e82129a4 | 2138 | |
b2edc303 | 2139 | #ifdef __sun |
49d6a036 | 2140 | struct interface *ifp; |
b2edc303 RM |
2141 | struct rs_state *state; |
2142 | ||
2143 | ifp = arg; | |
2144 | state = RS_STATE(ifp); | |
2145 | ctx = ifp->ctx; | |
49d6a036 | 2146 | fd = state->nd_fd; |
b2edc303 | 2147 | #else |
cc9d9bf8 | 2148 | ctx = arg; |
49d6a036 | 2149 | fd = ctx->nd_fd; |
b2edc303 | 2150 | #endif |
5d619328 RM |
2151 | |
2152 | if (events != ELE_READ) | |
2153 | logerrx("%s: unexpected event 0x%04x", __func__, events); | |
2154 | ||
49d6a036 | 2155 | len = recvmsg(fd, &msg, 0); |
fddd88ae | 2156 | if (len == -1) { |
94d1ded9 | 2157 | logerr(__func__); |
e82129a4 RM |
2158 | return; |
2159 | } | |
e82129a4 | 2160 | |
c548c5b3 RM |
2161 | iov.iov_len = (size_t)len; |
2162 | ipv6nd_recvmsg(ctx, &msg); | |
e82129a4 RM |
2163 | } |
2164 | ||
d936ec19 | 2165 | static void |
6e6e06af | 2166 | ipv6nd_startrs1(void *arg) |
91cd7324 | 2167 | { |
6e6e06af | 2168 | struct interface *ifp = arg; |
ca15a0aa | 2169 | struct rs_state *state; |
91cd7324 | 2170 | |
9efdc92f | 2171 | loginfox("%s: soliciting an IPv6 router", ifp->name); |
673e81e5 RM |
2172 | state = RS_STATE(ifp); |
2173 | if (state == NULL) { | |
e82129a4 | 2174 | ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state)); |
673e81e5 | 2175 | state = RS_STATE(ifp); |
fbbb0875 | 2176 | if (state == NULL) { |
94d1ded9 | 2177 | logerr(__func__); |
6e6e06af | 2178 | return; |
fbbb0875 | 2179 | } |
b0a0f6d4 | 2180 | #ifdef __sun |
b2edc303 | 2181 | state->nd_fd = -1; |
b0a0f6d4 | 2182 | #endif |
673e81e5 RM |
2183 | } |
2184 | ||
2185 | /* Always make a new probe as the underlying hardware | |
2186 | * address could have changed. */ | |
e82129a4 | 2187 | ipv6nd_makersprobe(ifp); |
fbbb0875 | 2188 | if (state->rs == NULL) { |
94d1ded9 | 2189 | logerr(__func__); |
6e6e06af | 2190 | return; |
fbbb0875 | 2191 | } |
91cd7324 | 2192 | |
cd09e583 | 2193 | state->retrans = RETRANS_TIMER; |
ca15a0aa | 2194 | state->rsprobes = 0; |
e82129a4 | 2195 | ipv6nd_sendrsprobe(ifp); |
6e6e06af RM |
2196 | } |
2197 | ||
2198 | void | |
2199 | ipv6nd_startrs(struct interface *ifp) | |
2200 | { | |
858d217d | 2201 | unsigned int delay; |
6e6e06af | 2202 | |
d936ec19 | 2203 | eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); |
f572315d RM |
2204 | if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) { |
2205 | ipv6nd_startrs1(ifp); | |
2206 | return; | |
2207 | } | |
2208 | ||
858d217d | 2209 | delay = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MSEC_PER_SEC); |
0e56d022 | 2210 | logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds", |
858d217d RM |
2211 | ifp->name, (float)delay / MSEC_PER_SEC); |
2212 | eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp); | |
6e6e06af | 2213 | return; |
91cd7324 | 2214 | } |
f1cf924a DG |
2215 | |
2216 | static struct routeinfo *routeinfo_findalloc(struct ra *rap, const struct in6_addr *prefix, uint8_t prefix_len) | |
2217 | { | |
2218 | struct routeinfo *ri; | |
2219 | char buf[INET6_ADDRSTRLEN]; | |
2220 | const char *p; | |
2221 | ||
2222 | TAILQ_FOREACH(ri, &rap->rinfos, next) { | |
2223 | if (ri->prefix_len == prefix_len && | |
2224 | IN6_ARE_ADDR_EQUAL(&ri->prefix, prefix)) | |
2225 | return ri; | |
2226 | } | |
2227 | ||
2228 | ri = malloc(sizeof(struct routeinfo)); | |
2229 | if (ri == NULL) | |
2230 | return NULL; | |
2231 | ||
2232 | memcpy(&ri->prefix, prefix, sizeof(ri->prefix)); | |
2233 | ri->prefix_len = prefix_len; | |
2234 | p = inet_ntop(AF_INET6, prefix, buf, sizeof(buf)); | |
2235 | if (p) | |
2236 | snprintf(ri->sprefix, | |
2237 | sizeof(ri->sprefix), | |
2238 | "%s/%d", | |
2239 | p, prefix_len); | |
2240 | else | |
2241 | ri->sprefix[0] = '\0'; | |
2242 | TAILQ_INSERT_TAIL(&rap->rinfos, ri, next); | |
2243 | return ri; | |
2244 | } | |
2245 | ||
2246 | static void routeinfohead_free(struct routeinfohead *head) | |
2247 | { | |
2248 | struct routeinfo *ri; | |
2249 | ||
2250 | while ((ri = TAILQ_FIRST(head))) { | |
2251 | TAILQ_REMOVE(head, ri, next); | |
2252 | free(ri); | |
2253 | } | |
2254 | } |