#include "if-options.h"
#include "if-pref.h"
#include "ipv4ll.h"
+#include "ipv6rs.h"
#include "net.h"
#include "signals.h"
static int ifc;
static char *cffile;
static char *pidfile;
-static int linkfd = -1;
+static int linkfd = -1, ipv6rsfd = -1;
struct dhcp_op {
uint8_t value;
}
void
-drop_config(struct interface *iface, const char *reason)
+drop_dhcp(struct interface *iface, const char *reason)
{
free(iface->state->old);
iface->state->old = iface->state->new;
syslog(LOG_INFO, "%s: removing interface", iface->name);
if (strcmp(iface->state->reason, "RELEASE") != 0)
- drop_config(iface, "STOP");
+ drop_dhcp(iface, "STOP");
+ if (iface->ras) {
+ ipv6rs_free(iface);
+ iface->ras = NULL;
+ iface->state->reason = "ROUTERADVERT";
+ run_script(iface);
+ }
close_sockets(iface);
delete_timeout(NULL, iface);
for (ifp = ifaces; ifp; ifp = ifp->next) {
if (r == -1) {
syslog(LOG_ERR, "%s: send_raw_packet: %m", iface->name);
if (!(options & DHCPCD_TEST))
- drop_config(iface, "FAIL");
+ drop_dhcp(iface, "FAIL");
close_sockets(iface);
delete_timeout(NULL, iface);
callback = NULL;
syslog(LOG_ERR, "%s: lease expired", iface->name);
delete_timeout(NULL, iface);
- drop_config(iface, "EXPIRE");
+ drop_dhcp(iface, "EXPIRE");
unlink(iface->leasefile);
if (iface->carrier != LINK_DOWN)
start_interface(iface);
/* We should restart on a NAK */
log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from);
if (!(options & DHCPCD_TEST)) {
- drop_config(iface, "NAK");
+ drop_dhcp(iface, "NAK");
unlink(iface->leasefile);
}
close_sockets(iface);
ts.tv_sec = RELEASE_DELAY_S;
ts.tv_nsec = RELEASE_DELAY_NS;
nanosleep(&ts, NULL);
- drop_config(iface, "RELEASE");
+ drop_dhcp(iface, "RELEASE");
}
unlink(iface->leasefile);
}
syslog(LOG_INFO, "%s: carrier lost", iface->name);
close_sockets(iface);
delete_timeouts(iface, start_expire, NULL);
- drop_config(iface, "NOCARRIER");
+ drop_dhcp(iface, "NOCARRIER");
+ if (iface->ras) {
+ ipv6rs_free(iface);
+ iface->ras = NULL;
+ iface->state->reason = "ROUTERADVERT";
+ run_script(iface);
+ }
}
} else if (carrier == 1 && !(~iface->flags & (IFF_UP | IFF_RUNNING))) {
if (iface->carrier != LINK_UP) {
free(iface->state->offer);
iface->state->offer = NULL;
+ if (ifo->options & DHCPCD_IPV6RS)
+ ipv6rs_start(iface);
+
if (iface->state->arping_index < ifo->arping_len) {
start_arping(iface);
return;
if (iface->hwlen == 0 && ifo->clientid[0] == '\0') {
syslog(LOG_WARNING, "%s: needs a clientid to configure",
iface->name);
- drop_config(iface, "FAIL");
+ drop_dhcp(iface, "FAIL");
close_sockets(iface);
delete_timeout(NULL, iface);
return;
syslog(LOG_INFO,
"%s: expiring for new hardware address",
ifp->name);
- drop_config(ifp, "EXPIRE");
+ drop_dhcp(ifp, "EXPIRE");
}
memcpy(ifp->hwaddr, hwaddr, hwlen);
ifp->hwlen = hwlen;
if (type == RTM_DELADDR) {
if (ifp->state->new &&
ifp->state->new->yiaddr == addr->s_addr)
- drop_config(ifp, "EXPIRE");
+ drop_dhcp(ifp, "EXPIRE");
return;
}
(opt & (DHCPCD_INFORM | DHCPCD_STATIC) &&
!(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))))
{
- drop_config(iface, "EXPIRE");
+ drop_dhcp(iface, "EXPIRE");
} else {
free(iface->state->offer);
iface->state->offer = NULL;
if (options & DHCPCD_TEST)
exit(EXIT_FAILURE);
- /* As drop_config could re-arrange the order, we do it like this. */
+ /* As drop_dhcp could re-arrange the order, we do it like this. */
for (;;) {
/* Be sane and drop the last config first */
ifl = NULL;
add_event(linkfd, handle_link, NULL);
}
+#if 0
+ if (options & DHCPCD_IPV6RS && disable_rtadv() == -1) {
+ syslog(LOG_ERR, "ipv6rs: %m");
+ options &= ~DHCPCD_IPV6RS;
+ }
+#endif
+
+ if (options & DHCPCD_IPV6RS) {
+ ipv6rsfd = ipv6rs_open();
+ if (ipv6rsfd == -1) {
+ syslog(LOG_ERR, "ipv6rs: %m");
+ options &= ~DHCPCD_IPV6RS;
+ } else {
+ add_event(ipv6rsfd, ipv6rs_handledata, NULL);
+// atexit(restore_rtadv);
+ }
+ }
+
ifc = argc - optind;
ifv = argv + optind;
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2011 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#ifdef __linux__
+# define _LINUX_IN6_H
+# include <linux/ipv6.h>
+#endif
+
+#define ELOOP_QUEUE 1
+#include "bind.h"
+#include "common.h"
+#include "configure.h"
+#include "dhcpcd.h"
+#include "eloop.h"
+#include "ipv6rs.h"
+
+#define ALLROUTERS "ff02::2"
+#define HOPLIMIT 255
+
+#define ROUNDUP8(a) (1 + (((a) - 1) | 7))
+
+#define RTR_SOLICITATION_INTERVAL 4 /* seconds */
+#define MAX_RTR_SOLICITATIONS 3 /* times */
+
+#ifndef ND_OPT_RDNSS
+#define ND_OPT_RDNSS 25
+struct nd_opt_rdnss { /* RDNSS option RFC 6106 */
+ uint8_t nd_opt_rdnss_type;
+ uint8_t nd_opt_rdnss_len;
+ uint16_t nd_opt_rdnss_reserved;
+ uint32_t nd_opt_rdnss_lifetime;
+ /* followed by list of IP prefixes */
+} _packed;
+#endif
+
+#ifndef ND_OPT_DNSSL
+#define ND_OPT_DNSSL 31
+struct nd_opt_dnssl { /* DNSSL option RFC 6106 */
+ uint8_t nd_opt_dnssl_type;
+ uint8_t nd_opt_dnssl_len;
+ uint16_t nd_opt_dnssl_reserved;
+ uint32_t nd_opt_dnssl_lifetime;
+ /* followed by list of DNS servers */
+} _packed;
+#endif
+
+static int sock;
+static struct sockaddr_in6 allrouters, from;
+static struct msghdr sndhdr;
+static struct iovec sndiov[2];
+static unsigned char *sndbuf;
+static struct msghdr rcvhdr;
+static struct iovec rcviov[2];
+static unsigned char *rcvbuf;
+static unsigned char ansbuf[1500];
+static char ntopbuf[INET6_ADDRSTRLEN];
+
+int
+ipv6rs_open(void)
+{
+ int on;
+ int len;
+ struct icmp6_filter filt;
+
+ memset(&allrouters, 0, sizeof(allrouters));
+ allrouters.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+ allrouters.sin6_len = sizeof(allrouters);
+#endif
+ if (inet_pton(AF_INET6, ALLROUTERS, &allrouters.sin6_addr.s6_addr) != 1)
+ return -1;
+ sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ if (sock == -1)
+ return -1;
+ on = 1;
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &on, sizeof(on)) == -1)
+ return -1;
+
+ on = 1;
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
+ &on, sizeof(on)) == -1)
+ return -1;
+
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER,
+ &filt, sizeof(filt)) == -1)
+ return -1;
+
+ len = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int));
+ sndbuf = xzalloc(len);
+ if (sndbuf == NULL)
+ return -1;
+ sndhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndhdr.msg_iov = sndiov;
+ sndhdr.msg_iovlen = 1;
+ sndhdr.msg_control = sndbuf;
+ sndhdr.msg_controllen = len;
+ rcvbuf = xzalloc(len);
+ if (rcvbuf == NULL)
+ return -1;
+ rcvhdr.msg_name = &from;
+ rcvhdr.msg_namelen = sizeof(from);
+ rcvhdr.msg_iov = rcviov;
+ rcvhdr.msg_iovlen = 1;
+ rcvhdr.msg_control = rcvbuf;
+ rcvhdr.msg_controllen = len;
+ rcviov[0].iov_base = ansbuf;
+ rcviov[0].iov_len = sizeof(ansbuf);
+ return sock;
+}
+
+static int
+ipv6rs_makeprobe(struct interface *ifp)
+{
+ struct nd_router_solicit *rs;
+ struct nd_opt_hdr *nd;
+
+ ifp->rslen = sizeof(*rs) + ROUNDUP8(ifp->hwlen + 2);
+ ifp->rs = xzalloc(ifp->rslen);
+ if (ifp->rs == NULL)
+ return -1;
+ rs = (struct nd_router_solicit *)ifp->rs;
+ rs->nd_rs_type = ND_ROUTER_SOLICIT;
+ rs->nd_rs_code = 0;
+ rs->nd_rs_cksum = 0;
+ rs->nd_rs_reserved = 0;
+ nd = (struct nd_opt_hdr *)(ifp->rs + sizeof(*rs));
+ nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+ nd->nd_opt_len = (ROUNDUP8(ifp->hwlen + 2)) >> 3;
+ memcpy(nd + 1, ifp->hwaddr, ifp->hwlen);
+ return 0;
+}
+
+static void
+ipv6rs_sendprobe(void *arg)
+{
+ struct interface *ifp = arg;
+ struct sockaddr_in6 dst;
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi;
+ int hoplimit = HOPLIMIT;
+
+ dst = allrouters;
+ //dst.sin6_scope_id = ifp->linkid;
+
+ ipv6rs_makeprobe(ifp);
+ sndhdr.msg_name = (caddr_t)&dst;
+ sndhdr.msg_iov[0].iov_base = ifp->rs;
+ sndhdr.msg_iov[0].iov_len = ifp->rslen;
+
+ /* Set the outbound interface */
+ cm = CMSG_FIRSTHDR(&sndhdr);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memset(&pi, 0, sizeof(pi));
+ pi.ipi6_ifindex = if_nametoindex(ifp->name);
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+ /* Hop limit */
+ cm = CMSG_NXTHDR(&sndhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(hoplimit));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit));
+
+ syslog(LOG_INFO, "%s: sending IPv6 Router Solicitation", ifp->name);
+ if (sendmsg(sock, &sndhdr, 0) == -1)
+ syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name);
+
+ if (++ifp->rsprobes < MAX_RTR_SOLICITATIONS)
+ add_timeout_sec(RTR_SOLICITATION_INTERVAL,
+ ipv6rs_sendprobe, ifp);
+ else
+ syslog(LOG_INFO, "%s: no IPv6 Routers available", ifp->name);
+}
+
+static void
+ipv6rs_sort(struct interface *ifp)
+{
+ struct ra *rap, *sorted, *ran, *rat;
+
+ if (ifp->ras == NULL || ifp->ras->next == NULL)
+ return;
+
+ /* Sort our RA's - most recent first */
+ sorted = ifp->ras;
+ ifp->ras = ifp->ras->next;
+ sorted->next = NULL;
+ for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) {
+ /* Are we the new head? */
+ if (timercmp(&rap->received, &sorted->received, <)) {
+ rap->next = sorted;
+ sorted = rap;
+ continue;
+ }
+ /* Do we fit in the middle? */
+ for (rat = sorted; rat->next; rat = rat->next) {
+ if (timercmp(&rap->received, &rat->next->received, <)) {
+ rap->next = rat->next;
+ rat->next = rap;
+ break;
+ }
+ }
+ /* We must be at the end */
+ if (!rat->next) {
+ rat->next = rap;
+ rap->next = NULL;
+ }
+ }
+}
+
+void
+ipv6rs_handledata(_unused void *arg)
+{
+ ssize_t len, l, n, olen;
+ struct cmsghdr *cm;
+ int hoplimit;
+ struct in6_pktinfo pkt;
+ struct icmp6_hdr *icp;
+ struct interface *ifp;
+ const char *sfrom;
+ struct nd_router_advert *radvert;
+ struct nd_opt_prefix_info *pi;
+ struct nd_opt_mtu *mtu;
+ struct nd_opt_rdnss *rdnss;
+ struct nd_opt_dnssl *dnssl;
+ uint32_t lifetime;
+ uint8_t *p, *op;
+ struct in6_addr addr;
+ char buf[INET6_ADDRSTRLEN];
+ const char *cbp;
+ struct ra *rap;
+ struct nd_opt_hdr *ndo;
+ struct ra_opt *rao, *raol;
+ char *opt;
+ struct timeval expire;
+
+ len = recvmsg(sock, &rcvhdr, 0);
+ if (len == -1) {
+ syslog(LOG_ERR, "recvmsg: %m");
+ return;
+ }
+ sfrom = inet_ntop(AF_INET6, &from.sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN);
+ if ((size_t)len < sizeof(struct nd_router_advert)) {
+ syslog(LOG_ERR, "IPv6 RA packet too short from %s", sfrom);
+ return;
+ }
+
+ pkt.ipi6_ifindex = hoplimit = 0;
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvhdr);
+ cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvhdr, cm))
+ {
+ if (cm->cmsg_level != IPPROTO_IPV6)
+ continue;
+ switch(cm->cmsg_type) {
+ case IPV6_PKTINFO:
+ if (cm->cmsg_len == CMSG_LEN(sizeof(pkt)))
+ memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt));
+ break;
+ case IPV6_HOPLIMIT:
+ if (cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int));
+ break;
+ }
+ }
+
+ if (pkt.ipi6_ifindex == 0 || hoplimit == 0) {
+ syslog(LOG_ERR,
+ "IPv6 RA did not contain index or hop limit from %s",
+ sfrom);
+ return;
+ }
+
+ icp = (struct icmp6_hdr *)rcvhdr.msg_iov[0].iov_base;
+ if (icp->icmp6_type != ND_ROUTER_ADVERT ||
+ icp->icmp6_code != 0)
+ {
+ syslog(LOG_ERR, "invalid IPv6 type or code from %s", sfrom);
+ return;
+ }
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
+ syslog(LOG_ERR, "RA recieved from non local IPv6 address %s",
+ sfrom);
+ return;
+ }
+
+ for (ifp = ifaces; ifp; ifp = ifp->next)
+ if (if_nametoindex(ifp->name) == (unsigned int)pkt.ipi6_ifindex)
+ break;
+ if (ifp == NULL) {
+ syslog(LOG_ERR,"received RA for unexpected interface from %s",
+ sfrom);
+ return;
+ }
+
+ syslog(LOG_INFO, "%s: Router Advertisement from %s", ifp->name, sfrom);
+ delete_timeouts(ifp, NULL);
+ radvert = (struct nd_router_advert *)icp;
+
+ for (rap = ifp->ras; rap; rap = rap->next) {
+ if (memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr,
+ sizeof(rap->from.s6_addr)) == 0)
+ break;
+ }
+ if (rap == NULL) {
+ rap = xmalloc(sizeof(*rap));
+ rap->next = ifp->ras;
+ rap->options = NULL;
+ ifp->ras = rap;
+ memcpy(rap->from.s6_addr, from.sin6_addr.s6_addr,
+ sizeof(rap->from.s6_addr));
+ strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
+ }
+
+ get_monotonic(&rap->received);
+ rap->lifetime = ntohl(icp->icmp6_dataun.icmp6_un_data32);
+
+ len -= sizeof(struct nd_router_advert);
+ p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
+ olen = 0;
+ lifetime = ~0U;
+ for (olen = 0; len > 0; p += olen, len -= olen) {
+ if ((size_t)len < sizeof(struct nd_opt_hdr)) {
+ syslog(LOG_ERR, "%s: Short option", ifp->name);
+ break;
+ }
+ ndo = (struct nd_opt_hdr *)p;
+ olen = ndo->nd_opt_len * 8 ;
+ if (olen == 0) {
+ syslog(LOG_ERR, "%s: zero length option", ifp->name);
+ break;
+ }
+ if (olen > len) {
+ syslog(LOG_ERR,
+ "%s: Option length exceeds message", ifp->name);
+ break;
+ }
+
+ opt = NULL;
+ switch (ndo->nd_opt_type) {
+ case ND_OPT_PREFIX_INFORMATION:
+ pi = (struct nd_opt_prefix_info *)ndo;
+ if (pi->nd_opt_pi_len != 4) {
+ syslog(LOG_ERR,
+ "%s: invalid option len for prefix",
+ ifp->name);
+ break;
+ }
+ if (pi->nd_opt_pi_prefix_len > 128) {
+ syslog(LOG_ERR, "%s: invalid prefix len",
+ ifp->name);
+ break;
+ }
+ if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) ||
+ IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix))
+ {
+ syslog(LOG_ERR,
+ "%s: invalid prefix in RA", ifp->name);
+ break;
+ }
+ opt = xstrdup(inet_ntop(AF_INET6,
+ pi->nd_opt_pi_prefix.s6_addr,
+ ntopbuf, INET6_ADDRSTRLEN));
+ if (opt) {
+ rap->prefix_len = pi->nd_opt_pi_prefix_len;
+ rap->prefix_vltime =
+ ntohl(pi->nd_opt_pi_valid_time);
+ rap->prefix_pltime =
+ ntohl(pi->nd_opt_pi_preferred_time);
+ }
+ break;
+
+ case ND_OPT_MTU:
+ mtu = (struct nd_opt_mtu *)p;
+ snprintf(buf, sizeof(buf), "%d",
+ ntohl(mtu->nd_opt_mtu_mtu));
+ opt = xstrdup(buf);
+ break;
+
+ case ND_OPT_RDNSS:
+ rdnss = (struct nd_opt_rdnss *)p;
+ lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime);
+ op = (uint8_t *)ndo;
+ op += offsetof(struct nd_opt_rdnss,
+ nd_opt_rdnss_lifetime);
+ op += sizeof(rdnss->nd_opt_rdnss_lifetime);
+ l = 0;
+ for (n = ndo->nd_opt_len - 1; n > 1; n -= 2) {
+ memcpy(&addr.s6_addr, op, sizeof(addr.s6_addr));
+ cbp = inet_ntop(AF_INET6, &addr,
+ ntopbuf, INET6_ADDRSTRLEN);
+ if (cbp == NULL) {
+ syslog(LOG_ERR,
+ "%s: invalid RDNSS address",
+ ifp->name);
+ } else {
+ if (opt) {
+ l = strlen(opt);
+ opt = xrealloc(opt,
+ l + strlen(cbp) + 2);
+ opt[l] = ' ';
+ strcpy(opt + l + 1, cbp);
+ opt[l + strlen(cbp) + l + 1] =
+ '\0';
+ } else
+ opt = xstrdup(cbp);
+ }
+ op += sizeof(addr.s6_addr);
+ }
+ break;
+
+ case ND_OPT_DNSSL:
+ dnssl = (struct nd_opt_dnssl *)p;
+ lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime);
+ op = p + offsetof(struct nd_opt_dnssl,
+ nd_opt_dnssl_lifetime);
+ op += sizeof(dnssl->nd_opt_dnssl_lifetime);
+ n = (dnssl->nd_opt_dnssl_len - 1) * 8;
+ l = decode_rfc3397(NULL, 0, n, op);
+ if (l < 1) {
+ syslog(LOG_ERR, "%s: invalid DNSSL option",
+ ifp->name);
+ } else {
+ opt = xmalloc(l);
+ decode_rfc3397(opt, l, n, op);
+ }
+ break;
+ }
+
+ if (opt == NULL)
+ continue;
+ for (raol = NULL, rao = rap->options;
+ rao;
+ raol = rao, rao = rao->next)
+ {
+ if (rao->type == ndo->nd_opt_type &&
+ strcmp(rao->option, opt) == 0)
+ break;
+ }
+ if (lifetime == 0) {
+ if (rao) {
+ if (raol)
+ raol->next = rao->next;
+ else
+ rap->options = rao->next;
+ free(rao->option);
+ free(rao);
+ }
+ continue;
+ }
+
+ if (rao == NULL) {
+ rao = xmalloc(sizeof(*rao));
+ rao->next = rap->options;
+ rap->options = rao;
+ rao->type = ndo->nd_opt_type;
+ rao->option = opt;
+ }
+ if (lifetime == ~0U) {
+ rao->expire.tv_sec = 0;
+ rao->expire.tv_usec = 0;
+ } else {
+ expire.tv_sec = lifetime;
+ expire.tv_usec = 0;
+ timeradd(&rap->received, &expire, &rao->expire);
+ }
+ }
+
+ ipv6rs_sort(ifp);
+
+ if (options & DHCPCD_TEST)
+ ifp->state->reason = "TEST";
+ else
+ ifp->state->reason = "ROUTERADVERT";
+ run_script(ifp);
+ if (options & DHCPCD_TEST)
+ exit(EXIT_SUCCESS);
+
+ delete_timeouts(ifp, NULL);
+ ipv6rs_expire(ifp);
+ daemonise();
+}
+
+ssize_t
+ipv6rs_env(char **env, const char *prefix, const struct interface *ifp)
+{
+ ssize_t l;
+ struct timeval now;
+ const struct ra *rap;
+ const struct ra_opt *rao;
+ int i;
+ char buffer[32], buffer2[32];
+ const char *optn;
+
+ l = 0;
+ get_monotonic(&now);
+ for (rap = ifp->ras, i = 1; rap; rap = rap->next, i++) {
+ if (env) {
+ snprintf(buffer, sizeof(buffer),
+ "ra%d_from", i);
+ setvar(&env, prefix, buffer, rap->sfrom);
+ }
+ l++;
+ for (rao = rap->options; rao; rao = rao->next) {
+ if (rao->expire.tv_sec != 0 &&
+ rao->expire.tv_usec != 0 &&
+ timercmp(&now, &rao->expire, >))
+ {
+ syslog(LOG_INFO, "%s: %s: expired option %d",
+ ifp->name, rap->sfrom, rao->type);
+ continue;
+ }
+ if (rao->option == NULL)
+ continue;
+ if (env == NULL) {
+ switch (rao->type) {
+ case ND_OPT_PREFIX_INFORMATION:
+ l += 4;
+ break;
+ default:
+ l++;
+ }
+ continue;
+ }
+ switch (rao->type) {
+ case ND_OPT_PREFIX_INFORMATION:
+ optn = "prefix";
+ break;
+ case ND_OPT_MTU:
+ optn = "mtu";
+ break;
+ case ND_OPT_RDNSS:
+ optn = "rdnss";
+ break;
+ case ND_OPT_DNSSL:
+ optn = "dnssl";
+ break;
+ default:
+ continue;
+ }
+ snprintf(buffer, sizeof(buffer), "ra%d_%s", i, optn);
+ setvar(&env, prefix, buffer, rao->option);
+ l++;
+ switch (rao->type) {
+ case ND_OPT_PREFIX_INFORMATION:
+ snprintf(buffer, sizeof(buffer),
+ "ra%d_prefix_len", i);
+ snprintf(buffer2, sizeof(buffer2),
+ "%d", rap->prefix_len);
+ setvar(&env, prefix, buffer, buffer2);
+
+ snprintf(buffer, sizeof(buffer),
+ "ra%d_prefix_vltime", i);
+ snprintf(buffer2, sizeof(buffer2),
+ "%d", rap->prefix_vltime);
+ setvar(&env, prefix, buffer, buffer2);
+
+ snprintf(buffer, sizeof(buffer),
+ "ra%d_prefix_pltime", i);
+ snprintf(buffer2, sizeof(buffer2),
+ "%d", rap->prefix_pltime);
+ setvar(&env, prefix, buffer, buffer2);
+ l += 3;
+ break;
+ }
+
+ }
+ }
+
+ if (env)
+ setvard(&env, prefix, "ra_count", i - 1);
+ l++;
+ return l;
+}
+
+static void
+ipv6rs_free_opts(struct ra *rap)
+{
+ struct ra_opt *rao, *raon;
+
+ for (rao = rap->options; rao && (raon = rao->next, 1); rao = raon) {
+ free(rao->option);
+ free(rao);
+ }
+}
+
+void
+ipv6rs_free(struct interface *ifp)
+{
+ struct ra *rap, *ran;
+
+ for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) {
+ ipv6rs_free_opts(rap);
+ free(rap);
+ }
+}
+
+void
+ipv6rs_expire(void *arg)
+{
+ struct interface *ifp;
+ struct ra *rap, *ran, *ral;
+ struct timeval now, lt, expire, next;
+ int expired;
+ uint32_t expire_secs;
+
+ ifp = arg;
+ get_monotonic(&now);
+ expired = 0;
+ expire_secs = ~0U;
+ timerclear(&next);
+
+ for (rap = ifp->ras, ral = NULL;
+ rap && (ran = rap->next, 1);
+ ral = rap, rap = ran)
+ {
+ lt.tv_sec = rap->lifetime;
+ lt.tv_usec = 0;
+ timeradd(&rap->received, <, &expire);
+ if (timercmp(&now, &expire, >)) {
+ expired = 1;
+ if (ral)
+ ral->next = ran;
+ else
+ ifp->ras = ran;
+ ipv6rs_free_opts(rap);
+ free(rap);
+ } else {
+ timersub(&now, &expire, <);
+ if (!timerisset(&next) || timercmp(&next, <, >))
+ next = lt;
+ }
+ }
+
+ if (timerisset(&next))
+ add_timeout_tv(&next, ipv6rs_expire, ifp);
+
+ if (expired) {
+ ifp->state->reason = "ROUTERADVERT";
+ run_script(ifp);
+ }
+
+}
+
+int
+ipv6rs_start(struct interface *ifp)
+{
+
+ delete_timeouts(ifp, NULL);
+
+ /* Always make a new probe as the underlying hardware
+ * address could have changed. */
+ free(ifp->rs);
+ ipv6rs_makeprobe(ifp);
+ if (ifp->rs == NULL)
+ return -1;
+
+ ifp->rsprobes = 0;
+ ipv6rs_sendprobe(ifp);
+ return 0;
+}