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