diff options
Diffstat (limited to 'ipv6rs.c')
-rw-r--r-- | ipv6rs.c | 762 |
1 files changed, 762 insertions, 0 deletions
diff --git a/ipv6rs.c b/ipv6rs.c new file mode 100644 index 0000000..df96934 --- /dev/null +++ b/ipv6rs.c @@ -0,0 +1,762 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 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]; + +#if DEBUG_MEMORY +static void +ipv6rs_cleanup(void) +{ + + free(sndbuf); + free(rcvbuf); +} +#endif + +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; + +#if DEBUG_MEMORY + atexit(ipv6rs_cleanup); +#endif + + 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; + + free(ifp->rs); + 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 *nd_ra; + 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; + int has_dns; + + 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; + } + 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; + } + + /* We don't want to spam the log with the fact we got an RA every + * 30 seconds or so, so only spam the log if it's different. */ + if (options & DHCPCD_DEBUG || rap == NULL || + (rap->expired || rap->data_len != len || + memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0)) + { + if (rap) { + free(rap->data); + rap->data_len = 0; + } + syslog(LOG_INFO, "%s: Router Advertisement from %s", + ifp->name, sfrom); + } + + 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)); + rap->data_len = 0; + } + if (rap->data_len == 0) { + rap->data = xmalloc(len); + memcpy(rap->data, icp, len); + rap->data_len = len; + } + + get_monotonic(&rap->received); + nd_ra = (struct nd_router_advert *)icp; + rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); + rap->expired = 0; + + len -= sizeof(struct nd_router_advert); + p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); + olen = 0; + lifetime = ~0U; + has_dns = 0; + 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); + } else + opt = xstrdup(cbp); + if (lifetime > 0) + has_dns = 1; + } + 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; + } else + free(opt); + if (lifetime == ~0U) + timerclear(&rao->expire); + else { + expire.tv_sec = lifetime; + expire.tv_usec = 0; + timeradd(&rap->received, &expire, &rao->expire); + } + } + + ipv6rs_sort(ifp); + run_script_reason(ifp, options & DHCPCD_TEST ? "TEST" : "ROUTERADVERT"); + if (options & DHCPCD_TEST) + exit(EXIT_SUCCESS); + + /* If we don't require RDNSS then set has_dns = 1 so we fork */ + if (!(ifp->state->options->options & DHCPCD_IPV6RA_REQRDNSS)) + has_dns = 1; + + if (has_dns) + delete_q_timeout(0, handle_exit_timeout, NULL); + delete_timeout(NULL, ifp); + ipv6rs_expire(ifp); + if (has_dns) + daemonise(); + else if (options & DHCPCD_DAEMONISE && !(options & DHCPCD_DAEMONISED)) + syslog(LOG_WARNING, + "%s: did not fork due to an absent RDNSS option in the RA", + ifp->name); +} + +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->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; + + free(ifp->rs); + ifp->rs = NULL; + for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) { + ipv6rs_free_opts(rap); + free(rap->data); + free(rap); + } + ifp->ras = NULL; +} + +void +ipv6rs_expire(void *arg) +{ + struct interface *ifp; + struct ra *rap, *ran, *ral; + struct ra_opt *rao, *raol, *raon; + 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, >)) { + syslog(LOG_INFO, "%s: %s: expired Router Advertisement", + ifp->name, rap->sfrom); + rap->expired = expired = 1; + if (ral) + ral->next = ran; + else + ifp->ras = ran; + ipv6rs_free_opts(rap); + free(rap); + continue; + } + timersub(&expire, &now, <); + if (!timerisset(&next) || timercmp(&next, <, >)) + next = lt; + + for (rao = rap->options, raol = NULL; + rao && (raon = rao->next); + raol = rao, rao = raon) + { + if (!timerisset(&rao->expire)) + continue; + if (timercmp(&now, &rao->expire, >)) { + syslog(LOG_INFO, + "%s: %s: expired option %d", + ifp->name, rap->sfrom, rao->type); + rap->expired = expired = 1; + if (raol) + raol = raon; + else + rap->options = raon; + continue; + } + timersub(&rao->expire, &now, <); + if (!timerisset(&next) || timercmp(&next, <, >)) + next = lt; + } + } + + if (timerisset(&next)) + add_timeout_tv(&next, ipv6rs_expire, ifp); + if (expired) + run_script_reason(ifp, "ROUTERADVERT"); +} + +int +ipv6rs_start(struct interface *ifp) +{ + + delete_timeout(NULL, ifp); + + /* Always make a new probe as the underlying hardware + * address could have changed. */ + ipv6rs_makeprobe(ifp); + if (ifp->rs == NULL) + return -1; + + ifp->rsprobes = 0; + ipv6rs_sendprobe(ifp); + return 0; +} |