| /* |
| * dhcpcd - DHCP client daemon |
| * Copyright (c) 2006-2012 Roy Marples <[email protected]> |
| * 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; |
| } |