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