]>
Commit | Line | Data |
---|---|---|
8cc47ba2 | 1 | /* |
91cd7324 | 2 | * dhcpcd - DHCP client daemon |
fad320b9 | 3 | * Copyright (c) 2006-2014 Roy Marples <roy@marples.name> |
91cd7324 RM |
4 | * All rights reserved |
5 | ||
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions | |
8 | * are met: | |
9 | * 1. Redistributions of source code must retain the above copyright | |
10 | * notice, this list of conditions and the following disclaimer. | |
11 | * 2. Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * | |
15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
25 | * SUCH DAMAGE. | |
26 | */ | |
27 | ||
eebe9a18 | 28 | #include <sys/ioctl.h> |
91cd7324 RM |
29 | #include <sys/param.h> |
30 | #include <sys/socket.h> | |
31 | #include <net/if.h> | |
32 | #include <netinet/in.h> | |
33 | #include <netinet/ip6.h> | |
34 | #include <netinet/icmp6.h> | |
35 | ||
36 | #include <errno.h> | |
e8c8e9b9 | 37 | #include <fcntl.h> |
91cd7324 RM |
38 | #include <stddef.h> |
39 | #include <stdlib.h> | |
40 | #include <string.h> | |
41 | #include <syslog.h> | |
fbbb0875 | 42 | #include <unistd.h> |
91cd7324 | 43 | |
e5cd8255 | 44 | #define ELOOP_QUEUE 3 |
91cd7324 | 45 | #include "common.h" |
91cd7324 | 46 | #include "dhcpcd.h" |
d7555c12 | 47 | #include "dhcp6.h" |
91cd7324 | 48 | #include "eloop.h" |
a3ee6b23 | 49 | #include "if.h" |
eebe9a18 | 50 | #include "ipv6.h" |
e82129a4 | 51 | #include "ipv6nd.h" |
294eff4d | 52 | #include "script.h" |
91cd7324 | 53 | |
d7555c12 RM |
54 | /* Debugging Router Solicitations is a lot of spam, so disable it */ |
55 | //#define DEBUG_RS | |
56 | ||
91cd7324 RM |
57 | #define RTR_SOLICITATION_INTERVAL 4 /* seconds */ |
58 | #define MAX_RTR_SOLICITATIONS 3 /* times */ | |
59 | ||
60 | #ifndef ND_OPT_RDNSS | |
61 | #define ND_OPT_RDNSS 25 | |
62 | struct nd_opt_rdnss { /* RDNSS option RFC 6106 */ | |
63 | uint8_t nd_opt_rdnss_type; | |
64 | uint8_t nd_opt_rdnss_len; | |
65 | uint16_t nd_opt_rdnss_reserved; | |
66 | uint32_t nd_opt_rdnss_lifetime; | |
67 | /* followed by list of IP prefixes */ | |
3491ea4d | 68 | } __packed; |
91cd7324 RM |
69 | #endif |
70 | ||
71 | #ifndef ND_OPT_DNSSL | |
72 | #define ND_OPT_DNSSL 31 | |
73 | struct nd_opt_dnssl { /* DNSSL option RFC 6106 */ | |
74 | uint8_t nd_opt_dnssl_type; | |
75 | uint8_t nd_opt_dnssl_len; | |
76 | uint16_t nd_opt_dnssl_reserved; | |
77 | uint32_t nd_opt_dnssl_lifetime; | |
78 | /* followed by list of DNS servers */ | |
3491ea4d | 79 | } __packed; |
91cd7324 RM |
80 | #endif |
81 | ||
eebe9a18 RM |
82 | /* Minimal IPv6 MTU */ |
83 | #ifndef IPV6_MMTU | |
84 | #define IPV6_MMTU 1280 | |
85 | #endif | |
86 | ||
87 | #ifndef ND_RA_FLAG_RTPREF_HIGH | |
88 | #define ND_RA_FLAG_RTPREF_MASK 0x18 | |
89 | #define ND_RA_FLAG_RTPREF_HIGH 0x08 | |
90 | #define ND_RA_FLAG_RTPREF_MEDIUM 0x00 | |
91 | #define ND_RA_FLAG_RTPREF_LOW 0x18 | |
92 | #define ND_RA_FLAG_RTPREF_RSV 0x10 | |
93 | #endif | |
94 | ||
95 | /* RTPREF_MEDIUM has to be 0! */ | |
96 | #define RTPREF_HIGH 1 | |
97 | #define RTPREF_MEDIUM 0 | |
98 | #define RTPREF_LOW (-1) | |
99 | #define RTPREF_RESERVED (-2) | |
100 | #define RTPREF_INVALID (-3) /* internal */ | |
101 | ||
e82129a4 RM |
102 | #define MIN_RANDOM_FACTOR 500 /* millisecs */ |
103 | #define MAX_RANDOM_FACTOR 1500 /* millisecs */ | |
104 | #define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */ | |
105 | #define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */ | |
106 | ||
107 | #if BYTE_ORDER == BIG_ENDIAN | |
108 | #define IPV6_ADDR_INT32_ONE 1 | |
109 | #define IPV6_ADDR_INT16_MLL 0xff02 | |
110 | #elif BYTE_ORDER == LITTLE_ENDIAN | |
111 | #define IPV6_ADDR_INT32_ONE 0x01000000 | |
112 | #define IPV6_ADDR_INT16_MLL 0x02ff | |
113 | #endif | |
114 | ||
a3ee6b23 RM |
115 | #define ND6REACHABLE_TIMER 1 |
116 | ||
e82129a4 RM |
117 | /* Debugging Neighbor Solicitations is a lot of spam, so disable it */ |
118 | //#define DEBUG_NS | |
119 | // | |
120 | ||
8d5de853 | 121 | static void ipv6nd_handledata(void *); |
7cece083 | 122 | |
65e5b9f9 RM |
123 | /* |
124 | * Android ships buggy ICMP6 filter headers. | |
125 | * Supply our own until they fix their shit. | |
126 | * References: | |
127 | * https://android-review.googlesource.com/#/c/58438/ | |
128 | * http://code.google.com/p/android/issues/original?id=32621&seq=24 | |
129 | */ | |
130 | #ifdef __ANDROID__ | |
131 | #undef ICMP6_FILTER_WILLPASS | |
132 | #undef ICMP6_FILTER_WILLBLOCK | |
133 | #undef ICMP6_FILTER_SETPASS | |
134 | #undef ICMP6_FILTER_SETBLOCK | |
135 | #undef ICMP6_FILTER_SETPASSALL | |
136 | #undef ICMP6_FILTER_SETBLOCKALL | |
137 | #define ICMP6_FILTER_WILLPASS(type, filterp) \ | |
138 | ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0) | |
139 | #define ICMP6_FILTER_WILLBLOCK(type, filterp) \ | |
140 | ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0) | |
141 | #define ICMP6_FILTER_SETPASS(type, filterp) \ | |
142 | ((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31)))) | |
143 | #define ICMP6_FILTER_SETBLOCK(type, filterp) \ | |
144 | ((((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31)))) | |
145 | #define ICMP6_FILTER_SETPASSALL(filterp) \ | |
146 | memset(filterp, 0, sizeof(struct icmp6_filter)); | |
147 | #define ICMP6_FILTER_SETBLOCKALL(filterp) \ | |
148 | memset(filterp, 0xff, sizeof(struct icmp6_filter)); | |
149 | #endif | |
150 | ||
62247de8 RM |
151 | /* Support older systems with different defines */ |
152 | #if !defined(IPV6_RECVHOPLIMIT) && defined(IPV6_HOPLIMIT) | |
153 | #define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT | |
154 | #endif | |
155 | #if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) | |
156 | #define IPV6_RECVPKTINFO IPV6_PKTINFO | |
157 | #endif | |
158 | ||
aae24feb | 159 | static int |
4eb7b489 | 160 | ipv6nd_open(struct dhcpcd_ctx *dctx) |
91cd7324 | 161 | { |
4eb7b489 | 162 | struct ipv6_ctx *ctx; |
91cd7324 | 163 | int on; |
4eb7b489 | 164 | struct icmp6_filter filt; |
91cd7324 | 165 | |
4eb7b489 RM |
166 | ctx = dctx->ipv6; |
167 | if (ctx->nd_fd != -1) | |
608fd9f4 | 168 | return ctx->nd_fd; |
e8c8e9b9 | 169 | #ifdef SOCK_CLOEXEC |
d8cb8958 | 170 | ctx->nd_fd = socket(PF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, |
cc050202 | 171 | IPPROTO_ICMPV6); |
4eb7b489 | 172 | if (ctx->nd_fd == -1) |
fbbb0875 | 173 | return -1; |
e8c8e9b9 | 174 | #else |
d8cb8958 | 175 | if ((ctx->nd_fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1) |
e8c8e9b9 RM |
176 | return -1; |
177 | if ((on = fcntl(ctx->nd_fd, F_GETFD, 0)) == -1 || | |
178 | fcntl(ctx->nd_fd, F_SETFD, on | FD_CLOEXEC) == -1) | |
179 | { | |
180 | close(ctx->nd_fd); | |
181 | ctx->nd_fd = -1; | |
182 | return -1; | |
183 | } | |
184 | if ((on = fcntl(ctx->nd_fd, F_GETFL, 0)) == -1 || | |
185 | fcntl(ctx->nd_fd, F_SETFL, on | O_NONBLOCK) == -1) | |
186 | { | |
187 | close(ctx->nd_fd); | |
188 | ctx->nd_fd = -1; | |
189 | return -1; | |
190 | } | |
191 | #endif | |
fbbb0875 | 192 | |
cc431339 RM |
193 | /* RFC4861 4.1 */ |
194 | on = 255; | |
195 | if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, | |
196 | &on, sizeof(on)) == -1) | |
197 | goto eexit; | |
198 | ||
91cd7324 | 199 | on = 1; |
4eb7b489 RM |
200 | if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, |
201 | &on, sizeof(on)) == -1) | |
fbbb0875 | 202 | goto eexit; |
91cd7324 RM |
203 | |
204 | on = 1; | |
4eb7b489 RM |
205 | if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, |
206 | &on, sizeof(on)) == -1) | |
fbbb0875 | 207 | goto eexit; |
91cd7324 RM |
208 | |
209 | ICMP6_FILTER_SETBLOCKALL(&filt); | |
4eb7b489 | 210 | ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt); |
91cd7324 | 211 | ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); |
4eb7b489 RM |
212 | if (setsockopt(ctx->nd_fd, IPPROTO_ICMPV6, ICMP6_FILTER, |
213 | &filt, sizeof(filt)) == -1) | |
fbbb0875 | 214 | goto eexit; |
fbbb0875 | 215 | |
23f9d8b4 RM |
216 | eloop_event_add(dctx->eloop, ctx->nd_fd, |
217 | ipv6nd_handledata, dctx, NULL, NULL); | |
4eb7b489 | 218 | return ctx->nd_fd; |
e82129a4 RM |
219 | |
220 | eexit: | |
4eb7b489 | 221 | if (ctx->nd_fd != -1) { |
23f9d8b4 | 222 | eloop_event_delete(dctx->eloop, ctx->nd_fd, 0); |
e9882fb0 | 223 | close(ctx->nd_fd); |
4eb7b489 RM |
224 | ctx->nd_fd = -1; |
225 | } | |
e82129a4 RM |
226 | return -1; |
227 | } | |
228 | ||
229 | static int | |
230 | ipv6nd_makersprobe(struct interface *ifp) | |
91cd7324 | 231 | { |
ca15a0aa | 232 | struct rs_state *state; |
91cd7324 RM |
233 | struct nd_router_solicit *rs; |
234 | struct nd_opt_hdr *nd; | |
235 | ||
ca15a0aa RM |
236 | state = RS_STATE(ifp); |
237 | free(state->rs); | |
238 | state->rslen = sizeof(*rs) + ROUNDUP8(ifp->hwlen + 2); | |
10e17e3f | 239 | state->rs = calloc(1, state->rslen); |
ca15a0aa | 240 | if (state->rs == NULL) |
91cd7324 | 241 | return -1; |
ca15a0aa | 242 | rs = (struct nd_router_solicit *)(void *)state->rs; |
91cd7324 RM |
243 | rs->nd_rs_type = ND_ROUTER_SOLICIT; |
244 | rs->nd_rs_code = 0; | |
245 | rs->nd_rs_cksum = 0; | |
246 | rs->nd_rs_reserved = 0; | |
ca15a0aa | 247 | nd = (struct nd_opt_hdr *)(state->rs + sizeof(*rs)); |
91cd7324 RM |
248 | nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; |
249 | nd->nd_opt_len = (ROUNDUP8(ifp->hwlen + 2)) >> 3; | |
250 | memcpy(nd + 1, ifp->hwaddr, ifp->hwlen); | |
251 | return 0; | |
252 | } | |
673e81e5 | 253 | |
91cd7324 | 254 | static void |
e82129a4 | 255 | ipv6nd_sendrsprobe(void *arg) |
91cd7324 RM |
256 | { |
257 | struct interface *ifp = arg; | |
4eb7b489 | 258 | struct ipv6_ctx *ctx; |
ca15a0aa | 259 | struct rs_state *state; |
91cd7324 RM |
260 | struct sockaddr_in6 dst; |
261 | struct cmsghdr *cm; | |
262 | struct in6_pktinfo pi; | |
91cd7324 | 263 | |
0e906716 | 264 | if (ipv6_linklocal(ifp) == NULL) { |
5331b839 | 265 | syslog(LOG_DEBUG, |
5301406a | 266 | "%s: delaying Router Solicitation for LL address", |
5331b839 | 267 | ifp->name); |
e82129a4 | 268 | ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp); |
5331b839 RM |
269 | return; |
270 | } | |
271 | ||
4eb7b489 RM |
272 | memset(&dst, 0, sizeof(dst)); |
273 | dst.sin6_family = AF_INET6; | |
274 | #ifdef SIN6_LEN | |
275 | dst.sin6_len = sizeof(dst); | |
276 | #endif | |
22f64b55 | 277 | dst.sin6_scope_id = ifp->index; |
cc2b109a | 278 | if (inet_pton(AF_INET6, ALLROUTERS, &dst.sin6_addr) != 1) { |
4eb7b489 RM |
279 | syslog(LOG_ERR, "%s: %m", __func__); |
280 | return; | |
281 | } | |
91cd7324 | 282 | |
ca15a0aa | 283 | state = RS_STATE(ifp); |
4eb7b489 | 284 | ctx = ifp->ctx->ipv6; |
c1b035da | 285 | ctx->sndhdr.msg_name = (void *)&dst; |
4eb7b489 RM |
286 | ctx->sndhdr.msg_iov[0].iov_base = state->rs; |
287 | ctx->sndhdr.msg_iov[0].iov_len = state->rslen; | |
91cd7324 RM |
288 | |
289 | /* Set the outbound interface */ | |
4eb7b489 | 290 | cm = CMSG_FIRSTHDR(&ctx->sndhdr); |
8fc52ced RM |
291 | if (cm == NULL) /* unlikely */ |
292 | return; | |
91cd7324 RM |
293 | cm->cmsg_level = IPPROTO_IPV6; |
294 | cm->cmsg_type = IPV6_PKTINFO; | |
295 | cm->cmsg_len = CMSG_LEN(sizeof(pi)); | |
296 | memset(&pi, 0, sizeof(pi)); | |
2c77247b | 297 | pi.ipi6_ifindex = ifp->index; |
91cd7324 RM |
298 | memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); |
299 | ||
ad574a91 | 300 | syslog(LOG_DEBUG, "%s: sending Router Solicitation", ifp->name); |
4eb7b489 | 301 | if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1) { |
c5d2e393 | 302 | syslog(LOG_ERR, "%s: %s: sendmsg: %m", ifp->name, __func__); |
e82129a4 | 303 | ipv6nd_drop(ifp); |
7b9cd6f0 | 304 | ifp->options->options &= ~(DHCPCD_IPV6 | DHCPCD_IPV6RS); |
83e82504 RM |
305 | return; |
306 | } | |
91cd7324 | 307 | |
ca15a0aa | 308 | if (state->rsprobes++ < MAX_RTR_SOLICITATIONS) |
4eb7b489 RM |
309 | eloop_timeout_add_sec(ifp->ctx->eloop, |
310 | RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); | |
91cd7324 | 311 | else |
ad574a91 | 312 | syslog(LOG_WARNING, "%s: no IPv6 Routers available", ifp->name); |
91cd7324 RM |
313 | } |
314 | ||
72c37f5f RM |
315 | static void |
316 | ipv6nd_reachable(struct ra *rap, int flags) | |
317 | { | |
318 | ||
319 | if (flags & IPV6ND_REACHABLE) { | |
320 | if (rap->lifetime && rap->expired) { | |
321 | syslog(LOG_INFO, "%s: %s is reachable again", | |
322 | rap->iface->name, rap->sfrom); | |
323 | rap->expired = 0; | |
324 | ipv6_buildroutes(rap->iface->ctx); | |
325 | /* XXX Not really an RA */ | |
326 | script_runreason(rap->iface, "ROUTERADVERT"); | |
327 | } | |
328 | } else { | |
329 | /* Any error means it's really gone from the kernel | |
330 | * neighbour database */ | |
331 | if (rap->lifetime && !rap->expired) { | |
332 | syslog(LOG_WARNING, | |
333 | "%s: %s is unreachable, expiring it", | |
334 | rap->iface->name, rap->sfrom); | |
335 | rap->expired = 1; | |
336 | ipv6_buildroutes(rap->iface->ctx); | |
337 | /* XXX Not really an RA */ | |
338 | script_runreason(rap->iface, "ROUTERADVERT"); | |
339 | } | |
340 | } | |
341 | } | |
342 | ||
343 | #ifdef HAVE_RTM_GETNEIGH | |
344 | void | |
345 | ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, int flags) | |
346 | { | |
347 | struct ra *rap; | |
348 | ||
362153f5 RM |
349 | if (ctx->ipv6) { |
350 | TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { | |
351 | if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) { | |
352 | ipv6nd_reachable(rap, flags); | |
353 | break; | |
354 | } | |
72c37f5f RM |
355 | } |
356 | } | |
357 | } | |
358 | ||
359 | #else | |
360 | ||
a3ee6b23 RM |
361 | static void |
362 | ipv6nd_checkreachablerouters(void *arg) | |
363 | { | |
364 | struct dhcpcd_ctx *ctx = arg; | |
365 | struct ra *rap; | |
72c37f5f | 366 | int flags; |
a3ee6b23 RM |
367 | |
368 | TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { | |
72c37f5f RM |
369 | flags = if_nd6reachable(rap->iface->name, &rap->from); |
370 | if (flags == -1) { | |
371 | /* An error occured, so it's unreachable */ | |
372 | flags = 0; | |
a3ee6b23 | 373 | } |
72c37f5f | 374 | ipv6nd_reachable(rap, flags); |
a3ee6b23 RM |
375 | } |
376 | ||
377 | eloop_timeout_add_sec(ctx->eloop, ND6REACHABLE_TIMER, | |
378 | ipv6nd_checkreachablerouters, ctx); | |
379 | } | |
72c37f5f | 380 | #endif |
a3ee6b23 | 381 | |
91cd7324 | 382 | static void |
e82129a4 | 383 | ipv6nd_free_opts(struct ra *rap) |
91cd7324 | 384 | { |
eebe9a18 | 385 | struct ra_opt *rao; |
91cd7324 | 386 | |
eebe9a18 RM |
387 | while ((rao = TAILQ_FIRST(&rap->options))) { |
388 | TAILQ_REMOVE(&rap->options, rao, next); | |
389 | free(rao->option); | |
390 | free(rao); | |
391 | } | |
392 | } | |
91cd7324 | 393 | |
e54dee19 | 394 | int |
4eb7b489 | 395 | ipv6nd_addrexists(struct dhcpcd_ctx *ctx, const struct ipv6_addr *addr) |
376e8b80 RM |
396 | { |
397 | struct ra *rap; | |
398 | struct ipv6_addr *ap; | |
399 | ||
4eb7b489 | 400 | TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { |
376e8b80 | 401 | TAILQ_FOREACH(ap, &rap->addrs, next) { |
7013b073 RM |
402 | if (addr == NULL) { |
403 | if ((ap->flags & | |
404 | (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) == | |
405 | (IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) | |
406 | return 1; | |
73e77470 | 407 | } else if (IN6_ARE_ADDR_EQUAL(&ap->addr, &addr->addr)) |
376e8b80 RM |
408 | return 1; |
409 | } | |
410 | } | |
411 | return 0; | |
412 | } | |
413 | ||
e82129a4 | 414 | void ipv6nd_freedrop_ra(struct ra *rap, int drop) |
eebe9a18 RM |
415 | { |
416 | ||
4eb7b489 RM |
417 | eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); |
418 | eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); | |
5b428df2 | 419 | if (!drop) |
4eb7b489 | 420 | TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next); |
72c37f5f | 421 | #ifndef HAVE_RTM_GETNEIGH |
a3ee6b23 RM |
422 | if (TAILQ_FIRST(rap->iface->ctx->ipv6->ra_routers) == NULL) |
423 | eloop_timeout_delete(rap->iface->ctx->eloop, | |
424 | ipv6nd_checkreachablerouters, rap->iface->ctx); | |
72c37f5f | 425 | #endif |
8fdedf59 | 426 | ipv6_freedrop_addrs(&rap->addrs, drop, NULL); |
e82129a4 | 427 | ipv6nd_free_opts(rap); |
eebe9a18 | 428 | free(rap->data); |
eebe9a18 | 429 | free(rap); |
a3ee6b23 | 430 | |
eebe9a18 RM |
431 | } |
432 | ||
433 | ssize_t | |
e82129a4 | 434 | ipv6nd_free(struct interface *ifp) |
eebe9a18 | 435 | { |
ca15a0aa | 436 | struct rs_state *state; |
eebe9a18 | 437 | struct ra *rap, *ran; |
4eb7b489 | 438 | struct dhcpcd_ctx *ctx; |
eebe9a18 RM |
439 | ssize_t n; |
440 | ||
ca15a0aa | 441 | state = RS_STATE(ifp); |
a9d78def RM |
442 | if (state == NULL) |
443 | return 0; | |
444 | ||
445 | free(state->rs); | |
446 | free(state); | |
447 | ifp->if_data[IF_DATA_IPV6ND] = NULL; | |
eebe9a18 | 448 | n = 0; |
4eb7b489 | 449 | TAILQ_FOREACH_SAFE(rap, ifp->ctx->ipv6->ra_routers, next, ran) { |
eebe9a18 | 450 | if (rap->iface == ifp) { |
e82129a4 | 451 | ipv6nd_free_ra(rap); |
eebe9a18 | 452 | n++; |
91cd7324 | 453 | } |
eebe9a18 | 454 | } |
a9d78def RM |
455 | |
456 | /* If we don't have any more IPv6 enabled interfaces, | |
457 | * close the global socket and release resources */ | |
4eb7b489 RM |
458 | ctx = ifp->ctx; |
459 | TAILQ_FOREACH(ifp, ctx->ifaces, next) { | |
a9d78def RM |
460 | if (RS_STATE(ifp)) |
461 | break; | |
462 | } | |
463 | if (ifp == NULL) { | |
4eb7b489 | 464 | if (ctx->ipv6->nd_fd != -1) { |
23f9d8b4 | 465 | eloop_event_delete(ctx->eloop, ctx->ipv6->nd_fd, 0); |
e9882fb0 | 466 | close(ctx->ipv6->nd_fd); |
4eb7b489 | 467 | ctx->ipv6->nd_fd = -1; |
a9d78def | 468 | } |
a9d78def RM |
469 | } |
470 | ||
eebe9a18 RM |
471 | return n; |
472 | } | |
473 | ||
474 | static int | |
475 | rtpref(struct ra *rap) | |
476 | { | |
ca15a0aa | 477 | |
eebe9a18 RM |
478 | switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) { |
479 | case ND_RA_FLAG_RTPREF_HIGH: | |
480 | return (RTPREF_HIGH); | |
481 | case ND_RA_FLAG_RTPREF_MEDIUM: | |
482 | case ND_RA_FLAG_RTPREF_RSV: | |
483 | return (RTPREF_MEDIUM); | |
484 | case ND_RA_FLAG_RTPREF_LOW: | |
485 | return (RTPREF_LOW); | |
486 | default: | |
487 | syslog(LOG_ERR, "rtpref: impossible RA flag %x", rap->flags); | |
488 | return (RTPREF_INVALID); | |
489 | } | |
490 | /* NOTREACHED */ | |
491 | } | |
492 | ||
493 | static void | |
4eb7b489 | 494 | add_router(struct ipv6_ctx *ctx, struct ra *router) |
eebe9a18 RM |
495 | { |
496 | struct ra *rap; | |
497 | ||
4eb7b489 | 498 | TAILQ_FOREACH(rap, ctx->ra_routers, next) { |
eebe9a18 RM |
499 | if (router->iface->metric < rap->iface->metric || |
500 | (router->iface->metric == rap->iface->metric && | |
501 | rtpref(router) > rtpref(rap))) | |
502 | { | |
503 | TAILQ_INSERT_BEFORE(rap, router, next); | |
504 | return; | |
91cd7324 RM |
505 | } |
506 | } | |
4eb7b489 | 507 | TAILQ_INSERT_TAIL(ctx->ra_routers, router, next); |
91cd7324 RM |
508 | } |
509 | ||
b5b066a5 | 510 | static int |
e82129a4 | 511 | ipv6nd_scriptrun(struct ra *rap) |
a8df1b28 | 512 | { |
e2c4a256 | 513 | int hasdns, hasaddress, pid; |
d5690e93 | 514 | struct ipv6_addr *ap; |
a8df1b28 RM |
515 | const struct ra_opt *rao; |
516 | ||
e2c4a256 | 517 | hasaddress = 0; |
a8df1b28 | 518 | /* If all addresses have completed DAD run the script */ |
a8df1b28 | 519 | TAILQ_FOREACH(ap, &rap->addrs, next) { |
a824f281 RM |
520 | if ((ap->flags & (IPV6_AF_ONLINK | IPV6_AF_AUTOCONF)) == |
521 | (IPV6_AF_ONLINK | IPV6_AF_AUTOCONF)) | |
522 | { | |
e2c4a256 | 523 | hasaddress = 1; |
d5690e93 RM |
524 | if (!(ap->flags & IPV6_AF_DADCOMPLETED) && |
525 | ipv6_findaddr(ap->iface, &ap->addr)) | |
526 | ap->flags |= IPV6_AF_DADCOMPLETED; | |
527 | if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) { | |
528 | syslog(LOG_DEBUG, | |
529 | "%s: waiting for Router Advertisement" | |
530 | " DAD to complete", | |
531 | rap->iface->name); | |
b5b066a5 | 532 | return 0; |
d5690e93 | 533 | } |
d8194bcd | 534 | } |
a8df1b28 RM |
535 | } |
536 | ||
537 | /* If we don't require RDNSS then set hasdns = 1 so we fork */ | |
538 | if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS)) | |
539 | hasdns = 1; | |
540 | else { | |
541 | hasdns = 0; | |
542 | TAILQ_FOREACH(rao, &rap->options, next) { | |
543 | if (rao->type == ND_OPT_RDNSS && | |
544 | rao->option && | |
545 | timerisset(&rao->expire)) | |
546 | { | |
547 | hasdns = 1; | |
548 | break; | |
549 | } | |
550 | } | |
551 | } | |
552 | ||
553 | script_runreason(rap->iface, "ROUTERADVERT"); | |
e2c4a256 RM |
554 | pid = 0; |
555 | if (hasdns && (hasaddress || | |
556 | !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))) | |
94bec972 | 557 | pid = dhcpcd_daemonise(rap->iface->ctx); |
a8df1b28 RM |
558 | #if 0 |
559 | else if (options & DHCPCD_DAEMONISE && | |
560 | !(options & DHCPCD_DAEMONISED) && new_data) | |
561 | syslog(LOG_WARNING, | |
562 | "%s: did not fork due to an absent" | |
563 | " RDNSS option in the RA", | |
564 | ifp->name); | |
565 | } | |
566 | #endif | |
e2c4a256 | 567 | return pid; |
a8df1b28 RM |
568 | } |
569 | ||
3ed12ab8 RM |
570 | static void |
571 | ipv6nd_addaddr(void *arg) | |
572 | { | |
573 | struct ipv6_addr *ap = arg; | |
574 | ||
575 | ipv6_addaddr(ap); | |
576 | } | |
577 | ||
a0011b99 RM |
578 | int |
579 | ipv6nd_dadcompleted(const struct interface *ifp) | |
580 | { | |
581 | const struct ra *rap; | |
582 | const struct ipv6_addr *ap; | |
583 | ||
584 | TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { | |
585 | if (rap->iface != ifp) | |
586 | continue; | |
587 | TAILQ_FOREACH(ap, &rap->addrs, next) { | |
588 | if (ap->flags & IPV6_AF_AUTOCONF && | |
589 | !(ap->flags & IPV6_AF_DADCOMPLETED)) | |
590 | return 0; | |
591 | } | |
592 | } | |
593 | return 1; | |
594 | } | |
595 | ||
d8194bcd | 596 | static void |
e82129a4 | 597 | ipv6nd_dadcallback(void *arg) |
d8194bcd RM |
598 | { |
599 | struct ipv6_addr *ap = arg, *rapap; | |
600 | struct interface *ifp; | |
601 | struct ra *rap; | |
602 | int wascompleted, found; | |
3ed12ab8 RM |
603 | struct timeval tv; |
604 | char buf[INET6_ADDRSTRLEN]; | |
605 | const char *p; | |
4f5b9dd2 | 606 | int dadcounter; |
d8194bcd | 607 | |
3ed12ab8 | 608 | ifp = ap->iface; |
46b8a6b7 | 609 | wascompleted = (ap->flags & IPV6_AF_DADCOMPLETED); |
46b8a6b7 | 610 | ap->flags |= IPV6_AF_DADCOMPLETED; |
3ed12ab8 RM |
611 | if (ap->flags & IPV6_AF_DUPLICATED) { |
612 | ap->dadcounter++; | |
d8194bcd RM |
613 | syslog(LOG_WARNING, "%s: DAD detected %s", |
614 | ap->iface->name, ap->saddr); | |
d8194bcd | 615 | |
3ed12ab8 RM |
616 | /* Try and make another stable private address. |
617 | * Because ap->dadcounter is always increamented, | |
618 | * a different address is generated. */ | |
619 | /* XXX Cache DAD counter per prefix/id/ssid? */ | |
fd89860f RM |
620 | if (ifp->options->options & DHCPCD_SLAACPRIVATE) { |
621 | if (ap->dadcounter >= IDGEN_RETRIES) { | |
622 | syslog(LOG_ERR, | |
623 | "%s: unable to obtain a" | |
624 | " stable private address", | |
625 | ifp->name); | |
626 | goto try_script; | |
627 | } | |
3ed12ab8 RM |
628 | syslog(LOG_INFO, "%s: deleting address %s", |
629 | ifp->name, ap->saddr); | |
630 | if (if_deladdress6(ap) == -1 && | |
631 | errno != EADDRNOTAVAIL && errno != ENXIO) | |
632 | syslog(LOG_ERR, "if_deladdress6: %m"); | |
4f5b9dd2 | 633 | dadcounter = ap->dadcounter; |
3ed12ab8 RM |
634 | if (ipv6_makestableprivate(&ap->addr, |
635 | &ap->prefix, ap->prefix_len, | |
4f5b9dd2 | 636 | ifp, &dadcounter) == -1) |
3ed12ab8 RM |
637 | { |
638 | syslog(LOG_ERR, | |
639 | "%s: ipv6_makestableprivate: %m", | |
640 | ifp->name); | |
641 | return; | |
642 | } | |
4f5b9dd2 | 643 | ap->dadcounter = dadcounter; |
3ed12ab8 RM |
644 | ap->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); |
645 | ap->flags |= IPV6_AF_NEW; | |
cc2b109a | 646 | p = inet_ntop(AF_INET6, &ap->addr, buf, sizeof(buf)); |
3ed12ab8 RM |
647 | if (p) |
648 | snprintf(ap->saddr, | |
649 | sizeof(ap->saddr), | |
650 | "%s/%d", | |
651 | p, ap->prefix_len); | |
652 | else | |
653 | ap->saddr[0] = '\0'; | |
654 | tv.tv_sec = 0; | |
655 | tv.tv_usec = (suseconds_t)arc4random_uniform( | |
656 | IDGEN_DELAY * USECINSEC); | |
657 | timernorm(&tv); | |
658 | eloop_timeout_add_tv(ifp->ctx->eloop, &tv, | |
659 | ipv6nd_addaddr, ap); | |
660 | return; | |
661 | } | |
662 | } | |
d8194bcd | 663 | |
fd89860f | 664 | try_script: |
3ed12ab8 | 665 | if (!wascompleted) { |
4eb7b489 | 666 | TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { |
d8194bcd RM |
667 | if (rap->iface != ifp) |
668 | continue; | |
669 | wascompleted = 1; | |
e92ca600 | 670 | found = 0; |
d8194bcd | 671 | TAILQ_FOREACH(rapap, &rap->addrs, next) { |
a824f281 RM |
672 | if (rapap->flags & IPV6_AF_AUTOCONF && |
673 | (rapap->flags & IPV6_AF_DADCOMPLETED) == 0) | |
674 | { | |
d8194bcd RM |
675 | wascompleted = 0; |
676 | break; | |
677 | } | |
678 | if (rapap == ap) | |
679 | found = 1; | |
680 | } | |
681 | ||
4f422dd3 | 682 | if (wascompleted && found) { |
ad574a91 | 683 | syslog(LOG_DEBUG, |
d8194bcd RM |
684 | "%s: Router Advertisement DAD completed", |
685 | rap->iface->name); | |
b5b066a5 RM |
686 | if (ipv6nd_scriptrun(rap)) |
687 | return; | |
d8194bcd RM |
688 | } |
689 | } | |
690 | } | |
691 | } | |
692 | ||
aae24feb | 693 | static void |
4eb7b489 | 694 | ipv6nd_handlera(struct ipv6_ctx *ctx, struct interface *ifp, |
34457fe6 | 695 | struct icmp6_hdr *icp, size_t len) |
91cd7324 | 696 | { |
34457fe6 RM |
697 | size_t olen, l, m, n; |
698 | ssize_t r; | |
7be4b9b3 | 699 | struct nd_router_advert *nd_ra; |
91cd7324 RM |
700 | struct nd_opt_prefix_info *pi; |
701 | struct nd_opt_mtu *mtu; | |
702 | struct nd_opt_rdnss *rdnss; | |
703 | struct nd_opt_dnssl *dnssl; | |
eebe9a18 | 704 | uint32_t lifetime, mtuv; |
91cd7324 RM |
705 | uint8_t *p, *op; |
706 | struct in6_addr addr; | |
707 | char buf[INET6_ADDRSTRLEN]; | |
708 | const char *cbp; | |
709 | struct ra *rap; | |
710 | struct nd_opt_hdr *ndo; | |
eebe9a18 RM |
711 | struct ra_opt *rao; |
712 | struct ipv6_addr *ap; | |
d7555c12 | 713 | char *opt, *tmp; |
91cd7324 | 714 | struct timeval expire; |
a8df1b28 | 715 | uint8_t new_rap, new_data; |
91cd7324 | 716 | |
34457fe6 | 717 | if (len < sizeof(struct nd_router_advert)) { |
4eb7b489 | 718 | syslog(LOG_ERR, "IPv6 RA packet too short from %s", ctx->sfrom); |
91cd7324 RM |
719 | return; |
720 | } | |
721 | ||
4eb7b489 RM |
722 | if (!IN6_IS_ADDR_LINKLOCAL(&ctx->from.sin6_addr)) { |
723 | syslog(LOG_ERR, "RA from non local address %s", ctx->sfrom); | |
91cd7324 RM |
724 | return; |
725 | } | |
726 | ||
91cd7324 | 727 | if (ifp == NULL) { |
d7555c12 | 728 | #ifdef DEBUG_RS |
4eb7b489 RM |
729 | syslog(LOG_DEBUG, "RA for unexpected interface from %s", |
730 | ctx->sfrom); | |
4c6a8bec RM |
731 | #endif |
732 | return; | |
733 | } | |
734 | if (!(ifp->options->options & DHCPCD_IPV6RS)) { | |
735 | #ifdef DEBUG_RS | |
736 | syslog(LOG_DEBUG, "%s: unexpected RA from %s", | |
4eb7b489 | 737 | ifp->name, ctx->sfrom); |
d7555c12 | 738 | #endif |
91cd7324 RM |
739 | return; |
740 | } | |
0e906716 | 741 | |
e7a30a46 | 742 | /* We could receive a RA before we sent a RS*/ |
0e906716 RM |
743 | if (ipv6_linklocal(ifp) == NULL) { |
744 | #ifdef DEBUG_RS | |
745 | syslog(LOG_DEBUG, "%s: received RA from %s (no link-local)", | |
4eb7b489 | 746 | ifp->name, ctx->sfrom); |
0e906716 RM |
747 | #endif |
748 | return; | |
749 | } | |
750 | ||
4eb7b489 | 751 | TAILQ_FOREACH(rap, ctx->ra_routers, next) { |
fe292175 | 752 | if (ifp == rap->iface && |
cc2b109a | 753 | IN6_ARE_ADDR_EQUAL(&rap->from, &ctx->from.sin6_addr)) |
91cd7324 RM |
754 | break; |
755 | } | |
46caaa5e | 756 | |
e42bbc9b | 757 | nd_ra = (struct nd_router_advert *)icp; |
e42bbc9b | 758 | |
46caaa5e RM |
759 | /* We don't want to spam the log with the fact we got an RA every |
760 | * 30 seconds or so, so only spam the log if it's different. */ | |
ee70f4ab | 761 | if (rap == NULL || (rap->data_len != len || |
46caaa5e RM |
762 | memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0)) |
763 | { | |
764 | if (rap) { | |
765 | free(rap->data); | |
766 | rap->data_len = 0; | |
767 | } | |
d7555c12 | 768 | new_data = 1; |
d7555c12 RM |
769 | } else |
770 | new_data = 0; | |
ee70f4ab RM |
771 | if (new_data || ifp->options->options & DHCPCD_DEBUG) |
772 | syslog(LOG_INFO, "%s: Router Advertisement from %s", | |
4eb7b489 | 773 | ifp->name, ctx->sfrom); |
46caaa5e | 774 | |
91cd7324 | 775 | if (rap == NULL) { |
10e17e3f RM |
776 | rap = calloc(1, sizeof(*rap)); |
777 | if (rap == NULL) { | |
778 | syslog(LOG_ERR, "%s: %m", __func__); | |
779 | return; | |
780 | } | |
eebe9a18 | 781 | rap->iface = ifp; |
cc2b109a | 782 | rap->from = ctx->from.sin6_addr; |
4eb7b489 | 783 | strlcpy(rap->sfrom, ctx->sfrom, sizeof(rap->sfrom)); |
eebe9a18 RM |
784 | TAILQ_INIT(&rap->addrs); |
785 | TAILQ_INIT(&rap->options); | |
786 | new_rap = 1; | |
787 | } else | |
788 | new_rap = 0; | |
46caaa5e | 789 | if (rap->data_len == 0) { |
28382337 RM |
790 | rap->data = malloc(len); |
791 | if (rap->data == NULL) { | |
792 | syslog(LOG_ERR, "%s: %m", __func__); | |
793 | if (new_rap) | |
794 | free(rap); | |
795 | return; | |
796 | } | |
46caaa5e RM |
797 | memcpy(rap->data, icp, len); |
798 | rap->data_len = len; | |
91cd7324 RM |
799 | } |
800 | ||
801 | get_monotonic(&rap->received); | |
eebe9a18 | 802 | rap->flags = nd_ra->nd_ra_flags_reserved; |
e42bbc9b RM |
803 | if (new_rap == 0 && rap->lifetime == 0) |
804 | syslog(LOG_WARNING, "%s: %s router available", | |
805 | ifp->name, rap->sfrom); | |
7be4b9b3 | 806 | rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); |
ea112ab2 RM |
807 | if (nd_ra->nd_ra_reachable) { |
808 | rap->reachable = ntohl(nd_ra->nd_ra_reachable); | |
809 | if (rap->reachable > MAX_REACHABLE_TIME) | |
810 | rap->reachable = 0; | |
811 | } | |
812 | if (nd_ra->nd_ra_retransmit) | |
813 | rap->retrans = ntohl(nd_ra->nd_ra_retransmit); | |
b88df421 RM |
814 | if (rap->lifetime) |
815 | rap->expired = 0; | |
91cd7324 RM |
816 | |
817 | len -= sizeof(struct nd_router_advert); | |
818 | p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); | |
91cd7324 | 819 | lifetime = ~0U; |
8fc52ced | 820 | for (; len > 0; p += olen, len -= olen) { |
34457fe6 | 821 | if (len < sizeof(struct nd_opt_hdr)) { |
4eb7b489 | 822 | syslog(LOG_ERR, "%s: short option", ifp->name); |
91cd7324 RM |
823 | break; |
824 | } | |
825 | ndo = (struct nd_opt_hdr *)p; | |
826 | olen = ndo->nd_opt_len * 8 ; | |
827 | if (olen == 0) { | |
828 | syslog(LOG_ERR, "%s: zero length option", ifp->name); | |
829 | break; | |
830 | } | |
831 | if (olen > len) { | |
832 | syslog(LOG_ERR, | |
833 | "%s: Option length exceeds message", ifp->name); | |
834 | break; | |
835 | } | |
836 | ||
837 | opt = NULL; | |
838 | switch (ndo->nd_opt_type) { | |
839 | case ND_OPT_PREFIX_INFORMATION: | |
eebe9a18 | 840 | pi = (struct nd_opt_prefix_info *)(void *)ndo; |
91cd7324 | 841 | if (pi->nd_opt_pi_len != 4) { |
ee550e84 | 842 | syslog(new_data ? LOG_ERR : LOG_DEBUG, |
91cd7324 RM |
843 | "%s: invalid option len for prefix", |
844 | ifp->name); | |
c448a53a | 845 | continue; |
91cd7324 RM |
846 | } |
847 | if (pi->nd_opt_pi_prefix_len > 128) { | |
ee550e84 RM |
848 | syslog(new_data ? LOG_ERR : LOG_DEBUG, |
849 | "%s: invalid prefix len", | |
91cd7324 | 850 | ifp->name); |
c448a53a | 851 | continue; |
91cd7324 RM |
852 | } |
853 | if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) || | |
854 | IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) | |
855 | { | |
ee550e84 | 856 | syslog(new_data ? LOG_ERR : LOG_DEBUG, |
91cd7324 | 857 | "%s: invalid prefix in RA", ifp->name); |
c448a53a | 858 | continue; |
91cd7324 | 859 | } |
e54dee19 RM |
860 | if (ntohl(pi->nd_opt_pi_preferred_time) > |
861 | ntohl(pi->nd_opt_pi_valid_time)) | |
862 | { | |
ee550e84 | 863 | syslog(new_data ? LOG_ERR : LOG_DEBUG, |
e54dee19 | 864 | "%s: pltime > vltime", ifp->name); |
c448a53a | 865 | continue; |
e54dee19 | 866 | } |
eebe9a18 RM |
867 | TAILQ_FOREACH(ap, &rap->addrs, next) |
868 | if (ap->prefix_len ==pi->nd_opt_pi_prefix_len && | |
cc2b109a RM |
869 | IN6_ARE_ADDR_EQUAL(&ap->prefix, |
870 | &pi->nd_opt_pi_prefix)) | |
eebe9a18 RM |
871 | break; |
872 | if (ap == NULL) { | |
cd3612e5 RM |
873 | if (!(pi->nd_opt_pi_flags_reserved & |
874 | ND_OPT_PI_FLAG_AUTO) && | |
875 | !(pi->nd_opt_pi_flags_reserved & | |
876 | ND_OPT_PI_FLAG_ONLINK)) | |
4242c5f2 | 877 | continue; |
66fd5d67 | 878 | ap = calloc(1, sizeof(*ap)); |
c448a53a | 879 | if (ap == NULL) |
28382337 | 880 | break; |
66fd5d67 | 881 | ap->iface = rap->iface; |
a824f281 | 882 | ap->flags = IPV6_AF_NEW; |
eebe9a18 | 883 | ap->prefix_len = pi->nd_opt_pi_prefix_len; |
cc2b109a | 884 | ap->prefix = pi->nd_opt_pi_prefix; |
e54dee19 RM |
885 | if (pi->nd_opt_pi_flags_reserved & |
886 | ND_OPT_PI_FLAG_AUTO) | |
887 | { | |
a824f281 | 888 | ap->flags |= IPV6_AF_AUTOCONF; |
4f5b9dd2 RM |
889 | ap->dadcounter = |
890 | ipv6_makeaddr(&ap->addr, ifp, | |
e54dee19 RM |
891 | &ap->prefix, |
892 | pi->nd_opt_pi_prefix_len); | |
4f5b9dd2 RM |
893 | if (ap->dadcounter == -1) { |
894 | free(ap); | |
895 | break; | |
896 | } | |
e54dee19 | 897 | cbp = inet_ntop(AF_INET6, |
cc2b109a | 898 | &ap->addr, |
4eb7b489 | 899 | buf, sizeof(buf)); |
e54dee19 RM |
900 | if (cbp) |
901 | snprintf(ap->saddr, | |
902 | sizeof(ap->saddr), | |
903 | "%s/%d", | |
904 | cbp, ap->prefix_len); | |
905 | else | |
906 | ap->saddr[0] = '\0'; | |
907 | } else { | |
908 | memset(&ap->addr, 0, sizeof(ap->addr)); | |
eebe9a18 | 909 | ap->saddr[0] = '\0'; |
e54dee19 | 910 | } |
e82129a4 | 911 | ap->dadcallback = ipv6nd_dadcallback; |
eebe9a18 | 912 | TAILQ_INSERT_TAIL(&rap->addrs, ap, next); |
66fd5d67 | 913 | } |
cd3612e5 RM |
914 | if (pi->nd_opt_pi_flags_reserved & |
915 | ND_OPT_PI_FLAG_ONLINK) | |
46b8a6b7 | 916 | ap->flags |= IPV6_AF_ONLINK; |
eebe9a18 RM |
917 | ap->prefix_vltime = |
918 | ntohl(pi->nd_opt_pi_valid_time); | |
919 | ap->prefix_pltime = | |
920 | ntohl(pi->nd_opt_pi_preferred_time); | |
66fd5d67 | 921 | ap->nsprobes = 0; |
50083515 | 922 | if (opt) { |
3af8f2b2 RM |
923 | l = strlen(opt) + 1; |
924 | m = strlen(ap->saddr) + 1; | |
925 | tmp = realloc(opt, l + m); | |
fa245a4d RM |
926 | if (tmp) { |
927 | opt = tmp; | |
3af8f2b2 RM |
928 | opt[l - 1] = ' '; |
929 | strlcpy(opt + l, ap->saddr, m); | |
c448a53a | 930 | } else { |
cbfc9c3a | 931 | syslog(LOG_ERR, "%s: %m", __func__); |
c448a53a RM |
932 | continue; |
933 | } | |
934 | } else | |
78369646 | 935 | opt = strdup(ap->saddr); |
50083515 | 936 | lifetime = ap->prefix_vltime; |
91cd7324 RM |
937 | break; |
938 | ||
939 | case ND_OPT_MTU: | |
eebe9a18 RM |
940 | mtu = (struct nd_opt_mtu *)(void *)p; |
941 | mtuv = ntohl(mtu->nd_opt_mtu_mtu); | |
942 | if (mtuv < IPV6_MMTU) { | |
943 | syslog(LOG_ERR, "%s: invalid MTU %d", | |
944 | ifp->name, mtuv); | |
945 | break; | |
946 | } | |
f98846d4 | 947 | rap->mtu = mtuv; |
eebe9a18 | 948 | snprintf(buf, sizeof(buf), "%d", mtuv); |
78369646 | 949 | opt = strdup(buf); |
91cd7324 RM |
950 | break; |
951 | ||
952 | case ND_OPT_RDNSS: | |
953 | rdnss = (struct nd_opt_rdnss *)p; | |
954 | lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime); | |
955 | op = (uint8_t *)ndo; | |
956 | op += offsetof(struct nd_opt_rdnss, | |
957 | nd_opt_rdnss_lifetime); | |
958 | op += sizeof(rdnss->nd_opt_rdnss_lifetime); | |
959 | l = 0; | |
c2e168a8 | 960 | for (n = ndo->nd_opt_len - 1; n > 1; n -= 2, |
cc2b109a | 961 | op += sizeof(addr)) |
c2e168a8 | 962 | { |
34457fe6 RM |
963 | r = ipv6_printaddr(NULL, 0, op, ifp->name); |
964 | if (r != -1) | |
965 | l += (size_t)r + 1; | |
c2e168a8 RM |
966 | } |
967 | op = (uint8_t *)ndo; | |
968 | op += offsetof(struct nd_opt_rdnss, | |
969 | nd_opt_rdnss_lifetime); | |
970 | op += sizeof(rdnss->nd_opt_rdnss_lifetime); | |
971 | tmp = opt = malloc(l); | |
c448a53a RM |
972 | if (opt == NULL) |
973 | continue; | |
974 | for (n = ndo->nd_opt_len - 1; n > 1; n -= 2, | |
cc2b109a | 975 | op += sizeof(addr)) |
c448a53a RM |
976 | { |
977 | r = ipv6_printaddr(tmp, l, op, | |
978 | ifp->name); | |
979 | if (r != -1) { | |
980 | l -= ((size_t)r + 1); | |
981 | tmp += (size_t)r; | |
982 | *tmp++ = ' '; | |
b2e8d8da | 983 | } |
c448a53a RM |
984 | } |
985 | if (tmp != opt) | |
986 | (*--tmp) = '\0'; | |
987 | else | |
988 | *opt = '\0'; | |
91cd7324 | 989 | break; |
673e81e5 | 990 | |
91cd7324 RM |
991 | case ND_OPT_DNSSL: |
992 | dnssl = (struct nd_opt_dnssl *)p; | |
993 | lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime); | |
994 | op = p + offsetof(struct nd_opt_dnssl, | |
995 | nd_opt_dnssl_lifetime); | |
996 | op += sizeof(dnssl->nd_opt_dnssl_lifetime); | |
997 | n = (dnssl->nd_opt_dnssl_len - 1) * 8; | |
34457fe6 RM |
998 | r = decode_rfc3397(NULL, 0, op, n); |
999 | if (r < 1) { | |
ee550e84 RM |
1000 | syslog(new_data ? LOG_ERR : LOG_DEBUG, |
1001 | "%s: invalid DNSSL option", | |
91cd7324 | 1002 | ifp->name); |
c448a53a | 1003 | continue; |
91cd7324 | 1004 | } else { |
34457fe6 | 1005 | l = (size_t)r; |
78369646 RM |
1006 | tmp = malloc(l); |
1007 | if (tmp) { | |
34457fe6 RM |
1008 | decode_rfc3397(tmp, l, op, n); |
1009 | l -= 1; | |
1010 | n = (size_t)print_string(NULL, 0, | |
1011 | (const uint8_t *)tmp, l); | |
78369646 RM |
1012 | opt = malloc(n); |
1013 | if (opt) | |
1014 | print_string(opt, n, | |
34457fe6 | 1015 | (const uint8_t *)tmp, l); |
cbfc9c3a RM |
1016 | else |
1017 | syslog(LOG_ERR, "%s: %m", | |
1018 | __func__); | |
78369646 | 1019 | free(tmp); |
c448a53a | 1020 | } |
91cd7324 RM |
1021 | } |
1022 | break; | |
17b0dbad RM |
1023 | |
1024 | default: | |
1025 | continue; | |
91cd7324 RM |
1026 | } |
1027 | ||
c448a53a RM |
1028 | if (opt == NULL) { |
1029 | syslog(LOG_ERR, "%s: %m", __func__); | |
91cd7324 | 1030 | continue; |
c448a53a | 1031 | } |
cbfc9c3a | 1032 | |
eebe9a18 | 1033 | TAILQ_FOREACH(rao, &rap->options, next) { |
91cd7324 RM |
1034 | if (rao->type == ndo->nd_opt_type && |
1035 | strcmp(rao->option, opt) == 0) | |
1036 | break; | |
1037 | } | |
6f522199 | 1038 | if (lifetime == 0 || *opt == '\0') { |
91cd7324 | 1039 | if (rao) { |
eebe9a18 | 1040 | TAILQ_REMOVE(&rap->options, rao, next); |
91cd7324 RM |
1041 | free(rao->option); |
1042 | free(rao); | |
1043 | } | |
f08afbd8 | 1044 | free(opt); |
91cd7324 RM |
1045 | continue; |
1046 | } | |
1047 | ||
1048 | if (rao == NULL) { | |
28382337 RM |
1049 | rao = malloc(sizeof(*rao)); |
1050 | if (rao == NULL) { | |
1051 | syslog(LOG_ERR, "%s: %m", __func__); | |
1052 | continue; | |
1053 | } | |
91cd7324 RM |
1054 | rao->type = ndo->nd_opt_type; |
1055 | rao->option = opt; | |
eebe9a18 | 1056 | TAILQ_INSERT_TAIL(&rap->options, rao, next); |
bb02dff1 RM |
1057 | } else |
1058 | free(opt); | |
449df9c8 RM |
1059 | if (lifetime == ~0U) |
1060 | timerclear(&rao->expire); | |
1061 | else { | |
717bc86c | 1062 | expire.tv_sec = (time_t)lifetime; |
91cd7324 RM |
1063 | expire.tv_usec = 0; |
1064 | timeradd(&rap->received, &expire, &rao->expire); | |
1065 | } | |
1066 | } | |
1067 | ||
eebe9a18 | 1068 | if (new_rap) |
4eb7b489 RM |
1069 | add_router(ifp->ctx->ipv6, rap); |
1070 | if (ifp->ctx->options & DHCPCD_TEST) { | |
294eff4d | 1071 | script_runreason(ifp, "TEST"); |
d7555c12 | 1072 | goto handle_flag; |
b88df421 | 1073 | } |
7529fdf1 | 1074 | ipv6_addaddrs(&rap->addrs); |
4eb7b489 | 1075 | ipv6_buildroutes(ifp->ctx); |
4f422dd3 RM |
1076 | if (ipv6nd_scriptrun(rap)) |
1077 | return; | |
61dd6cf9 | 1078 | |
4eb7b489 RM |
1079 | eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); |
1080 | eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */ | |
eebe9a18 | 1081 | |
d7555c12 RM |
1082 | handle_flag: |
1083 | if (rap->flags & ND_RA_FLAG_MANAGED) { | |
4f422dd3 | 1084 | if (new_data && dhcp6_start(ifp, DH6S_INIT) == -1) |
e54dee19 | 1085 | syslog(LOG_ERR, "dhcp6_start: %s: %m", ifp->name); |
d7555c12 | 1086 | } else if (rap->flags & ND_RA_FLAG_OTHER) { |
4f422dd3 | 1087 | if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1) |
d7555c12 RM |
1088 | syslog(LOG_ERR, "dhcp6_start: %s: %m", ifp->name); |
1089 | } else { | |
4f422dd3 | 1090 | if (new_data) |
d7555c12 RM |
1091 | syslog(LOG_DEBUG, "%s: No DHCPv6 instruction in RA", |
1092 | ifp->name); | |
4eb7b489 RM |
1093 | if (ifp->ctx->options & DHCPCD_TEST) { |
1094 | eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); | |
a9d78def RM |
1095 | return; |
1096 | } | |
d7555c12 | 1097 | } |
35308011 RM |
1098 | |
1099 | /* Expire should be called last as the rap object could be destroyed */ | |
e82129a4 | 1100 | ipv6nd_expirera(ifp); |
a3ee6b23 | 1101 | |
72c37f5f | 1102 | #ifndef HAVE_RTM_GETNEIGH |
a3ee6b23 RM |
1103 | /* Start our reachability tests now */ |
1104 | ipv6nd_checkreachablerouters(ifp->ctx); | |
72c37f5f | 1105 | #endif |
eebe9a18 RM |
1106 | } |
1107 | ||
1108 | int | |
047235d7 | 1109 | ipv6nd_hasra(const struct interface *ifp) |
eebe9a18 RM |
1110 | { |
1111 | const struct ra *rap; | |
1112 | ||
2433e54d RM |
1113 | if (ifp->ctx->ipv6) { |
1114 | TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) | |
047235d7 | 1115 | if (rap->iface == ifp && !rap->expired) |
2433e54d RM |
1116 | return 1; |
1117 | } | |
eebe9a18 | 1118 | return 0; |
91cd7324 RM |
1119 | } |
1120 | ||
047235d7 RM |
1121 | int |
1122 | ipv6nd_hasradhcp(const struct interface *ifp) | |
1123 | { | |
1124 | const struct ra *rap; | |
1125 | ||
1126 | if (ifp->ctx->ipv6) { | |
1127 | TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { | |
1128 | if (rap->iface == ifp && | |
1129 | !rap->expired && | |
1130 | (rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) | |
1131 | return 1; | |
1132 | } | |
1133 | } | |
1134 | return 0; | |
1135 | } | |
1136 | ||
91cd7324 | 1137 | ssize_t |
e82129a4 | 1138 | ipv6nd_env(char **env, const char *prefix, const struct interface *ifp) |
91cd7324 | 1139 | { |
6f522199 | 1140 | size_t i, l, len; |
91cd7324 RM |
1141 | const struct ra *rap; |
1142 | const struct ra_opt *rao; | |
eebe9a18 | 1143 | char buffer[32]; |
91cd7324 | 1144 | const char *optn; |
d4e41f4b | 1145 | char **pref, **mtu, **rdnss, **dnssl, ***var, *new; |
eebe9a18 | 1146 | |
34457fe6 | 1147 | i = l = 0; |
4eb7b489 | 1148 | TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { |
eebe9a18 RM |
1149 | i++; |
1150 | if (rap->iface != ifp) | |
1151 | continue; | |
91cd7324 RM |
1152 | if (env) { |
1153 | snprintf(buffer, sizeof(buffer), | |
76bb4d03 | 1154 | "ra%zu_from", i); |
34457fe6 | 1155 | setvar(&env, prefix, buffer, rap->sfrom); |
91cd7324 RM |
1156 | } |
1157 | l++; | |
449df9c8 | 1158 | |
f98846d4 | 1159 | pref = mtu = rdnss = dnssl = NULL; |
eebe9a18 | 1160 | TAILQ_FOREACH(rao, &rap->options, next) { |
91cd7324 RM |
1161 | if (rao->option == NULL) |
1162 | continue; | |
f98846d4 RM |
1163 | var = NULL; |
1164 | switch(rao->type) { | |
91cd7324 | 1165 | case ND_OPT_PREFIX_INFORMATION: |
6f522199 | 1166 | optn = "prefix"; /* really address */ |
d4e41f4b | 1167 | var = &pref; |
91cd7324 RM |
1168 | break; |
1169 | case ND_OPT_MTU: | |
1170 | optn = "mtu"; | |
d4e41f4b | 1171 | var = &mtu; |
91cd7324 RM |
1172 | break; |
1173 | case ND_OPT_RDNSS: | |
1174 | optn = "rdnss"; | |
673e81e5 | 1175 | var = &rdnss; |
91cd7324 RM |
1176 | break; |
1177 | case ND_OPT_DNSSL: | |
1178 | optn = "dnssl"; | |
d4e41f4b | 1179 | var = &dnssl; |
91cd7324 RM |
1180 | break; |
1181 | default: | |
1182 | continue; | |
1183 | } | |
d4e41f4b RM |
1184 | if (*var == NULL) { |
1185 | *var = env ? env : &new; | |
1186 | l++; | |
1187 | } else if (env) { | |
f98846d4 RM |
1188 | /* With single only options, last one takes |
1189 | * precedence */ | |
1190 | if (rao->type == ND_OPT_MTU) { | |
1191 | new = strchr(**var, '='); | |
1192 | if (new == NULL) { | |
1193 | syslog(LOG_ERR, "new is null"); | |
1194 | continue; | |
1195 | } else | |
1196 | new++; | |
34457fe6 | 1197 | len = (size_t)(new - **var) + |
e54dee19 | 1198 | strlen(rao->option) + 1; |
f98846d4 RM |
1199 | if (len > strlen(**var)) |
1200 | new = realloc(**var, len); | |
1201 | else | |
1202 | new = **var; | |
1203 | if (new) { | |
1204 | **var = new; | |
1205 | new = strchr(**var, '='); | |
3af8f2b2 | 1206 | if (new) { |
34457fe6 RM |
1207 | len -= |
1208 | (size_t) | |
1209 | (new - **var); | |
3af8f2b2 RM |
1210 | strlcpy(new + 1, |
1211 | rao->option, | |
1212 | len - 1); | |
1213 | } else | |
e54dee19 RM |
1214 | syslog(LOG_ERR, |
1215 | "new is null"); | |
f98846d4 RM |
1216 | } |
1217 | continue; | |
1218 | } | |
3af8f2b2 RM |
1219 | len = strlen(rao->option) + 1; |
1220 | new = realloc(**var, strlen(**var) + 1 + len); | |
34457fe6 RM |
1221 | if (new) { |
1222 | **var = new; | |
1223 | new += strlen(new); | |
1224 | *new++ = ' '; | |
1225 | strlcpy(new, rao->option, len); | |
1226 | } else | |
1227 | syslog(LOG_ERR, "%s: %m", __func__); | |
28382337 | 1228 | continue; |
d4e41f4b RM |
1229 | } |
1230 | if (env) { | |
1231 | snprintf(buffer, sizeof(buffer), | |
76bb4d03 | 1232 | "ra%zu_%s", i, optn); |
34457fe6 | 1233 | setvar(&env, prefix, buffer, rao->option); |
d4e41f4b | 1234 | } |
91cd7324 RM |
1235 | } |
1236 | } | |
1237 | ||
34457fe6 RM |
1238 | if (env) |
1239 | setvard(&env, prefix, "ra_count", i); | |
91cd7324 | 1240 | l++; |
34457fe6 | 1241 | return (ssize_t)l; |
91cd7324 RM |
1242 | } |
1243 | ||
a8df1b28 | 1244 | void |
4eb7b489 | 1245 | ipv6nd_handleifa(struct dhcpcd_ctx *ctx, int cmd, const char *ifname, |
d8194bcd | 1246 | const struct in6_addr *addr, int flags) |
a8df1b28 RM |
1247 | { |
1248 | struct ra *rap; | |
a8df1b28 | 1249 | |
4eb7b489 RM |
1250 | if (ctx->ipv6 == NULL) |
1251 | return; | |
1252 | TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { | |
a8df1b28 RM |
1253 | if (strcmp(rap->iface->name, ifname)) |
1254 | continue; | |
d8194bcd | 1255 | ipv6_handleifa_addrs(cmd, &rap->addrs, addr, flags); |
a8df1b28 RM |
1256 | } |
1257 | } | |
1258 | ||
91cd7324 | 1259 | void |
e82129a4 | 1260 | ipv6nd_expirera(void *arg) |
91cd7324 RM |
1261 | { |
1262 | struct interface *ifp; | |
eebe9a18 RM |
1263 | struct ra *rap, *ran; |
1264 | struct ra_opt *rao, *raon; | |
91cd7324 | 1265 | struct timeval now, lt, expire, next; |
d4e41f4b | 1266 | int expired, valid; |
91cd7324 RM |
1267 | |
1268 | ifp = arg; | |
1269 | get_monotonic(&now); | |
1270 | expired = 0; | |
91cd7324 RM |
1271 | timerclear(&next); |
1272 | ||
4eb7b489 | 1273 | TAILQ_FOREACH_SAFE(rap, ifp->ctx->ipv6->ra_routers, next, ran) { |
eebe9a18 RM |
1274 | if (rap->iface != ifp) |
1275 | continue; | |
4f422dd3 RM |
1276 | valid = 0; |
1277 | if (rap->lifetime) { | |
717bc86c | 1278 | lt.tv_sec = (time_t)rap->lifetime; |
4f422dd3 RM |
1279 | lt.tv_usec = 0; |
1280 | timeradd(&rap->received, <, &expire); | |
1281 | if (rap->lifetime == 0 || timercmp(&now, &expire, >)) { | |
1282 | if (!rap->expired) { | |
1283 | syslog(LOG_WARNING, | |
1284 | "%s: %s: router expired", | |
1285 | ifp->name, rap->sfrom); | |
1286 | rap->expired = expired = 1; | |
4f422dd3 RM |
1287 | } |
1288 | } else { | |
1289 | valid = 1; | |
1290 | timersub(&expire, &now, <); | |
1291 | if (!timerisset(&next) || | |
1292 | timercmp(&next, <, >)) | |
1293 | next = lt; | |
35308011 | 1294 | } |
35308011 RM |
1295 | } |
1296 | ||
7529fdf1 | 1297 | /* Addresses are expired in ipv6_addaddrs |
78ae7296 | 1298 | * so that DHCPv6 addresses can be removed also. */ |
eebe9a18 | 1299 | TAILQ_FOREACH_SAFE(rao, &rap->options, next, raon) { |
47102c83 RM |
1300 | if (rap->expired) { |
1301 | switch(rao->type) { | |
1302 | case ND_OPT_RDNSS: /* FALLTHROUGH */ | |
1303 | case ND_OPT_DNSSL: | |
1304 | /* RFC6018 end of section 5.2 states | |
1305 | * that if tha RA has a lifetime of 0 | |
1306 | * then we should expire these | |
1307 | * options */ | |
1308 | TAILQ_REMOVE(&rap->options, rao, next); | |
1309 | expired = 1; | |
1310 | free(rao->option); | |
1311 | free(rao); | |
1312 | continue; | |
1313 | } | |
1314 | } | |
449df9c8 RM |
1315 | if (!timerisset(&rao->expire)) |
1316 | continue; | |
1317 | if (timercmp(&now, &rao->expire, >)) { | |
35308011 RM |
1318 | /* Expired prefixes are logged above */ |
1319 | if (rao->type != ND_OPT_PREFIX_INFORMATION) | |
ad574a91 | 1320 | syslog(LOG_WARNING, |
35308011 RM |
1321 | "%s: %s: expired option %d", |
1322 | ifp->name, rap->sfrom, rao->type); | |
eebe9a18 RM |
1323 | TAILQ_REMOVE(&rap->options, rao, next); |
1324 | expired = 1; | |
1325 | free(rao->option); | |
1326 | free(rao); | |
449df9c8 RM |
1327 | continue; |
1328 | } | |
d4e41f4b | 1329 | valid = 1; |
449df9c8 | 1330 | timersub(&rao->expire, &now, <); |
91cd7324 RM |
1331 | if (!timerisset(&next) || timercmp(&next, <, >)) |
1332 | next = lt; | |
1333 | } | |
d4e41f4b RM |
1334 | |
1335 | /* No valid lifetimes are left on the RA, so we might | |
1336 | * as well punt it. */ | |
e42bbc9b | 1337 | if (!valid && TAILQ_FIRST(&rap->addrs) == NULL) |
e82129a4 | 1338 | ipv6nd_free_ra(rap); |
91cd7324 RM |
1339 | } |
1340 | ||
1341 | if (timerisset(&next)) | |
4eb7b489 RM |
1342 | eloop_timeout_add_tv(ifp->ctx->eloop, |
1343 | &next, ipv6nd_expirera, ifp); | |
e82129a4 | 1344 | if (expired) { |
4eb7b489 | 1345 | ipv6_buildroutes(ifp->ctx); |
e82129a4 RM |
1346 | script_runreason(ifp, "ROUTERADVERT"); |
1347 | } | |
1348 | } | |
1349 | ||
1350 | void | |
1351 | ipv6nd_drop(struct interface *ifp) | |
1352 | { | |
1353 | struct ra *rap; | |
1354 | int expired = 0; | |
1355 | TAILQ_HEAD(rahead, ra) rtrs; | |
1356 | ||
2433e54d RM |
1357 | if (ifp->ctx->ipv6 == NULL) |
1358 | return; | |
1359 | ||
4eb7b489 | 1360 | eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); |
e82129a4 | 1361 | TAILQ_INIT(&rtrs); |
4eb7b489 | 1362 | TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { |
e82129a4 RM |
1363 | if (rap->iface == ifp) { |
1364 | rap->expired = expired = 1; | |
4eb7b489 | 1365 | TAILQ_REMOVE(ifp->ctx->ipv6->ra_routers, rap, next); |
e82129a4 RM |
1366 | TAILQ_INSERT_TAIL(&rtrs, rap, next); |
1367 | } | |
1368 | } | |
eebe9a18 | 1369 | if (expired) { |
e82129a4 RM |
1370 | while ((rap = TAILQ_FIRST(&rtrs))) { |
1371 | TAILQ_REMOVE(&rtrs, rap, next); | |
1372 | ipv6nd_drop_ra(rap); | |
1373 | } | |
4eb7b489 | 1374 | ipv6_buildroutes(ifp->ctx); |
15fc1181 RM |
1375 | if ((ifp->options->options & |
1376 | (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != | |
1377 | (DHCPCD_EXITING | DHCPCD_PERSISTENT)) | |
1378 | script_runreason(ifp, "ROUTERADVERT"); | |
e82129a4 RM |
1379 | } |
1380 | } | |
a9d78def | 1381 | |
e82129a4 | 1382 | static void |
4eb7b489 | 1383 | ipv6nd_handlena(struct ipv6_ctx *ctx, struct interface *ifp, |
34457fe6 | 1384 | struct icmp6_hdr *icp, size_t len) |
e82129a4 RM |
1385 | { |
1386 | struct nd_neighbor_advert *nd_na; | |
1387 | struct ra *rap; | |
1388 | int is_router, is_solicited; | |
e82129a4 | 1389 | |
e82129a4 | 1390 | if ((size_t)len < sizeof(struct nd_neighbor_advert)) { |
4eb7b489 | 1391 | syslog(LOG_ERR, "IPv6 NA packet too short from %s", ctx->sfrom); |
e82129a4 RM |
1392 | return; |
1393 | } | |
1394 | ||
1395 | if (ifp == NULL) { | |
1396 | #ifdef DEBUG_NS | |
4eb7b489 RM |
1397 | syslog(LOG_DEBUG, "NA for unexpected interface from %s", |
1398 | ctx->sfrom); | |
e82129a4 RM |
1399 | #endif |
1400 | return; | |
1401 | } | |
1402 | ||
1403 | nd_na = (struct nd_neighbor_advert *)icp; | |
1404 | is_router = nd_na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER; | |
1405 | is_solicited = nd_na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED; | |
1406 | ||
1407 | if (IN6_IS_ADDR_MULTICAST(&nd_na->nd_na_target)) { | |
1408 | syslog(LOG_ERR, "%s: NA for multicast address from %s", | |
4eb7b489 | 1409 | ifp->name, ctx->sfrom); |
e82129a4 RM |
1410 | return; |
1411 | } | |
1412 | ||
4eb7b489 | 1413 | TAILQ_FOREACH(rap, ctx->ra_routers, next) { |
7529fdf1 | 1414 | if (rap->iface == ifp && |
cc2b109a | 1415 | IN6_ARE_ADDR_EQUAL(&rap->from, &nd_na->nd_na_target)) |
e82129a4 | 1416 | break; |
e82129a4 RM |
1417 | } |
1418 | if (rap == NULL) { | |
e82129a4 | 1419 | #ifdef DEBUG_NS |
7529fdf1 RM |
1420 | syslog(LOG_DEBUG, "%s: unexpected NA from s", |
1421 | ifp->name, ctx->sfrom); | |
e82129a4 RM |
1422 | #endif |
1423 | return; | |
1424 | } | |
1425 | ||
1426 | #ifdef DEBUG_NS | |
1427 | syslog(LOG_DEBUG, "%s: %sNA from %s", | |
4eb7b489 | 1428 | ifp->name, is_solicited ? "solicited " : "", ctx->sfrom); |
e82129a4 RM |
1429 | #endif |
1430 | ||
1431 | /* Node is no longer a router, so remove it from consideration */ | |
1432 | if (!is_router && !rap->expired) { | |
1433 | syslog(LOG_INFO, "%s: %s is no longer a router", | |
4eb7b489 | 1434 | ifp->name, ctx->sfrom); |
e82129a4 | 1435 | rap->expired = 1; |
4eb7b489 | 1436 | ipv6_buildroutes(ifp->ctx); |
294eff4d | 1437 | script_runreason(ifp, "ROUTERADVERT"); |
e82129a4 RM |
1438 | return; |
1439 | } | |
1440 | ||
1441 | if (is_solicited && is_router && rap->lifetime) { | |
1442 | if (rap->expired) { | |
1443 | rap->expired = 0; | |
1444 | syslog(LOG_INFO, "%s: %s is reachable again", | |
4eb7b489 RM |
1445 | ifp->name, ctx->sfrom); |
1446 | ipv6_buildroutes(ifp->ctx); | |
e82129a4 RM |
1447 | script_runreason(rap->iface, "ROUTERADVERT"); /* XXX */ |
1448 | } | |
eebe9a18 | 1449 | } |
91cd7324 RM |
1450 | } |
1451 | ||
e82129a4 | 1452 | static void |
4eb7b489 | 1453 | ipv6nd_handledata(void *arg) |
e82129a4 | 1454 | { |
4eb7b489 RM |
1455 | struct dhcpcd_ctx *dhcpcd_ctx; |
1456 | struct ipv6_ctx *ctx; | |
e82129a4 RM |
1457 | ssize_t len; |
1458 | struct cmsghdr *cm; | |
1459 | int hoplimit; | |
1460 | struct in6_pktinfo pkt; | |
1461 | struct icmp6_hdr *icp; | |
1462 | struct interface *ifp; | |
1463 | ||
4eb7b489 RM |
1464 | dhcpcd_ctx = arg; |
1465 | ctx = dhcpcd_ctx->ipv6; | |
b826c088 RM |
1466 | ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + |
1467 | CMSG_SPACE(sizeof(int)); | |
4eb7b489 | 1468 | len = recvmsg(ctx->nd_fd, &ctx->rcvhdr, 0); |
68e67270 | 1469 | if (len == -1 || len == 0) { |
e82129a4 | 1470 | syslog(LOG_ERR, "recvmsg: %m"); |
23f9d8b4 | 1471 | eloop_event_delete(dhcpcd_ctx->eloop, ctx->nd_fd, 0); |
68e67270 RM |
1472 | close(ctx->nd_fd); |
1473 | ctx->nd_fd = -1; | |
e82129a4 RM |
1474 | return; |
1475 | } | |
4eb7b489 RM |
1476 | ctx->sfrom = inet_ntop(AF_INET6, &ctx->from.sin6_addr, |
1477 | ctx->ntopbuf, INET6_ADDRSTRLEN); | |
e82129a4 | 1478 | if ((size_t)len < sizeof(struct icmp6_hdr)) { |
4eb7b489 RM |
1479 | syslog(LOG_ERR, "IPv6 ICMP packet too short from %s", |
1480 | ctx->sfrom); | |
e82129a4 RM |
1481 | return; |
1482 | } | |
1483 | ||
1484 | pkt.ipi6_ifindex = hoplimit = 0; | |
4eb7b489 | 1485 | for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&ctx->rcvhdr); |
e82129a4 | 1486 | cm; |
4eb7b489 | 1487 | cm = (struct cmsghdr *)CMSG_NXTHDR(&ctx->rcvhdr, cm)) |
e82129a4 RM |
1488 | { |
1489 | if (cm->cmsg_level != IPPROTO_IPV6) | |
1490 | continue; | |
1491 | switch(cm->cmsg_type) { | |
1492 | case IPV6_PKTINFO: | |
1493 | if (cm->cmsg_len == CMSG_LEN(sizeof(pkt))) | |
1494 | memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt)); | |
1495 | break; | |
1496 | case IPV6_HOPLIMIT: | |
1497 | if (cm->cmsg_len == CMSG_LEN(sizeof(int))) | |
1498 | memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int)); | |
1499 | break; | |
1500 | } | |
1501 | } | |
1502 | ||
1503 | if (pkt.ipi6_ifindex == 0 || hoplimit == 0) { | |
1504 | syslog(LOG_ERR, | |
4eb7b489 RM |
1505 | "IPv6 RA/NA did not contain index or hop limit from %s", |
1506 | ctx->sfrom); | |
e82129a4 RM |
1507 | return; |
1508 | } | |
1509 | ||
4eb7b489 | 1510 | TAILQ_FOREACH(ifp, dhcpcd_ctx->ifaces, next) { |
e82129a4 RM |
1511 | if (ifp->index == (unsigned int)pkt.ipi6_ifindex) |
1512 | break; | |
1513 | } | |
1514 | ||
4eb7b489 | 1515 | icp = (struct icmp6_hdr *)ctx->rcvhdr.msg_iov[0].iov_base; |
e82129a4 RM |
1516 | if (icp->icmp6_code == 0) { |
1517 | switch(icp->icmp6_type) { | |
1518 | case ND_NEIGHBOR_ADVERT: | |
34457fe6 | 1519 | ipv6nd_handlena(ctx, ifp, icp, (size_t)len); |
e82129a4 RM |
1520 | return; |
1521 | case ND_ROUTER_ADVERT: | |
34457fe6 | 1522 | ipv6nd_handlera(ctx, ifp, icp, (size_t)len); |
e82129a4 RM |
1523 | return; |
1524 | } | |
1525 | } | |
7cece083 | 1526 | |
e82129a4 | 1527 | syslog(LOG_ERR, "invalid IPv6 type %d or code %d from %s", |
4eb7b489 | 1528 | icp->icmp6_type, icp->icmp6_code, ctx->sfrom); |
e82129a4 RM |
1529 | } |
1530 | ||
d936ec19 | 1531 | static void |
6e6e06af | 1532 | ipv6nd_startrs1(void *arg) |
91cd7324 | 1533 | { |
6e6e06af | 1534 | struct interface *ifp = arg; |
ca15a0aa | 1535 | struct rs_state *state; |
91cd7324 | 1536 | |
e42bbc9b | 1537 | syslog(LOG_INFO, "%s: soliciting an IPv6 router", ifp->name); |
4eb7b489 RM |
1538 | if (ipv6nd_open(ifp->ctx) == -1) { |
1539 | syslog(LOG_ERR, "%s: ipv6nd_open: %m", __func__); | |
6e6e06af | 1540 | return; |
ca15a0aa RM |
1541 | } |
1542 | ||
673e81e5 RM |
1543 | state = RS_STATE(ifp); |
1544 | if (state == NULL) { | |
e82129a4 | 1545 | ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state)); |
673e81e5 | 1546 | state = RS_STATE(ifp); |
fbbb0875 RM |
1547 | if (state == NULL) { |
1548 | syslog(LOG_ERR, "%s: %m", __func__); | |
6e6e06af | 1549 | return; |
fbbb0875 | 1550 | } |
673e81e5 RM |
1551 | } |
1552 | ||
1553 | /* Always make a new probe as the underlying hardware | |
1554 | * address could have changed. */ | |
e82129a4 | 1555 | ipv6nd_makersprobe(ifp); |
fbbb0875 | 1556 | if (state->rs == NULL) { |
e82129a4 | 1557 | syslog(LOG_ERR, "%s: ipv6ns_makersprobe: %m", __func__); |
6e6e06af | 1558 | return; |
fbbb0875 | 1559 | } |
91cd7324 | 1560 | |
ca15a0aa | 1561 | state->rsprobes = 0; |
e82129a4 | 1562 | ipv6nd_sendrsprobe(ifp); |
6e6e06af RM |
1563 | } |
1564 | ||
1565 | void | |
1566 | ipv6nd_startrs(struct interface *ifp) | |
1567 | { | |
1568 | struct timeval tv; | |
1569 | ||
d936ec19 | 1570 | eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); |
6e6e06af | 1571 | tv.tv_sec = 0; |
6eda989c RM |
1572 | tv.tv_usec = (suseconds_t)arc4random_uniform( |
1573 | MAX_RTR_SOLICITATION_DELAY * 1000000); | |
6e6e06af RM |
1574 | timernorm(&tv); |
1575 | syslog(LOG_DEBUG, | |
1576 | "%s: delaying IPv6 router solictation for %0.1f seconds", | |
1577 | ifp->name, timeval_to_double(&tv)); | |
1578 | eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6nd_startrs1, ifp); | |
1579 | return; | |
91cd7324 | 1580 | } |