aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/rtadvd/rtadvd.c
diff options
context:
space:
mode:
authorYoshinobu Inoue <shin@FreeBSD.org>2000-01-06 12:40:54 +0000
committerYoshinobu Inoue <shin@FreeBSD.org>2000-01-06 12:40:54 +0000
commit9a4365d0e0833374e893c519639bde71756aa104 (patch)
tree0fa615bec427848e939cbb6c31fed401f162edbb /usr.sbin/rtadvd/rtadvd.c
parent171eec1876b8c1cf601856138ec2f53771c1e32b (diff)
downloadsrc-9a4365d0e0833374e893c519639bde71756aa104.tar.gz
src-9a4365d0e0833374e893c519639bde71756aa104.zip
libipsec and IPsec related apps. (and some KAME related man pages)
Reviewed by: freebsd-arch, cvs-committers Obtained from: KAME project
Notes
Notes: svn path=/head/; revision=55505
Diffstat (limited to 'usr.sbin/rtadvd/rtadvd.c')
-rw-r--r--usr.sbin/rtadvd/rtadvd.c1239
1 files changed, 1239 insertions, 0 deletions
diff --git a/usr.sbin/rtadvd/rtadvd.c b/usr.sbin/rtadvd/rtadvd.c
new file mode 100644
index 000000000000..03ed974f656a
--- /dev/null
+++ b/usr.sbin/rtadvd/rtadvd.c
@@ -0,0 +1,1239 @@
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet/icmp6.h>
+
+#include <arpa/inet.h>
+
+#ifdef IPSEC
+#include <netinet6/ipsec.h>
+#endif /*IPSEC*/
+
+#include <time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <sysexits.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include "rtadvd.h"
+#include "rrenum.h"
+#include "advcap.h"
+#include "timer.h"
+#include "if.h"
+#include "config.h"
+
+struct msghdr rcvmhdr;
+static u_char rcvcmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int))];
+struct msghdr sndmhdr;
+struct iovec rcviov[2];
+struct iovec sndiov[2];
+struct sockaddr_in6 from;
+struct sockaddr_in6 sin6_allnodes = {sizeof(sin6_allnodes), AF_INET6};
+int sock, rrsock, rtsock;
+int accept_rr = 0;
+int dflag = 0, sflag = 0;
+
+u_char *conffile = NULL;
+
+struct rainfo *ralist = NULL;
+struct nd_optlist {
+ struct nd_optlist *next;
+ struct nd_opt_hdr *opt;
+};
+union nd_opts {
+ struct nd_opt_hdr *nd_opt_array[7];
+ struct {
+ struct nd_opt_hdr *zero;
+ struct nd_opt_hdr *src_lladdr;
+ struct nd_opt_hdr *tgt_lladdr;
+ struct nd_opt_prefix_info *pi;
+ struct nd_opt_rd_hdr *rh;
+ struct nd_opt_mtu *mtu;
+ struct nd_optlist *list;
+ } nd_opt_each;
+};
+#define nd_opts_src_lladdr nd_opt_each.src_lladdr
+#define nd_opts_tgt_lladdr nd_opt_each.tgt_lladdr
+#define nd_opts_pi nd_opt_each.pi
+#define nd_opts_rh nd_opt_each.rh
+#define nd_opts_mtu nd_opt_each.mtu
+#define nd_opts_list nd_opt_each.list
+
+#define NDOPT_FLAG_SRCLINKADDR 0x1
+#define NDOPT_FLAG_TGTLINKADDR 0x2
+#define NDOPT_FLAG_PREFIXINFO 0x4
+#define NDOPT_FLAG_RDHDR 0x8
+#define NDOPT_FLAG_MTU 0x10
+
+u_int32_t ndopt_flags[] = {
+ 0, NDOPT_FLAG_SRCLINKADDR, NDOPT_FLAG_TGTLINKADDR,
+ NDOPT_FLAG_PREFIXINFO, NDOPT_FLAG_RDHDR, NDOPT_FLAG_MTU
+};
+
+int main __P((int, char *[]));
+static void sock_open __P((int *, int, char *));
+static void rtsock_open __P((void));
+static void rtadvd_input __P((int));
+static void rs_input __P((int, struct nd_router_solicit *,
+ struct in6_pktinfo *, struct sockaddr_in6 *));
+static void ra_input __P((int, struct nd_router_advert *,
+ struct in6_pktinfo *, struct sockaddr_in6 *));
+static void prefix_check __P((struct nd_opt_prefix_info *, struct rainfo *,
+ struct sockaddr_in6 *));
+static int nd6_options __P((struct nd_opt_hdr *, int,
+ union nd_opts *, u_int32_t));
+static void free_ndopts __P((union nd_opts *));
+static struct rainfo *if_indextorainfo __P((int));
+static void ra_output __P((struct rainfo *));
+static void rtmsg_input __P((void));
+struct prefix *find_prefix __P((struct rainfo *, struct in6_addr *, int));
+
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ fd_set fdset;
+ int maxfd = 0;
+ struct timeval *timeout;
+ int i, ch;
+ int fflag = 0;
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ char *policy = NULL;
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+
+ openlog(*argv, LOG_NDELAY|LOG_PID, LOG_DAEMON);
+
+ /* get command line options and arguments */
+ while ((ch = getopt(argc, argv, "c:dDfR:s")) != -1) {
+ switch(ch) {
+ case 'c':
+ conffile = optarg;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'D':
+ dflag = 2;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ case 'P':
+ policy = strdup(optarg);
+ accept_rr = 1;
+ break;
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+ case 's':
+ sflag = 1;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc == 0) {
+ fprintf(stderr,
+ "usage: rtadvd [-c conffile] [-d|D] [-f] [-s]"
+ "interfaces...\n");
+ exit(1);
+ }
+
+ /* set log level */
+ if (dflag == 0)
+ (void)setlogmask(LOG_UPTO(LOG_ERR));
+ if (dflag == 1)
+ (void)setlogmask(LOG_UPTO(LOG_INFO));
+
+ /* timer initialization */
+ rtadvd_timer_init();
+
+ /* random value initialization */
+ srandom((u_long)time(NULL));
+
+ /* get iflist block from kernel */
+ init_iflist();
+
+ while (argc--)
+ getconfig(*argv++);
+
+ if (inet_pton(AF_INET6, ALLNODES, &sin6_allnodes.sin6_addr) != 1) {
+ fprintf(stderr, "fatal: inet_pton failed\n");
+ exit(1);
+ }
+ sock_open(&sock, 0, NULL);
+ if (accept_rr != 0)
+ sock_open(&rrsock, 1, policy);
+
+ if (!fflag)
+ daemon(1, 0);
+
+ FD_ZERO(&fdset);
+ FD_SET(sock, &fdset);
+ if (accept_rr)
+ FD_SET(rrsock, &fdset);
+ maxfd = sock > rrsock ? sock : rrsock;
+ if (sflag == 0) {
+ rtsock_open();
+ FD_SET(rtsock, &fdset);
+ if (rtsock > maxfd)
+ maxfd = rtsock;
+ }
+
+ while (1) {
+ struct fd_set select_fd = fdset; /* reinitialize */
+
+ /* timer expiration check and reset the timer */
+ timeout = rtadvd_check_timer();
+
+ syslog(LOG_DEBUG,
+ "<%s> set timer to %ld:%ld. waiting for inputs "
+ "or timeout",
+ __FUNCTION__,
+ timeout->tv_sec, timeout->tv_usec);
+
+ if ((i = select(maxfd + 1, &select_fd,
+ NULL, NULL, timeout)) < 0){
+ syslog(LOG_ERR, "<%s> select: %s",
+ __FUNCTION__, strerror(errno));
+ continue;
+ }
+ if (i == 0) /* timeout */
+ continue;
+ if (sflag == 0 && FD_ISSET(rtsock, &select_fd))
+ rtmsg_input();
+ if (FD_ISSET(sock, &select_fd))
+ rtadvd_input(sock);
+ if (accept_rr && FD_ISSET(rrsock, &select_fd))
+ rtadvd_input(rrsock);
+ }
+ exit(0); /* NOTREACHED */
+}
+
+static void
+rtmsg_input()
+{
+ int n, type, ifindex, plen;
+ size_t len;
+ char msg[2048], *next, *lim;
+ u_char ifname[16];
+ struct prefix *prefix;
+ struct rainfo *rai;
+ struct in6_addr *addr;
+ char addrbuf[INET6_ADDRSTRLEN];
+
+ n = read(rtsock, msg, 2048);
+ if (dflag > 1) {
+ syslog(LOG_DEBUG,
+ "<%s> received a routing message "
+ "(type = %d, len = %d)",
+ __FUNCTION__,
+ rtmsg_type(msg), n);
+ }
+ if (n > rtmsg_len(msg)) {
+ /*
+ * This usually won't happen for messages received on
+ * an routing socket.
+ */
+ if (dflag > 1)
+ syslog(LOG_DEBUG,
+ "<%s> received data length is larger than"
+ "1st routing message len. multiple messages?"
+ " read %d bytes, but 1st msg len = %d",
+ __FUNCTION__, n, rtmsg_len(msg));
+ }
+
+ lim = msg + n;
+ for (next = msg; next < lim; next += len) {
+ next = get_next_msg(next, lim, 0, &len,
+ RTADV_TYPE2BITMASK(RTM_ADD) |
+ RTADV_TYPE2BITMASK(RTM_DELETE) |
+ RTADV_TYPE2BITMASK(RTM_NEWADDR) |
+ RTADV_TYPE2BITMASK(RTM_DELADDR) |
+ RTADV_TYPE2BITMASK(RTM_IFINFO));
+ if (len == 0)
+ break;
+ type = rtmsg_type(next);
+ switch (type) {
+ case RTM_ADD:
+ case RTM_DELETE:
+ ifindex = get_rtm_ifindex(next);
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ ifindex = get_ifam_ifindex(next);
+ break;
+ case RTM_IFINFO:
+ ifindex = get_ifm_ifindex(next);
+ break;
+ default:
+ /* should not reach here */
+ if (dflag > 1) {
+ syslog(LOG_DEBUG,
+ "<%s:%d> unknown rtmsg %d on %s",
+ __FUNCTION__, __LINE__, type,
+ if_indextoname(ifindex, ifname));
+ }
+ return;
+ }
+
+ if ((rai = if_indextorainfo(ifindex)) == NULL) {
+ if (dflag > 1) {
+ syslog(LOG_DEBUG,
+ "<%s> route changed on "
+ "non advertising interface(%s)",
+ __FUNCTION__,
+ if_indextoname(ifindex, ifname));
+ }
+ return;
+ }
+
+ switch(type) {
+ case RTM_ADD:
+ /* init iffalgs because it may have changed */
+ iflist[ifindex]->ifm_flags =
+ if_getflags(ifindex,
+ iflist[ifindex]->ifm_flags);
+
+ addr = get_addr(msg);
+ plen = get_prefixlen(msg);
+ /* sanity check for plen */
+ if (plen < 4 /* as RFC2373, prefixlen is at least 4 */
+ || plen > 127) {
+ syslog(LOG_INFO, "<%s> new interface route's"
+ "plen %d is invalid for a prefix",
+ __FUNCTION__, plen);
+ return;
+ }
+ prefix = find_prefix(rai, addr, plen);
+ if (prefix) {
+ if (dflag > 1) {
+ syslog(LOG_DEBUG,
+ "<%s> new prefix(%s/%d) "
+ "added on %s, "
+ "but it was already in list",
+ __FUNCTION__,
+ inet_ntop(AF_INET6,
+ addr, (char *)addrbuf,
+ INET6_ADDRSTRLEN),
+ plen,
+ rai->ifname);
+ }
+ return;
+ }
+ make_prefix(rai, ifindex, addr, plen);
+ break;
+ case RTM_DELETE:
+ /* init ifflags because it may have changed */
+ iflist[ifindex]->ifm_flags =
+ if_getflags(ifindex,
+ iflist[ifindex]->ifm_flags);
+
+ addr = get_addr(msg);
+ plen = get_prefixlen(msg);
+ /* sanity check for plen */
+ if (plen < 4 /* as RFC2373, prefixlen is at least 4 */
+ || plen > 127) {
+ syslog(LOG_INFO, "<%s> deleted interface"
+ "route's"
+ "plen %d is invalid for a prefix",
+ __FUNCTION__, plen);
+ return;
+ }
+ prefix = find_prefix(rai, addr, plen);
+ if (prefix == NULL) {
+ if (dflag > 1) {
+ syslog(LOG_DEBUG,
+ "<%s> prefix(%s/%d) was "
+ "deleted on %s, "
+ "but it was not in list",
+ __FUNCTION__,
+ inet_ntop(AF_INET6,
+ addr, (char *)addrbuf,
+ INET6_ADDRSTRLEN),
+ plen,
+ rai->ifname);
+ }
+ return;
+ }
+ delete_prefix(rai, prefix);
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ /* init ifflags because it may have changed */
+ iflist[ifindex]->ifm_flags =
+ if_getflags(ifindex,
+ iflist[ifindex]->ifm_flags);
+ break;
+ case RTM_IFINFO:
+ iflist[ifindex]->ifm_flags = get_ifm_flags(next);
+ break;
+ default:
+ /* should not reach here */
+ if (dflag > 1) {
+ syslog(LOG_DEBUG,
+ "<%s:%d> unknown rtmsg %d on %s",
+ __FUNCTION__, __LINE__, type,
+ if_indextoname(ifindex, ifname));
+ }
+ return;
+ }
+ }
+
+ return;
+}
+
+void
+rtadvd_input(skt)
+{
+ int i;
+ int *hlimp = NULL;
+ struct icmp6_hdr *icp;
+ int ifindex = 0;
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi = NULL;
+ u_char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ struct in6_addr dst = in6addr_any;
+
+ /*
+ * Get message. We reset msg_controllen since the field could
+ * be modified if we had received a message before setting
+ * receive options.
+ */
+ rcvmhdr.msg_controllen = sizeof(rcvcmsgbuf);
+ if ((i = recvmsg(skt, &rcvmhdr, 0)) < 0)
+ return;
+
+ /* extract optional information via Advanced API */
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvmhdr);
+ cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvmhdr, cm)) {
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PKTINFO &&
+ cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
+ pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
+ ifindex = pi->ipi6_ifindex;
+ dst = pi->ipi6_addr;
+ }
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_HOPLIMIT &&
+ cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ hlimp = (int *)CMSG_DATA(cm);
+ }
+ if (ifindex == 0) {
+ syslog(LOG_ERR,
+ "<%s> failed to get receiving interface",
+ __FUNCTION__);
+ return;
+ }
+ if (hlimp == NULL) {
+ syslog(LOG_ERR,
+ "<%s> failed to get receiving hop limit",
+ __FUNCTION__);
+ return;
+ }
+
+ if (i < sizeof(struct icmp6_hdr)) {
+ syslog(LOG_ERR,
+ "<%s> packet size(%d) is too short",
+ __FUNCTION__, i);
+ return;
+ }
+
+ icp = (struct icmp6_hdr *)rcvmhdr.msg_iov[0].iov_base;
+
+ switch(icp->icmp6_type) {
+ case ND_ROUTER_SOLICIT:
+ /* hop limit verification - RFC-2461 6.1.1 */
+ if (*hlimp != 255) {
+ syslog(LOG_NOTICE,
+ "<%s> invalid hop limit(%d) "
+ "received from %s on %s",
+ __FUNCTION__, *hlimp,
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
+ INET6_ADDRSTRLEN),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+ rs_input(i, (struct nd_router_solicit *)icp, pi, &from);
+ break;
+ case ND_ROUTER_ADVERT:
+ /* hop limit verification - RFC-2461 6.1.1 */
+ if (*hlimp != 255) {
+ syslog(LOG_NOTICE,
+ "<%s> invalid hop limit(%d) "
+ "received from %s on %s",
+ __FUNCTION__, *hlimp,
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
+ INET6_ADDRSTRLEN),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+ ra_input(i, (struct nd_router_advert *)icp, pi, &from);
+ break;
+ case ICMP6_ROUTER_RENUMBERING:
+ if (accept_rr == 0) {
+ syslog(LOG_ERR,
+ "<%s> received a router renumbering "
+ "message, but not allowed to be accepted",
+ __FUNCTION__);
+ break;
+ }
+ rr_input(i, (struct icmp6_router_renum *)icp, pi, &from,
+ &dst);
+ break;
+ default:
+ /*
+ * Note that this case is POSSIBLE, especially just
+ * after invocation of the daemon. This is because we
+ * could receive message after opening the socket and
+ * before setting ICMP6 type filter(see sock_open()).
+ */
+ syslog(LOG_ERR,
+ "<%s> invalid icmp type(%d)",
+ __FUNCTION__, icp->icmp6_type);
+ return;
+ }
+
+ return;
+}
+
+static void
+rs_input(int len, struct nd_router_solicit *rs,
+ struct in6_pktinfo *pi, struct sockaddr_in6 *from)
+{
+ u_char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ union nd_opts ndopts;
+ struct rainfo *ra;
+
+ syslog(LOG_DEBUG,
+ "<%s> RS received from %s on %s",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+
+ /* ND option check */
+ memset(&ndopts, 0, sizeof(ndopts));
+ if (nd6_options((struct nd_opt_hdr *)(rs + 1),
+ len - sizeof(struct nd_router_solicit),
+ &ndopts, NDOPT_FLAG_SRCLINKADDR)) {
+ syslog(LOG_DEBUG,
+ "<%s> ND option check failed for an RS from %s on %s",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ /*
+ * If the IP source address is the unspecified address, there
+ * must be no source link-layer address option in the message.
+ * (RFC-2461 6.1.1)
+ */
+ if (IN6_IS_ADDR_UNSPECIFIED(&from->sin6_addr) &&
+ ndopts.nd_opts_src_lladdr) {
+ syslog(LOG_ERR,
+ "<%s> RS from unspecified src on %s has a link-layer"
+ " address option",
+ __FUNCTION__,
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ goto done;
+ }
+
+ ra = ralist;
+ while (ra != NULL) {
+ if (pi->ipi6_ifindex == ra->ifindex)
+ break;
+ ra = ra->next;
+ }
+ if (ra == NULL) {
+ syslog(LOG_INFO,
+ "<%s> RS received on non advertising interface(%s)",
+ __FUNCTION__,
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ goto done;
+ }
+
+ /*
+ * Decide whether to send RA according to the rate-limit
+ * consideration.
+ */
+ {
+ long delay; /* must not be greater than 1000000 */
+ struct timeval interval, now, min_delay, tm_tmp, *rest;
+
+ /*
+ * If there is already a waiting RS packet, don't
+ * update the timer.
+ */
+ if (ra->waiting++)
+ goto done;
+
+ /*
+ * Compute a random delay. If the computed value
+ * corresponds to a time later than the time the next
+ * multicast RA is scheduled to be sent, ignore the random
+ * delay and send the advertisement at the
+ * already-scheduled time. RFC-2461 6.2.6
+ */
+ delay = random() % MAX_RA_DELAY_TIME;
+ interval.tv_sec = 0;
+ interval.tv_usec = delay;
+ rest = rtadvd_timer_rest(ra->timer);
+ if (TIMEVAL_LT(*rest, interval)) {
+ syslog(LOG_DEBUG,
+ "<%s> random delay is larger than "
+ "the rest of normal timer",
+ __FUNCTION__);
+ interval = *rest;
+ }
+
+ /*
+ * If we sent a multicast Router Advertisement within
+ * the last MIN_DELAY_BETWEEN_RAS seconds, schedule
+ * the advertisement to be sent at a time corresponding to
+ * MIN_DELAY_BETWEEN_RAS plus the random value after the
+ * previous advertisement was sent.
+ */
+ gettimeofday(&now, NULL);
+ TIMEVAL_SUB(&now, &ra->lastsent, &tm_tmp);
+ min_delay.tv_sec = MIN_DELAY_BETWEEN_RAS;
+ min_delay.tv_usec = 0;
+ if (TIMEVAL_LT(tm_tmp, min_delay)) {
+ TIMEVAL_SUB(&min_delay, &tm_tmp, &min_delay);
+ TIMEVAL_ADD(&min_delay, &interval, &interval);
+ }
+ rtadvd_set_timer(&interval, ra->timer);
+ goto done;
+ }
+
+ done:
+ free_ndopts(&ndopts);
+ return;
+}
+
+static void
+ra_input(int len, struct nd_router_advert *ra,
+ struct in6_pktinfo *pi, struct sockaddr_in6 *from)
+{
+ struct rainfo *rai;
+ u_char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ union nd_opts ndopts;
+ char *on_off[] = {"OFF", "ON"};
+ u_int32_t reachabletime, retranstimer, mtu;
+
+ syslog(LOG_DEBUG,
+ "<%s> RA received from %s on %s",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+
+ /* ND option check */
+ memset(&ndopts, 0, sizeof(ndopts));
+ if (nd6_options((struct nd_opt_hdr *)(ra + 1),
+ len - sizeof(struct nd_router_advert),
+ &ndopts,
+ NDOPT_FLAG_PREFIXINFO | NDOPT_FLAG_MTU)) {
+ syslog(LOG_ERR,
+ "<%s> ND option check failed for an RA from %s on %s",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ /*
+ * RA consistency check according to RFC-2461 6.2.7
+ */
+ if ((rai = if_indextorainfo(pi->ipi6_ifindex)) == 0) {
+ syslog(LOG_INFO,
+ "<%s> received RA from %s on non-advertising"
+ " interface(%s)",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ goto done;
+ }
+ /* Cur Hop Limit value */
+ if (ra->nd_ra_curhoplimit && rai->hoplimit &&
+ ra->nd_ra_curhoplimit != rai->hoplimit) {
+ syslog(LOG_WARNING,
+ "<%s> CurHopLimit inconsistent on %s:"
+ " %d from %s, %d from us",
+ __FUNCTION__,
+ rai->ifname,
+ ra->nd_ra_curhoplimit,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ rai->hoplimit);
+ }
+ /* M flag */
+ if ((ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) !=
+ rai->managedflg) {
+ syslog(LOG_WARNING,
+ "<%s> M flag inconsistent on %s:"
+ " %s from %s, %s from us",
+ __FUNCTION__,
+ rai->ifname,
+ on_off[!rai->managedflg],
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ on_off[rai->managedflg]);
+ }
+ /* O flag */
+ if ((ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) !=
+ rai->otherflg) {
+ syslog(LOG_WARNING,
+ "<%s> O flag inconsistent on %s:"
+ " %s from %s, %s from us",
+ __FUNCTION__,
+ rai->ifname,
+ on_off[!rai->otherflg],
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ on_off[rai->otherflg]);
+ }
+ /* Reachable Time */
+ reachabletime = ntohl(ra->nd_ra_reachable);
+ if (reachabletime && rai->reachabletime &&
+ reachabletime != rai->reachabletime) {
+ syslog(LOG_WARNING,
+ "<%s> ReachableTime inconsistent on %s:"
+ " %d from %s, %d from us",
+ __FUNCTION__,
+ rai->ifname,
+ reachabletime,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ rai->reachabletime);
+ }
+ /* Retrans Timer */
+ retranstimer = ntohl(ra->nd_ra_retransmit);
+ if (retranstimer && rai->retranstimer &&
+ retranstimer != rai->retranstimer) {
+ syslog(LOG_WARNING,
+ "<%s> RetranceTimer inconsistent on %s:"
+ " %d from %s, %d from us",
+ __FUNCTION__,
+ rai->ifname,
+ retranstimer,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ rai->retranstimer);
+ }
+ /* Values in the MTU options */
+ if (ndopts.nd_opts_mtu) {
+ mtu = ntohl(ndopts.nd_opts_mtu->nd_opt_mtu_mtu);
+ if (mtu && rai->linkmtu && mtu != rai->linkmtu) {
+ syslog(LOG_WARNING,
+ "<%s> MTU option value inconsistent on %s:"
+ " %d from %s, %d from us",
+ __FUNCTION__,
+ rai->ifname, mtu,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ rai->linkmtu);
+ }
+ }
+ /* Preferred and Valid Lifetimes for prefixes */
+ {
+ struct nd_optlist *optp = ndopts.nd_opts_list;
+
+ if (ndopts.nd_opts_pi)
+ prefix_check(ndopts.nd_opts_pi, rai, from);
+ while (optp) {
+ prefix_check((struct nd_opt_prefix_info *)optp->opt,
+ rai, from);
+ optp = optp->next;
+ }
+ }
+
+ done:
+ free_ndopts(&ndopts);
+ return;
+}
+
+static void
+prefix_check(struct nd_opt_prefix_info *pinfo,
+ struct rainfo *rai, struct sockaddr_in6 *from)
+{
+ u_int32_t preferred_time, valid_time;
+ struct prefix *pp;
+ u_char ntopbuf[INET6_ADDRSTRLEN], prefixbuf[INET6_ADDRSTRLEN];
+
+ /*
+ * log if the adveritsed prefix has link-local scope(sanity check?)
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&pinfo->nd_opt_pi_prefix)) {
+ syslog(LOG_INFO,
+ "<%s> link-local prefix %s/%d is advertised "
+ "from %s on %s",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix,
+ prefixbuf, INET6_ADDRSTRLEN),
+ pinfo->nd_opt_pi_prefix_len,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ rai->ifname);
+ }
+
+ if ((pp = find_prefix(rai, &pinfo->nd_opt_pi_prefix,
+ pinfo->nd_opt_pi_prefix_len)) == NULL) {
+ syslog(LOG_INFO,
+ "<%s> prefix %s/%d from %s on %s is not in our list",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix,
+ prefixbuf, INET6_ADDRSTRLEN),
+ pinfo->nd_opt_pi_prefix_len,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ rai->ifname);
+ return;
+ }
+
+ preferred_time = ntohl(pinfo->nd_opt_pi_preferred_time);
+ if (preferred_time != pp->preflifetime)
+ syslog(LOG_WARNING,
+ "<%s> prefeerred lifetime for %s/%d"
+ " inconsistent on %s:"
+ " %d from %s, %d from us",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix,
+ prefixbuf, INET6_ADDRSTRLEN),
+ pinfo->nd_opt_pi_prefix_len,
+ rai->ifname, preferred_time,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ pp->preflifetime);
+
+ valid_time = ntohl(pinfo->nd_opt_pi_valid_time);
+ if (valid_time != pp->validlifetime)
+ syslog(LOG_WARNING,
+ "<%s> valid lifetime for %s/%d"
+ " inconsistent on %s:"
+ " %d from %s, %d from us",
+ __FUNCTION__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix,
+ prefixbuf, INET6_ADDRSTRLEN),
+ pinfo->nd_opt_pi_prefix_len,
+ rai->ifname, valid_time,
+ inet_ntop(AF_INET6, &from->sin6_addr,
+ ntopbuf, INET6_ADDRSTRLEN),
+ pp->validlifetime);
+}
+
+struct prefix *
+find_prefix(struct rainfo *rai, struct in6_addr *prefix, int plen)
+{
+ struct prefix *pp;
+ int bytelen, bitlen;
+
+ for (pp = rai->prefix.next; pp != &rai->prefix; pp = pp->next) {
+ if (plen != pp->prefixlen)
+ continue;
+ bytelen = plen / 8;
+ bitlen = plen % 8;
+ if (memcmp((void *)prefix, (void *)&pp->prefix, bytelen))
+ continue;
+ if (prefix->s6_addr[bytelen] >> (8 - bitlen) ==
+ pp->prefix.s6_addr[bytelen] >> (8 - bitlen))
+ return(pp);
+ }
+
+ return(NULL);
+}
+
+static int
+nd6_options(struct nd_opt_hdr *hdr, int limit,
+ union nd_opts *ndopts, u_int32_t optflags)
+{
+ int optlen = 0;
+
+ for (; limit > 0; limit -= optlen) {
+ hdr = (struct nd_opt_hdr *)((caddr_t)hdr + optlen);
+ optlen = hdr->nd_opt_len << 3;
+ if (hdr->nd_opt_len == 0) {
+ syslog(LOG_ERR,
+ "<%s> bad ND option length(0) (type = %d)",
+ __FUNCTION__, hdr->nd_opt_type);
+ goto bad;
+ }
+
+ if (hdr->nd_opt_type > ND_OPT_MTU) {
+ syslog(LOG_INFO,
+ "<%s> unknown ND option(type %d)",
+ __FUNCTION__,
+ hdr->nd_opt_type);
+ continue;
+ }
+
+ if ((ndopt_flags[hdr->nd_opt_type] & optflags) == 0) {
+ syslog(LOG_INFO,
+ "<%s> unexpected ND option(type %d)",
+ __FUNCTION__,
+ hdr->nd_opt_type);
+ continue;
+ }
+
+ switch(hdr->nd_opt_type) {
+ case ND_OPT_SOURCE_LINKADDR:
+ case ND_OPT_TARGET_LINKADDR:
+ case ND_OPT_REDIRECTED_HEADER:
+ case ND_OPT_MTU:
+ if (ndopts->nd_opt_array[hdr->nd_opt_type]) {
+ syslog(LOG_INFO,
+ "<%s> duplicated ND option"
+ " (type = %d)",
+ __FUNCTION__,
+ hdr->nd_opt_type);
+ }
+ ndopts->nd_opt_array[hdr->nd_opt_type] = hdr;
+ break;
+ case ND_OPT_PREFIX_INFORMATION:
+ {
+ struct nd_optlist *pfxlist;
+
+ if (ndopts->nd_opts_pi == 0) {
+ ndopts->nd_opts_pi =
+ (struct nd_opt_prefix_info *)hdr;
+ continue;
+ }
+ if ((pfxlist = malloc(sizeof(*pfxlist))) == NULL) {
+ syslog(LOG_ERR,
+ "<%s> can't allocate memory",
+ __FUNCTION__);
+ goto bad;
+ }
+ pfxlist->next = ndopts->nd_opts_list;
+ pfxlist->opt = hdr;
+ ndopts->nd_opts_list = pfxlist;
+
+ break;
+ }
+ default: /* impossible */
+ break;
+ }
+ }
+
+ return(0);
+
+ bad:
+ free_ndopts(ndopts);
+
+ return(-1);
+}
+
+static void
+free_ndopts(union nd_opts *ndopts)
+{
+ struct nd_optlist *opt = ndopts->nd_opts_list, *next;
+
+ while(opt) {
+ next = opt->next;
+ free(opt);
+ opt = next;
+ }
+}
+
+void
+sock_open(int *sockp, int is_rr, char *policy)
+{
+ struct icmp6_filter filt;
+ struct ipv6_mreq mreq;
+ struct rainfo *ra = ralist;
+ int on;
+ char *rtr_str;
+ /* XXX: should be max MTU attached to the node */
+ static u_char answer[1500];
+ static u_char sndcmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int))];
+
+ if ((*sockp = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
+ syslog(LOG_ERR, "<%s> socket: %s", __FUNCTION__,
+ strerror(errno));
+ exit(1);
+ }
+
+ /* specify to tell receiving interface */
+ on = 1;
+ if (setsockopt(*sockp, IPPROTO_IPV6, IPV6_PKTINFO, &on,
+ sizeof(on)) < 0) {
+ syslog(LOG_ERR, "<%s> IPV6_PKTINFO: %s",
+ __FUNCTION__, strerror(errno));
+ exit(1);
+ }
+
+ on = 1;
+ /* specify to tell value of hoplimit field of received IP6 hdr */
+ if (setsockopt(*sockp, IPPROTO_IPV6, IPV6_HOPLIMIT, &on,
+ sizeof(on)) < 0) {
+ syslog(LOG_ERR, "<%s> IPV6_HOPLIMIT: %s",
+ __FUNCTION__, strerror(errno));
+ exit(1);
+ }
+
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ if (is_rr)
+ ICMP6_FILTER_SETPASS(ICMP6_ROUTER_RENUMBERING, &filt);
+ else {
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ }
+ if (setsockopt(*sockp, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) < 0) {
+ syslog(LOG_ERR, "<%s> IICMP6_FILTER: %s",
+ __FUNCTION__, strerror(errno));
+ exit(1);
+ }
+
+
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ if (is_rr && policy != NULL) {
+ char *buf;
+
+ buf = ipsec_set_policy(policy, strlen(policy));
+ if (buf == NULL)
+ errx(EX_CONFIG, ipsec_strerror());
+ if (setsockopt(*sockp, IPPROTO_IPV6, IPV6_IPSEC_POLICY,
+ buf, ipsec_get_policylen(buf)) < 0)
+ err(EX_CONFIG, "ipsec policy cannot be configured");
+ free(buf);
+ }
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+
+ /*
+ * join all routers multicast address on each advertising interface.
+ */
+ rtr_str = is_rr ? ALLSITEROUTERS : ALLROUTERS;
+ if (inet_pton(AF_INET6, rtr_str, &mreq.ipv6mr_multiaddr.s6_addr)
+ != 1) {
+ syslog(LOG_ERR, "<%s> inet_pton for %s failed(library bug?)",
+ __FUNCTION__, rtr_str);
+ exit(1);
+ }
+ while(ra) {
+ mreq.ipv6mr_interface = ra->ifindex;
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq,
+ sizeof(mreq)) < 0) {
+ syslog(LOG_ERR, "<%s> IPV6_JOIN_GROUP for %s on"
+ "%s: %s", __FUNCTION__,
+ rtr_str, ra->ifname, strerror(errno));
+ exit(1);
+ }
+ ra = ra->next;
+ }
+ if (is_rr) /* msghdrs should have been alreadey initialized. */
+ return;
+
+ /* initialize msghdr for receiving packets */
+ rcviov[0].iov_base = (caddr_t)answer;
+ rcviov[0].iov_len = sizeof(answer);
+ rcvmhdr.msg_name = (caddr_t)&from;
+ rcvmhdr.msg_namelen = sizeof(from);
+ rcvmhdr.msg_iov = rcviov;
+ rcvmhdr.msg_iovlen = 1;
+ rcvmhdr.msg_control = (caddr_t) rcvcmsgbuf;
+ rcvmhdr.msg_controllen = sizeof(rcvcmsgbuf);
+
+ /* initialize msghdr for sending packets */
+ sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndmhdr.msg_iov = sndiov;
+ sndmhdr.msg_iovlen = 1;
+ sndmhdr.msg_control = (caddr_t)sndcmsgbuf;
+ sndmhdr.msg_controllen = sizeof(sndcmsgbuf);
+
+ return;
+}
+
+/* open a routing socket to watch the routing table */
+static void
+rtsock_open()
+{
+ if ((rtsock = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) {
+ syslog(LOG_ERR,
+ "<%s> socket: %s", __FUNCTION__, strerror(errno));
+ exit(1);
+ }
+}
+
+static struct rainfo *
+if_indextorainfo(int index)
+{
+ struct rainfo *rai = ralist;
+
+ for (rai = ralist; rai; rai = rai->next) {
+ if (rai->ifindex == index)
+ return(rai);
+ }
+
+ return(NULL); /* search failed */
+}
+
+static void
+ra_output(rainfo)
+struct rainfo *rainfo;
+{
+ int i;
+
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi;
+
+ sndmhdr.msg_name = (caddr_t)&sin6_allnodes;
+ sndmhdr.msg_iov[0].iov_base = (caddr_t)rainfo->ra_data;
+ sndmhdr.msg_iov[0].iov_len = rainfo->ra_datalen;
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+ /* specify the outgoing interface */
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ pi = (struct in6_pktinfo *)CMSG_DATA(cm);
+ memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); /*XXX*/
+ pi->ipi6_ifindex = rainfo->ifindex;
+
+ /* specify the hop limit of the packet */
+ {
+ int hoplimit = 255;
+
+ cm = CMSG_NXTHDR(&sndmhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));
+ }
+
+ syslog(LOG_DEBUG,
+ "<%s> send RA on %s, # of waitings = %d",
+ __FUNCTION__, rainfo->ifname, rainfo->waiting);
+
+ i = sendmsg(sock, &sndmhdr, 0);
+
+ if (i < 0 || i != rainfo->ra_datalen) {
+ if (i < 0) {
+ syslog(LOG_ERR, "<%s> sendmsg: %s",
+ __FUNCTION__, strerror(errno));
+ }
+ }
+
+ /* update counter */
+ if (rainfo->initcounter < MAX_INITIAL_RTR_ADVERTISEMENTS)
+ rainfo->initcounter++;
+
+ /* update timestamp */
+ gettimeofday(&rainfo->lastsent, NULL);
+
+ /* reset waiting conter */
+ rainfo->waiting = 0;
+}
+
+/* process RA timer */
+void
+ra_timeout(void *data)
+{
+ struct rainfo *rai = (struct rainfo *)data;
+
+#ifdef notyet
+ /* if necessary, reconstruct the packet. */
+#endif
+
+ syslog(LOG_DEBUG,
+ "<%s> RA timer on %s is expired",
+ __FUNCTION__, rai->ifname);
+
+ if (iflist[rai->ifindex]->ifm_flags & IFF_UP)
+ ra_output(rai);
+ else
+ syslog(LOG_DEBUG, "<%s> %s is not up, skip sending RA",
+ __FUNCTION__, rai->ifname);
+}
+
+/* update RA timer */
+void
+ra_timer_update(void *data, struct timeval *tm)
+{
+ struct rainfo *rai = (struct rainfo *)data;
+ long interval;
+
+ /*
+ * Whenever a multicast advertisement is sent from an interface,
+ * the timer is reset to a uniformly-distributed random value
+ * between the interface's configured MinRtrAdvInterval and
+ * MaxRtrAdvInterval(discovery-v2-02 6.2.4).
+ */
+ interval = rai->mininterval;
+ interval += random() % (rai->maxinterval - rai->mininterval);
+
+ /*
+ * For the first few advertisements (up to
+ * MAX_INITIAL_RTR_ADVERTISEMENTS), if the randomly chosen interval
+ * is greater than MAX_INITIAL_RTR_ADVERT_INTERVAL, the timer
+ * SHOULD be set to MAX_INITIAL_RTR_ADVERT_INTERVAL instead.
+ * (RFC-2461 6.2.4)
+ */
+ if (rai->initcounter < MAX_INITIAL_RTR_ADVERTISEMENTS &&
+ interval > MAX_INITIAL_RTR_ADVERT_INTERVAL)
+ interval = MAX_INITIAL_RTR_ADVERT_INTERVAL;
+
+ tm->tv_sec = interval;
+ tm->tv_usec = 0;
+
+ syslog(LOG_DEBUG,
+ "<%s> RA timer on %s is set to %ld:%ld",
+ __FUNCTION__, rai->ifname, tm->tv_sec, tm->tv_usec);
+
+ return;
+}