/* $KAME: route6d.c,v 1.104 2003/10/31 00:30:20 itojun Exp $ */ /*- * SPDX-License-Identifier: BSD-3-Clause * * 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. */ #ifndef lint static const char _rcsid[] = "$KAME: route6d.c,v 1.104 2003/10/31 00:30:20 itojun Exp $"; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_POLL_H #include #endif #include #include #include #include #include #include #include #include #include #include "route6d.h" #define MAXFILTER 40 #define RT_DUMP_MAXRETRY 15 #ifdef DEBUG #define INIT_INTERVAL6 6 #else #define INIT_INTERVAL6 10 /* Wait to submit an initial riprequest */ #endif /* alignment constraint for routing socket */ #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) struct ifc { /* Configuration of an interface */ TAILQ_ENTRY(ifc) ifc_next; char ifc_name[IFNAMSIZ]; /* if name */ int ifc_index; /* if index */ int ifc_mtu; /* if mtu */ int ifc_metric; /* if metric */ u_int ifc_flags; /* flags */ short ifc_cflags; /* IFC_XXX */ struct in6_addr ifc_mylladdr; /* my link-local address */ struct sockaddr_in6 ifc_ripsin; /* rip multicast address */ TAILQ_HEAD(, ifac) ifc_ifac_head; /* list of AF_INET6 addrs */ TAILQ_HEAD(, iff) ifc_iff_head; /* list of filters */ int ifc_joined; /* joined to ff02::9 */ }; static TAILQ_HEAD(, ifc) ifc_head = TAILQ_HEAD_INITIALIZER(ifc_head); struct ifac { /* Address associated to an interface */ TAILQ_ENTRY(ifac) ifac_next; struct ifc *ifac_ifc; /* back pointer */ struct in6_addr ifac_addr; /* address */ struct in6_addr ifac_raddr; /* remote address, valid in p2p */ int ifac_scope_id; /* scope id */ int ifac_plen; /* prefix length */ }; struct iff { /* Filters for an interface */ TAILQ_ENTRY(iff) iff_next; int iff_type; struct in6_addr iff_addr; int iff_plen; }; static struct ifc **index2ifc; static unsigned int nindex2ifc; static struct ifc *loopifcp = NULL; /* pointing to loopback */ #ifdef HAVE_POLL_H static struct pollfd set[2]; #else static fd_set *sockvecp; /* vector to select() for receiving */ static fd_set *recvecp; static int fdmasks; static int maxfd; /* maximum fd for select() */ #endif static int rtsock; /* the routing socket */ static int ripsock; /* socket to send/receive RIP datagram */ static struct rip6 *ripbuf; /* packet buffer for sending */ /* * Maintain the routes in a linked list. When the number of the routes * grows, somebody would like to introduce a hash based or a radix tree * based structure. I believe the number of routes handled by RIP is * limited and I don't have to manage a complex data structure, however. * * One of the major drawbacks of the linear linked list is the difficulty * of representing the relationship between a couple of routes. This may * be a significant problem when we have to support route aggregation with * suppressing the specifics covered by the aggregate. */ struct riprt { TAILQ_ENTRY(riprt) rrt_next; /* next destination */ struct riprt *rrt_same; /* same destination - future use */ struct netinfo6 rrt_info; /* network info */ struct in6_addr rrt_gw; /* gateway */ u_long rrt_flags; /* kernel routing table flags */ u_long rrt_rflags; /* route6d routing table flags */ time_t rrt_t; /* when the route validated */ int rrt_index; /* ifindex from which this route got */ }; static TAILQ_HEAD(, riprt) riprt_head = TAILQ_HEAD_INITIALIZER(riprt_head); static int dflag = 0; /* debug flag */ static int qflag = 0; /* quiet flag */ static int nflag = 0; /* don't update kernel routing table */ static int aflag = 0; /* age out even the statically defined routes */ static int hflag = 0; /* don't split horizon */ static int lflag = 0; /* exchange site local routes */ static int Pflag = 0; /* don't age out routes with RTF_PROTO[123] */ static int Qflag = RTF_PROTO2; /* set RTF_PROTO[123] flag to routes by RIPng */ static int sflag = 0; /* announce static routes w/ split horizon */ static int Sflag = 0; /* announce static routes to every interface */ static unsigned long routetag = 0; /* route tag attached on originating case */ static char *filter[MAXFILTER]; static int filtertype[MAXFILTER]; static int nfilter = 0; static pid_t pid; static struct sockaddr_storage ripsin; static int interval = 1; static time_t nextalarm = 0; #if 0 static time_t sup_trig_update = 0; #endif static FILE *rtlog = NULL; static int logopened = 0; static int seq = 0; static volatile sig_atomic_t seenalrm; static volatile sig_atomic_t seenquit; static volatile sig_atomic_t seenusr1; #define RRTF_AGGREGATE 0x08000000 #define RRTF_NOADVERTISE 0x10000000 #define RRTF_NH_NOT_LLADDR 0x20000000 #define RRTF_SENDANYWAY 0x40000000 #define RRTF_CHANGED 0x80000000 static void sighandler(int); static void ripalarm(void); static void riprecv(void); static void ripsend(struct ifc *, struct sockaddr_in6 *, int); static int out_filter(struct riprt *, struct ifc *); static void init(void); static void ifconfig(void); static int ifconfig1(const char *, const struct sockaddr *, struct ifc *, int); static void rtrecv(void); static int rt_del(const struct sockaddr_in6 *, const struct sockaddr_in6 *, const struct sockaddr_in6 *); static int rt_deladdr(struct ifc *, const struct sockaddr_in6 *, const struct sockaddr_in6 *); static void filterconfig(void); static int getifmtu(int); static const char *rttypes(struct rt_msghdr *); static const char *rtflags(struct rt_msghdr *); static const char *ifflags(int); static int ifrt(struct ifc *, int); static void ifrt_p2p(struct ifc *, int); static void applyplen(struct in6_addr *, int); static void ifrtdump(int); static void ifdump(int); static void ifdump0(FILE *, const struct ifc *); static void ifremove(int); static void rtdump(int); static void rt_entry(struct rt_msghdr *, int); static void rtdexit(void); static void riprequest(struct ifc *, struct netinfo6 *, int, struct sockaddr_in6 *); static void ripflush(struct ifc *, struct sockaddr_in6 *, int, struct netinfo6 *np); static void sendrequest(struct ifc *); static int sin6mask2len(const struct sockaddr_in6 *); static int mask2len(const struct in6_addr *, int); static int sendpacket(struct sockaddr_in6 *, int); static int addroute(struct riprt *, const struct in6_addr *, struct ifc *); static int delroute(struct netinfo6 *, struct in6_addr *); #if 0 static struct in6_addr *getroute(struct netinfo6 *, struct in6_addr *); #endif static void krtread(int); static int tobeadv(struct riprt *, struct ifc *); static char *allocopy(char *); static char *hms(void); static const char *inet6_n2p(const struct in6_addr *); static struct ifac *ifa_match(const struct ifc *, const struct in6_addr *, int); static struct in6_addr *plen2mask(int); static struct riprt *rtsearch(struct netinfo6 *); static int ripinterval(int); #if 0 static time_t ripsuptrig(void); #endif static void fatal(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); static void trace(int, const char *, ...) __attribute__((__format__(__printf__, 2, 3))); static void tracet(int, const char *, ...) __attribute__((__format__(__printf__, 2, 3))); static struct ifc *ifc_find(char *); static struct iff *iff_find(struct ifc *, int); static void setindex2ifc(int, struct ifc *); #define MALLOC(type) ((type *)malloc(sizeof(type))) #define IFIL_TYPE_ANY 0x0 #define IFIL_TYPE_A 'A' #define IFIL_TYPE_N 'N' #define IFIL_TYPE_T 'T' #define IFIL_TYPE_O 'O' #define IFIL_TYPE_L 'L' int main(int argc, char *argv[]) { int ch; int error = 0; unsigned long proto; struct ifc *ifcp; sigset_t mask, omask; const char *pidfile = ROUTE6D_PID; FILE *pidfh; char *progname; char *ep; progname = strrchr(*argv, '/'); if (progname) progname++; else progname = *argv; pid = getpid(); while ((ch = getopt(argc, argv, "A:N:O:R:T:L:t:adDhlnp:P:Q:qsS")) != -1) { switch (ch) { case 'A': case 'N': case 'O': case 'T': case 'L': if (nfilter >= MAXFILTER) { fatal("Exceeds MAXFILTER"); /*NOTREACHED*/ } filtertype[nfilter] = ch; filter[nfilter++] = allocopy(optarg); break; case 't': ep = NULL; routetag = strtoul(optarg, &ep, 0); if (!ep || *ep != '\0' || (routetag & ~0xffff) != 0) { fatal("invalid route tag"); /*NOTREACHED*/ } break; case 'p': pidfile = optarg; break; case 'P': ep = NULL; proto = strtoul(optarg, &ep, 0); if (!ep || *ep != '\0' || 3 < proto) { fatal("invalid P flag"); /*NOTREACHED*/ } if (proto == 0) Pflag = 0; if (proto == 1) Pflag |= RTF_PROTO1; if (proto == 2) Pflag |= RTF_PROTO2; if (proto == 3) Pflag |= RTF_PROTO3; break; case 'Q': ep = NULL; proto = strtoul(optarg, &ep, 0); if (!ep || *ep != '\0' || 3 < proto) { fatal("invalid Q flag"); /*NOTREACHED*/ } if (proto == 0) Qflag = 0; if (proto == 1) Qflag |= RTF_PROTO1; if (proto == 2) Qflag |= RTF_PROTO2; if (proto == 3) Qflag |= RTF_PROTO3; break; case 'R': if ((rtlog = fopen(optarg, "w")) == NULL) { fatal("Can not write to routelog"); /*NOTREACHED*/ } break; #define FLAG(c, flag, n) case c: do { flag = n; break; } while(0) FLAG('a', aflag, 1); break; FLAG('d', dflag, 1); break; FLAG('D', dflag, 2); break; FLAG('h', hflag, 1); break; FLAG('l', lflag, 1); break; FLAG('n', nflag, 1); break; FLAG('q', qflag, 1); break; FLAG('s', sflag, 1); break; FLAG('S', Sflag, 1); break; #undef FLAG default: fatal("Invalid option specified, terminating"); /*NOTREACHED*/ } } argc -= optind; argv += optind; if (argc > 0) { fatal("bogus extra arguments"); /*NOTREACHED*/ } if (geteuid()) { nflag = 1; fprintf(stderr, "No kernel update is allowed\n"); } if (dflag == 0) { if (daemon(0, 0) < 0) { fatal("daemon"); /*NOTREACHED*/ } } openlog(progname, LOG_NDELAY|LOG_PID, LOG_DAEMON); logopened++; if ((ripbuf = (struct rip6 *)malloc(RIP6_MAXMTU)) == NULL) fatal("malloc"); memset(ripbuf, 0, RIP6_MAXMTU); ripbuf->rip6_cmd = RIP6_RESPONSE; ripbuf->rip6_vers = RIP6_VERSION; ripbuf->rip6_res1[0] = 0; ripbuf->rip6_res1[1] = 0; init(); ifconfig(); TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if (ifcp->ifc_index < 0) { fprintf(stderr, "No ifindex found at %s " "(no link-local address?)\n", ifcp->ifc_name); error++; } } if (error) exit(1); if (loopifcp == NULL) { fatal("No loopback found"); /*NOTREACHED*/ } TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { ifrt(ifcp, 0); } filterconfig(); krtread(0); if (dflag) ifrtdump(0); pid = getpid(); if ((pidfh = fopen(pidfile, "w")) != NULL) { fprintf(pidfh, "%d\n", pid); fclose(pidfh); } if ((ripbuf = (struct rip6 *)malloc(RIP6_MAXMTU)) == NULL) { fatal("malloc"); /*NOTREACHED*/ } memset(ripbuf, 0, RIP6_MAXMTU); ripbuf->rip6_cmd = RIP6_RESPONSE; ripbuf->rip6_vers = RIP6_VERSION; ripbuf->rip6_res1[0] = 0; ripbuf->rip6_res1[1] = 0; if (signal(SIGALRM, sighandler) == SIG_ERR || signal(SIGQUIT, sighandler) == SIG_ERR || signal(SIGTERM, sighandler) == SIG_ERR || signal(SIGUSR1, sighandler) == SIG_ERR || signal(SIGHUP, sighandler) == SIG_ERR || signal(SIGINT, sighandler) == SIG_ERR) { fatal("signal"); /*NOTREACHED*/ } /* * To avoid rip packet congestion (not on a cable but in this * process), wait for a moment to send the first RIP6_RESPONSE * packets. */ alarm(ripinterval(INIT_INTERVAL6)); TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if (iff_find(ifcp, IFIL_TYPE_N) != NULL) continue; if (ifcp->ifc_index > 0 && (ifcp->ifc_flags & IFF_UP)) sendrequest(ifcp); } syslog(LOG_INFO, "**** Started ****"); sigemptyset(&mask); sigaddset(&mask, SIGALRM); while (1) { if (seenalrm) { ripalarm(); seenalrm = 0; continue; } if (seenquit) { rtdexit(); seenquit = 0; continue; } if (seenusr1) { ifrtdump(SIGUSR1); seenusr1 = 0; continue; } #ifdef HAVE_POLL_H switch (poll(set, 2, INFTIM)) #else memcpy(recvecp, sockvecp, fdmasks); switch (select(maxfd + 1, recvecp, 0, 0, 0)) #endif { case -1: if (errno != EINTR) { fatal("select"); /*NOTREACHED*/ } continue; case 0: continue; default: #ifdef HAVE_POLL_H if (set[0].revents & POLLIN) #else if (FD_ISSET(ripsock, recvecp)) #endif { sigprocmask(SIG_BLOCK, &mask, &omask); riprecv(); sigprocmask(SIG_SETMASK, &omask, NULL); } #ifdef HAVE_POLL_H if (set[1].revents & POLLIN) #else if (FD_ISSET(rtsock, recvecp)) #endif { sigprocmask(SIG_BLOCK, &mask, &omask); rtrecv(); sigprocmask(SIG_SETMASK, &omask, NULL); } } } } static void sighandler(int signo) { switch (signo) { case SIGALRM: seenalrm++; break; case SIGQUIT: case SIGTERM: seenquit++; break; case SIGUSR1: case SIGHUP: case SIGINT: seenusr1++; break; } } /* * gracefully exits after resetting sockopts. */ /* ARGSUSED */ static void rtdexit(void) { struct riprt *rrt; alarm(0); TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { if (rrt->rrt_rflags & RRTF_AGGREGATE) { delroute(&rrt->rrt_info, &rrt->rrt_gw); } } close(ripsock); close(rtsock); syslog(LOG_INFO, "**** Terminated ****"); closelog(); exit(1); } /* * Called periodically: * 1. age out the learned route. remove it if necessary. * 2. submit RIP6_RESPONSE packets. * Invoked in every SUPPLY_INTERVAL6 (30) seconds. I believe we don't have * to invoke this function in every 1 or 5 or 10 seconds only to age the * routes more precisely. */ /* ARGSUSED */ static void ripalarm(void) { struct ifc *ifcp; struct riprt *rrt, *rrt_tmp; time_t t_lifetime, t_holddown; /* age the RIP routes */ t_lifetime = time(NULL) - RIP_LIFETIME; t_holddown = t_lifetime - RIP_HOLDDOWN; TAILQ_FOREACH_SAFE(rrt, &riprt_head, rrt_next, rrt_tmp) { if (rrt->rrt_t == 0) continue; else if (rrt->rrt_t < t_holddown) { TAILQ_REMOVE(&riprt_head, rrt, rrt_next); delroute(&rrt->rrt_info, &rrt->rrt_gw); free(rrt); } else if (rrt->rrt_t < t_lifetime) rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6; } /* Supply updates */ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if (ifcp->ifc_index > 0 && (ifcp->ifc_flags & IFF_UP)) ripsend(ifcp, &ifcp->ifc_ripsin, 0); } alarm(ripinterval(SUPPLY_INTERVAL6)); } static void init(void) { int error; const int int0 = 0, int1 = 1, int255 = 255; struct addrinfo hints, *res; char port[NI_MAXSERV]; TAILQ_INIT(&ifc_head); nindex2ifc = 0; /*initial guess*/ index2ifc = NULL; snprintf(port, sizeof(port), "%u", RIP6_PORT); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; hints.ai_flags = AI_PASSIVE; error = getaddrinfo(NULL, port, &hints, &res); if (error) { fatal("%s", gai_strerror(error)); /*NOTREACHED*/ } if (res->ai_next) { fatal(":: resolved to multiple address"); /*NOTREACHED*/ } ripsock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (ripsock < 0) { fatal("rip socket"); /*NOTREACHED*/ } #ifdef IPV6_V6ONLY if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_V6ONLY, &int1, sizeof(int1)) < 0) { fatal("rip IPV6_V6ONLY"); /*NOTREACHED*/ } #endif if (bind(ripsock, res->ai_addr, res->ai_addrlen) < 0) { fatal("rip bind"); /*NOTREACHED*/ } if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &int255, sizeof(int255)) < 0) { fatal("rip IPV6_MULTICAST_HOPS"); /*NOTREACHED*/ } if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &int0, sizeof(int0)) < 0) { fatal("rip IPV6_MULTICAST_LOOP"); /*NOTREACHED*/ } #ifdef IPV6_RECVPKTINFO if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &int1, sizeof(int1)) < 0) { fatal("rip IPV6_RECVPKTINFO"); /*NOTREACHED*/ } #else /* old adv. API */ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_PKTINFO, &int1, sizeof(int1)) < 0) { fatal("rip IPV6_PKTINFO"); /*NOTREACHED*/ } #endif #ifdef IPV6_RECVPKTINFO if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &int1, sizeof(int1)) < 0) { fatal("rip IPV6_RECVHOPLIMIT"); /*NOTREACHED*/ } #else /* old adv. API */ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_HOPLIMIT, &int1, sizeof(int1)) < 0) { fatal("rip IPV6_HOPLIMIT"); /*NOTREACHED*/ } #endif freeaddrinfo(res); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; error = getaddrinfo(RIP6_DEST, port, &hints, &res); if (error) { fatal("%s", gai_strerror(error)); /*NOTREACHED*/ } if (res->ai_next) { fatal("%s resolved to multiple address", RIP6_DEST); /*NOTREACHED*/ } memcpy(&ripsin, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); #ifdef HAVE_POLL_H set[0].fd = ripsock; set[0].events = POLLIN; #else maxfd = ripsock; #endif if (nflag == 0) { if ((rtsock = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) { fatal("route socket"); /*NOTREACHED*/ } #ifdef HAVE_POLL_H set[1].fd = rtsock; set[1].events = POLLIN; #else if (rtsock > maxfd) maxfd = rtsock; #endif } else { #ifdef HAVE_POLL_H set[1].fd = -1; #else rtsock = -1; /*just for safety */ #endif } #ifndef HAVE_POLL_H fdmasks = howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask); if ((sockvecp = malloc(fdmasks)) == NULL) { fatal("malloc"); /*NOTREACHED*/ } if ((recvecp = malloc(fdmasks)) == NULL) { fatal("malloc"); /*NOTREACHED*/ } memset(sockvecp, 0, fdmasks); FD_SET(ripsock, sockvecp); if (rtsock >= 0) FD_SET(rtsock, sockvecp); #endif } #define RIPSIZE(n) \ (sizeof(struct rip6) + ((n)-1) * sizeof(struct netinfo6)) /* * ripflush flushes the rip datagram stored in the rip buffer */ static void ripflush(struct ifc *ifcp, struct sockaddr_in6 *sin6, int nrt, struct netinfo6 *np) { int i; int error; if (ifcp) tracet(1, "Send(%s): info(%d) to %s.%d\n", ifcp->ifc_name, nrt, inet6_n2p(&sin6->sin6_addr), ntohs(sin6->sin6_port)); else tracet(1, "Send: info(%d) to %s.%d\n", nrt, inet6_n2p(&sin6->sin6_addr), ntohs(sin6->sin6_port)); if (dflag >= 2) { np = ripbuf->rip6_nets; for (i = 0; i < nrt; i++, np++) { if (np->rip6_metric == NEXTHOP_METRIC) { if (IN6_IS_ADDR_UNSPECIFIED(&np->rip6_dest)) trace(2, " NextHop reset"); else { trace(2, " NextHop %s", inet6_n2p(&np->rip6_dest)); } } else { trace(2, " %s/%d[%d]", inet6_n2p(&np->rip6_dest), np->rip6_plen, np->rip6_metric); } if (np->rip6_tag) { trace(2, " tag=0x%04x", ntohs(np->rip6_tag) & 0xffff); } trace(2, "\n"); } } error = sendpacket(sin6, RIPSIZE(nrt)); if (error == EAFNOSUPPORT) { /* Protocol not supported */ if (ifcp != NULL) { tracet(1, "Could not send info to %s (%s): " "set IFF_UP to 0\n", ifcp->ifc_name, inet6_n2p(&ifcp->ifc_ripsin.sin6_addr)); /* As if down for AF_INET6 */ ifcp->ifc_flags &= ~IFF_UP; } else { tracet(1, "Could not send info to %s\n", inet6_n2p(&sin6->sin6_addr)); } } } /* * Generate RIP6_RESPONSE packets and send them. */ static void ripsend(struct ifc *ifcp, struct sockaddr_in6 *sin6, int flag) { struct riprt *rrt; struct in6_addr *nh; /* next hop */ struct netinfo6 *np; int maxrte; int nrt; if (qflag) return; if (ifcp == NULL) { /* * Request from non-link local address is not * a regular route6d update. */ maxrte = (IFMINMTU - sizeof(struct ip6_hdr) - sizeof(struct udphdr) - sizeof(struct rip6) + sizeof(struct netinfo6)) / sizeof(struct netinfo6); nh = NULL; nrt = 0; np = ripbuf->rip6_nets; TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { if (rrt->rrt_rflags & RRTF_NOADVERTISE) continue; /* Put the route to the buffer */ *np = rrt->rrt_info; np++; nrt++; if (nrt == maxrte) { ripflush(NULL, sin6, nrt, np); nh = NULL; nrt = 0; np = ripbuf->rip6_nets; } } if (nrt) /* Send last packet */ ripflush(NULL, sin6, nrt, np); return; } if ((flag & RRTF_SENDANYWAY) == 0 && (qflag || (ifcp->ifc_flags & IFF_LOOPBACK))) return; /* -N: no use */ if (iff_find(ifcp, IFIL_TYPE_N) != NULL) return; /* -T: generate default route only */ if (iff_find(ifcp, IFIL_TYPE_T) != NULL) { struct netinfo6 rrt_info; memset(&rrt_info, 0, sizeof(struct netinfo6)); rrt_info.rip6_dest = in6addr_any; rrt_info.rip6_plen = 0; rrt_info.rip6_metric = 1; rrt_info.rip6_metric += ifcp->ifc_metric; rrt_info.rip6_tag = htons(routetag & 0xffff); np = ripbuf->rip6_nets; *np = rrt_info; nrt = 1; ripflush(ifcp, sin6, nrt, np); return; } maxrte = (ifcp->ifc_mtu - sizeof(struct ip6_hdr) - sizeof(struct udphdr) - sizeof(struct rip6) + sizeof(struct netinfo6)) / sizeof(struct netinfo6); nrt = 0; np = ripbuf->rip6_nets; nh = NULL; TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { if (rrt->rrt_rflags & RRTF_NOADVERTISE) continue; /* Need to check filter here */ if (out_filter(rrt, ifcp) == 0) continue; /* Check split horizon and other conditions */ if (tobeadv(rrt, ifcp) == 0) continue; /* Only considers the routes with flag if specified */ if ((flag & RRTF_CHANGED) && (rrt->rrt_rflags & RRTF_CHANGED) == 0) continue; /* Check nexthop */ if (rrt->rrt_index == ifcp->ifc_index && !IN6_IS_ADDR_UNSPECIFIED(&rrt->rrt_gw) && (rrt->rrt_rflags & RRTF_NH_NOT_LLADDR) == 0) { if (nh == NULL || !IN6_ARE_ADDR_EQUAL(nh, &rrt->rrt_gw)) { if (nrt == maxrte - 2) { ripflush(ifcp, sin6, nrt, np); nh = NULL; nrt = 0; np = ripbuf->rip6_nets; } np->rip6_dest = rrt->rrt_gw; np->rip6_plen = 0; np->rip6_tag = 0; np->rip6_metric = NEXTHOP_METRIC; nh = &rrt->rrt_gw; np++; nrt++; } } else if (nh && (rrt->rrt_index != ifcp->ifc_index || !IN6_ARE_ADDR_EQUAL(nh, &rrt->rrt_gw) || rrt->rrt_rflags & RRTF_NH_NOT_LLADDR)) { /* Reset nexthop */ if (nrt == maxrte - 2) { ripflush(ifcp, sin6, nrt, np); nh = NULL; nrt = 0; np = ripbuf->rip6_nets; } memset(np, 0, sizeof(struct netinfo6)); np->rip6_metric = NEXTHOP_METRIC; nh = NULL; np++; nrt++; } /* Put the route to the buffer */ *np = rrt->rrt_info; np++; nrt++; if (nrt == maxrte) { ripflush(ifcp, sin6, nrt, np); nh = NULL; nrt = 0; np = ripbuf->rip6_nets; } } if (nrt) /* Send last packet */ ripflush(ifcp, sin6, nrt, np); } /* * outbound filter logic, per-route/interface. */ static int out_filter(struct riprt *rrt, struct ifc *ifcp) { struct iff *iffp; struct in6_addr ia; int ok; /* * -A: filter out less specific routes, if we have aggregated * route configured. */ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) { if (iffp->iff_type != 'A') continue; if (rrt->rrt_info.rip6_plen <= iffp->iff_plen) continue; ia = rrt->rrt_info.rip6_dest; applyplen(&ia, iffp->iff_plen); if (IN6_ARE_ADDR_EQUAL(&ia, &iffp->iff_addr)) return 0; } /* * if it is an aggregated route, advertise it only to the * interfaces specified on -A. */ if ((rrt->rrt_rflags & RRTF_AGGREGATE) != 0) { ok = 0; TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) { if (iffp->iff_type != 'A') continue; if (rrt->rrt_info.rip6_plen == iffp->iff_plen && IN6_ARE_ADDR_EQUAL(&rrt->rrt_info.rip6_dest, &iffp->iff_addr)) { ok = 1; break; } } if (!ok) return 0; } /* * -O: advertise only if prefix matches the configured prefix. */ if (iff_find(ifcp, IFIL_TYPE_O) != NULL) { ok = 0; TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) { if (iffp->iff_type != 'O') continue; if (rrt->rrt_info.rip6_plen < iffp->iff_plen) continue; ia = rrt->rrt_info.rip6_dest; applyplen(&ia, iffp->iff_plen); if (IN6_ARE_ADDR_EQUAL(&ia, &iffp->iff_addr)) { ok = 1; break; } } if (!ok) return 0; } /* the prefix should be advertised */ return 1; } /* * Determine if the route is to be advertised on the specified interface. * It checks options specified in the arguments and the split horizon rule. */ static int tobeadv(struct riprt *rrt, struct ifc *ifcp) { /* Special care for static routes */ if (rrt->rrt_flags & RTF_STATIC) { /* XXX don't advertise reject/blackhole routes */ if (rrt->rrt_flags & (RTF_REJECT | RTF_BLACKHOLE)) return 0; if (Sflag) /* Yes, advertise it anyway */ return 1; if (sflag && rrt->rrt_index != ifcp->ifc_index) return 1; return 0; } /* Regular split horizon */ if (hflag == 0 && rrt->rrt_index == ifcp->ifc_index) return 0; return 1; } /* * Send a rip packet actually. */ static int sendpacket(struct sockaddr_in6 *sin6, int len) { struct msghdr m; struct cmsghdr *cm; struct iovec iov[2]; struct in6_pktinfo *pi; u_char cmsgbuf[256]; int idx; struct sockaddr_in6 sincopy; /* do not overwrite the given sin */ sincopy = *sin6; sin6 = &sincopy; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) || IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) idx = sin6->sin6_scope_id; else idx = 0; m.msg_name = (caddr_t)sin6; m.msg_namelen = sizeof(*sin6); iov[0].iov_base = (caddr_t)ripbuf; iov[0].iov_len = len; m.msg_iov = iov; m.msg_iovlen = 1; m.msg_flags = 0; if (!idx) { m.msg_control = NULL; m.msg_controllen = 0; } else { memset(cmsgbuf, 0, sizeof(cmsgbuf)); cm = (struct cmsghdr *)(void *)cmsgbuf; m.msg_control = (caddr_t)cm; m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; pi = (struct in6_pktinfo *)(void *)CMSG_DATA(cm); memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); /*::*/ pi->ipi6_ifindex = idx; } if (sendmsg(ripsock, &m, 0 /*MSG_DONTROUTE*/) < 0) { trace(1, "sendmsg: %s\n", strerror(errno)); return errno; } return 0; } /* * Receive and process RIP packets. Update the routes/kernel forwarding * table if necessary. */ static void riprecv(void) { struct ifc *ifcp, *ic; struct sockaddr_in6 fsock; struct in6_addr nh; /* next hop */ struct rip6 *rp; struct netinfo6 *np, *nq; struct riprt *rrt; ssize_t len, nn; unsigned int need_trigger, idx; char buf[4 * RIP6_MAXMTU]; time_t t; struct msghdr m; struct cmsghdr *cm; struct iovec iov[2]; u_char cmsgbuf[256]; struct in6_pktinfo *pi = NULL; int *hlimp = NULL; struct iff *iffp; struct in6_addr ia; int ok; time_t t_half_lifetime; need_trigger = 0; m.msg_name = (caddr_t)&fsock; m.msg_namelen = sizeof(fsock); iov[0].iov_base = (caddr_t)buf; iov[0].iov_len = sizeof(buf); m.msg_iov = iov; m.msg_iovlen = 1; cm = (struct cmsghdr *)(void *)cmsgbuf; m.msg_control = (caddr_t)cm; m.msg_controllen = sizeof(cmsgbuf); m.msg_flags = 0; if ((len = recvmsg(ripsock, &m, 0)) < 0) { fatal("recvmsg"); /*NOTREACHED*/ } idx = 0; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&m); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(&m, cm)) { if (cm->cmsg_level != IPPROTO_IPV6) continue; switch (cm->cmsg_type) { case IPV6_PKTINFO: if (cm->cmsg_len != CMSG_LEN(sizeof(*pi))) { trace(1, "invalid cmsg length for IPV6_PKTINFO\n"); return; } pi = (struct in6_pktinfo *)(void *)CMSG_DATA(cm); idx = pi->ipi6_ifindex; break; case IPV6_HOPLIMIT: if (cm->cmsg_len != CMSG_LEN(sizeof(int))) { trace(1, "invalid cmsg length for IPV6_HOPLIMIT\n"); return; } hlimp = (int *)(void *)CMSG_DATA(cm); break; } } if ((size_t)len < sizeof(struct rip6)) { trace(1, "Packet too short\n"); return; } if (pi == NULL || hlimp == NULL) { /* * This can happen when the kernel failed to allocate memory * for the ancillary data. Although we might be able to handle * some cases without this info, those are minor and not so * important, so it's better to discard the packet for safer * operation. */ trace(1, "IPv6 packet information cannot be retrieved\n"); return; } nh = fsock.sin6_addr; nn = (len - sizeof(struct rip6) + sizeof(struct netinfo6)) / sizeof(struct netinfo6); rp = (struct rip6 *)(void *)buf; np = rp->rip6_nets; if (rp->rip6_vers != RIP6_VERSION) { trace(1, "Incorrect RIP version %d\n", rp->rip6_vers); return; } if (rp->rip6_cmd == RIP6_REQUEST) { if (idx && idx < nindex2ifc) { ifcp = index2ifc[idx]; riprequest(ifcp, np, nn, &fsock); } else { riprequest(NULL, np, nn, &fsock); } return; } if (!IN6_IS_ADDR_LINKLOCAL(&fsock.sin6_addr)) { trace(1, "Response from non-ll addr: %s\n", inet6_n2p(&fsock.sin6_addr)); return; /* Ignore packets from non-link-local addr */ } if (ntohs(fsock.sin6_port) != RIP6_PORT) { trace(1, "Response from non-rip port from %s\n", inet6_n2p(&fsock.sin6_addr)); return; } if (IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) && *hlimp != 255) { trace(1, "Response packet with a smaller hop limit (%d) from %s\n", *hlimp, inet6_n2p(&fsock.sin6_addr)); return; } /* * Further validation: since this program does not send off-link * requests, an incoming response must always come from an on-link * node. Although this is normally ensured by the source address * check above, it may not 100% be safe because there are router * implementations that (invalidly) allow a packet with a link-local * source address to be forwarded to a different link. * So we also check whether the destination address is a link-local * address or the hop limit is 255. Note that RFC2080 does not require * the specific hop limit for a unicast response, so we cannot assume * the limitation. */ if (!IN6_IS_ADDR_LINKLOCAL(&pi->ipi6_addr) && *hlimp != 255) { trace(1, "Response packet possibly from an off-link node: " "from %s to %s hlim=%d\n", inet6_n2p(&fsock.sin6_addr), inet6_n2p(&pi->ipi6_addr), *hlimp); return; } idx = fsock.sin6_scope_id; ifcp = (idx < nindex2ifc) ? index2ifc[idx] : NULL; if (!ifcp) { trace(1, "Packets to unknown interface index %d\n", idx); return; /* Ignore it */ } if (IN6_ARE_ADDR_EQUAL(&ifcp->ifc_mylladdr, &fsock.sin6_addr)) return; /* The packet is from me; ignore */ if (rp->rip6_cmd != RIP6_RESPONSE) { trace(1, "Invalid command %d\n", rp->rip6_cmd); return; } /* -N: no use */ if (iff_find(ifcp, IFIL_TYPE_N) != NULL) return; tracet(1, "Recv(%s): from %s.%d info(%zd)\n", ifcp->ifc_name, inet6_n2p(&nh), ntohs(fsock.sin6_port), nn); t = time(NULL); t_half_lifetime = t - (RIP_LIFETIME/2); for (; nn; nn--, np++) { if (np->rip6_metric == NEXTHOP_METRIC) { /* modify neighbor address */ if (IN6_IS_ADDR_LINKLOCAL(&np->rip6_dest)) { nh = np->rip6_dest; trace(1, "\tNexthop: %s\n", inet6_n2p(&nh)); } else if (IN6_IS_ADDR_UNSPECIFIED(&np->rip6_dest)) { nh = fsock.sin6_addr; trace(1, "\tNexthop: %s\n", inet6_n2p(&nh)); } else { nh = fsock.sin6_addr; trace(1, "\tInvalid Nexthop: %s\n", inet6_n2p(&np->rip6_dest)); } continue; } if (IN6_IS_ADDR_MULTICAST(&np->rip6_dest)) { trace(1, "\tMulticast netinfo6: %s/%d [%d]\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, np->rip6_metric); continue; } if (IN6_IS_ADDR_LOOPBACK(&np->rip6_dest)) { trace(1, "\tLoopback netinfo6: %s/%d [%d]\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, np->rip6_metric); continue; } if (IN6_IS_ADDR_LINKLOCAL(&np->rip6_dest)) { trace(1, "\tLink Local netinfo6: %s/%d [%d]\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, np->rip6_metric); continue; } /* may need to pass sitelocal prefix in some case, however*/ if (IN6_IS_ADDR_SITELOCAL(&np->rip6_dest) && !lflag) { trace(1, "\tSite Local netinfo6: %s/%d [%d]\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, np->rip6_metric); continue; } trace(2, "\tnetinfo6: %s/%d [%d]", inet6_n2p(&np->rip6_dest), np->rip6_plen, np->rip6_metric); if (np->rip6_tag) trace(2, " tag=0x%04x", ntohs(np->rip6_tag) & 0xffff); if (dflag >= 2) { ia = np->rip6_dest; applyplen(&ia, np->rip6_plen); if (!IN6_ARE_ADDR_EQUAL(&ia, &np->rip6_dest)) trace(2, " [junk outside prefix]"); } /* * -L: listen only if the prefix matches the configuration */ ok = 1; /* if there's no L filter, it is ok */ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) { if (iffp->iff_type != IFIL_TYPE_L) continue; ok = 0; if (np->rip6_plen < iffp->iff_plen) continue; /* special rule: ::/0 means default, not "in /0" */ if (iffp->iff_plen == 0 && np->rip6_plen > 0) continue; ia = np->rip6_dest; applyplen(&ia, iffp->iff_plen); if (IN6_ARE_ADDR_EQUAL(&ia, &iffp->iff_addr)) { ok = 1; break; } } if (!ok) { trace(2, " (filtered)\n"); continue; } trace(2, "\n"); np->rip6_metric++; np->rip6_metric += ifcp->ifc_metric; if (np->rip6_metric > HOPCNT_INFINITY6) np->rip6_metric = HOPCNT_INFINITY6; applyplen(&np->rip6_dest, np->rip6_plen); if ((rrt = rtsearch(np)) != NULL) { if (rrt->rrt_t == 0) continue; /* Intf route has priority */ nq = &rrt->rrt_info; if (nq->rip6_metric > np->rip6_metric) { if (rrt->rrt_index == ifcp->ifc_index && IN6_ARE_ADDR_EQUAL(&nh, &rrt->rrt_gw)) { /* Small metric from the same gateway */ nq->rip6_metric = np->rip6_metric; } else { /* Better route found */ rrt->rrt_index = ifcp->ifc_index; /* Update routing table */ delroute(nq, &rrt->rrt_gw); rrt->rrt_gw = nh; *nq = *np; addroute(rrt, &nh, ifcp); } rrt->rrt_rflags |= RRTF_CHANGED; rrt->rrt_t = t; need_trigger = 1; } else if (nq->rip6_metric < np->rip6_metric && rrt->rrt_index == ifcp->ifc_index && IN6_ARE_ADDR_EQUAL(&nh, &rrt->rrt_gw)) { /* Got worse route from same gw */ nq->rip6_metric = np->rip6_metric; rrt->rrt_t = t; rrt->rrt_rflags |= RRTF_CHANGED; need_trigger = 1; } else if (nq->rip6_metric == np->rip6_metric && np->rip6_metric < HOPCNT_INFINITY6) { if (rrt->rrt_index == ifcp->ifc_index && IN6_ARE_ADDR_EQUAL(&nh, &rrt->rrt_gw)) { /* same metric, same route from same gw */ rrt->rrt_t = t; } else if (rrt->rrt_t < t_half_lifetime) { /* Better route found */ rrt->rrt_index = ifcp->ifc_index; /* Update routing table */ delroute(nq, &rrt->rrt_gw); rrt->rrt_gw = nh; *nq = *np; addroute(rrt, &nh, ifcp); rrt->rrt_rflags |= RRTF_CHANGED; rrt->rrt_t = t; } } /* * if nq->rip6_metric == HOPCNT_INFINITY6 then * do not update age value. Do nothing. */ } else if (np->rip6_metric < HOPCNT_INFINITY6) { /* Got a new valid route */ if ((rrt = MALLOC(struct riprt)) == NULL) { fatal("malloc: struct riprt"); /*NOTREACHED*/ } memset(rrt, 0, sizeof(*rrt)); nq = &rrt->rrt_info; rrt->rrt_same = NULL; rrt->rrt_index = ifcp->ifc_index; rrt->rrt_flags = RTF_UP|RTF_GATEWAY; rrt->rrt_gw = nh; *nq = *np; applyplen(&nq->rip6_dest, nq->rip6_plen); if (nq->rip6_plen == sizeof(struct in6_addr) * 8) rrt->rrt_flags |= RTF_HOST; /* Update routing table */ addroute(rrt, &nh, ifcp); rrt->rrt_rflags |= RRTF_CHANGED; need_trigger = 1; rrt->rrt_t = t; /* Put the route to the list */ TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next); } } /* XXX need to care the interval between triggered updates */ if (need_trigger) { if (nextalarm > time(NULL) + RIP_TRIG_INT6_MAX) { TAILQ_FOREACH(ic, &ifc_head, ifc_next) { if (ifcp->ifc_index == ic->ifc_index) continue; if (ic->ifc_flags & IFF_UP) ripsend(ic, &ic->ifc_ripsin, RRTF_CHANGED); } } /* Reset the flag */ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { rrt->rrt_rflags &= ~RRTF_CHANGED; } } } /* * Send all routes request packet to the specified interface. */ static void sendrequest(struct ifc *ifcp) { struct netinfo6 *np; int error; if (ifcp->ifc_flags & IFF_LOOPBACK) return; ripbuf->rip6_cmd = RIP6_REQUEST; np = ripbuf->rip6_nets; memset(np, 0, sizeof(struct netinfo6)); np->rip6_metric = HOPCNT_INFINITY6; tracet(1, "Send rtdump Request to %s (%s)\n", ifcp->ifc_name, inet6_n2p(&ifcp->ifc_ripsin.sin6_addr)); error = sendpacket(&ifcp->ifc_ripsin, RIPSIZE(1)); if (error == EAFNOSUPPORT) { /* Protocol not supported */ tracet(1, "Could not send rtdump Request to %s (%s): " "set IFF_UP to 0\n", ifcp->ifc_name, inet6_n2p(&ifcp->ifc_ripsin.sin6_addr)); ifcp->ifc_flags &= ~IFF_UP; /* As if down for AF_INET6 */ } ripbuf->rip6_cmd = RIP6_RESPONSE; } /* * Process a RIP6_REQUEST packet. */ static void riprequest(struct ifc *ifcp, struct netinfo6 *np, int nn, struct sockaddr_in6 *sin6) { int i; struct riprt *rrt; if (!(nn == 1 && IN6_IS_ADDR_UNSPECIFIED(&np->rip6_dest) && np->rip6_plen == 0 && np->rip6_metric == HOPCNT_INFINITY6)) { /* Specific response, don't split-horizon */ trace(1, "\tRIP Request\n"); for (i = 0; i < nn; i++, np++) { rrt = rtsearch(np); if (rrt) np->rip6_metric = rrt->rrt_info.rip6_metric; else np->rip6_metric = HOPCNT_INFINITY6; } (void)sendpacket(sin6, RIPSIZE(nn)); return; } /* Whole routing table dump */ trace(1, "\tRIP Request -- whole routing table\n"); ripsend(ifcp, sin6, RRTF_SENDANYWAY); } /* * Get information of each interface. */ static void ifconfig(void) { struct ifaddrs *ifap, *ifa; struct ifc *ifcp; struct ipv6_mreq mreq; int s; if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { fatal("socket"); /*NOTREACHED*/ } if (getifaddrs(&ifap) != 0) { fatal("getifaddrs"); /*NOTREACHED*/ } for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; ifcp = ifc_find(ifa->ifa_name); /* we are interested in multicast-capable interfaces */ if ((ifa->ifa_flags & IFF_MULTICAST) == 0) continue; if (!ifcp) { /* new interface */ if ((ifcp = MALLOC(struct ifc)) == NULL) { fatal("malloc: struct ifc"); /*NOTREACHED*/ } memset(ifcp, 0, sizeof(*ifcp)); ifcp->ifc_index = -1; strlcpy(ifcp->ifc_name, ifa->ifa_name, sizeof(ifcp->ifc_name)); TAILQ_INIT(&ifcp->ifc_ifac_head); TAILQ_INIT(&ifcp->ifc_iff_head); ifcp->ifc_flags = ifa->ifa_flags; TAILQ_INSERT_HEAD(&ifc_head, ifcp, ifc_next); trace(1, "newif %s <%s>\n", ifcp->ifc_name, ifflags(ifcp->ifc_flags)); if (!strcmp(ifcp->ifc_name, LOOPBACK_IF)) loopifcp = ifcp; } else { /* update flag, this may be up again */ if (ifcp->ifc_flags != ifa->ifa_flags) { trace(1, "%s: <%s> -> ", ifcp->ifc_name, ifflags(ifcp->ifc_flags)); trace(1, "<%s>\n", ifflags(ifa->ifa_flags)); ifcp->ifc_cflags |= IFC_CHANGED; } ifcp->ifc_flags = ifa->ifa_flags; } if (ifconfig1(ifa->ifa_name, ifa->ifa_addr, ifcp, s) < 0) { /* maybe temporary failure */ continue; } if ((ifcp->ifc_flags & (IFF_LOOPBACK | IFF_UP)) == IFF_UP && 0 < ifcp->ifc_index && !ifcp->ifc_joined) { mreq.ipv6mr_multiaddr = ifcp->ifc_ripsin.sin6_addr; mreq.ipv6mr_interface = ifcp->ifc_index; if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) { fatal("IPV6_JOIN_GROUP"); /*NOTREACHED*/ } trace(1, "join %s %s\n", ifcp->ifc_name, RIP6_DEST); ifcp->ifc_joined++; } } close(s); freeifaddrs(ifap); } static int ifconfig1(const char *name, const struct sockaddr *sa, struct ifc *ifcp, int s) { struct in6_ifreq ifr; const struct sockaddr_in6 *sin6; struct ifac *ifac; int plen; char buf[BUFSIZ]; sin6 = (const struct sockaddr_in6 *)(const void *)sa; if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) && !lflag) return (-1); ifr.ifr_addr = *sin6; strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); if (ioctl(s, SIOCGIFNETMASK_IN6, (char *)&ifr) < 0) { syslog(LOG_INFO, "ioctl: SIOCGIFNETMASK_IN6"); return (-1); } plen = sin6mask2len(&ifr.ifr_addr); if ((ifac = ifa_match(ifcp, &sin6->sin6_addr, plen)) != NULL) { /* same interface found */ /* need check if something changed */ /* XXX not yet implemented */ return (-1); } /* * New address is found */ if ((ifac = MALLOC(struct ifac)) == NULL) { fatal("malloc: struct ifac"); /*NOTREACHED*/ } memset(ifac, 0, sizeof(*ifac)); ifac->ifac_ifc = ifcp; ifac->ifac_addr = sin6->sin6_addr; ifac->ifac_plen = plen; ifac->ifac_scope_id = sin6->sin6_scope_id; if (ifcp->ifc_flags & IFF_POINTOPOINT) { ifr.ifr_addr = *sin6; if (ioctl(s, SIOCGIFDSTADDR_IN6, (char *)&ifr) < 0) { fatal("ioctl: SIOCGIFDSTADDR_IN6"); /*NOTREACHED*/ } ifac->ifac_raddr = ifr.ifr_dstaddr.sin6_addr; inet_ntop(AF_INET6, (void *)&ifac->ifac_raddr, buf, sizeof(buf)); trace(1, "found address %s/%d -- %s\n", inet6_n2p(&ifac->ifac_addr), ifac->ifac_plen, buf); } else { trace(1, "found address %s/%d\n", inet6_n2p(&ifac->ifac_addr), ifac->ifac_plen); } if (ifcp->ifc_index < 0 && IN6_IS_ADDR_LINKLOCAL(&ifac->ifac_addr)) { ifcp->ifc_mylladdr = ifac->ifac_addr; ifcp->ifc_index = ifac->ifac_scope_id; memcpy(&ifcp->ifc_ripsin, &ripsin, ripsin.ss_len); ifcp->ifc_ripsin.sin6_scope_id = ifcp->ifc_index; setindex2ifc(ifcp->ifc_index, ifcp); ifcp->ifc_mtu = getifmtu(ifcp->ifc_index); if (ifcp->ifc_mtu > RIP6_MAXMTU) ifcp->ifc_mtu = RIP6_MAXMTU; if (ioctl(s, SIOCGIFMETRIC, (char *)&ifr) < 0) { fatal("ioctl: SIOCGIFMETRIC"); /*NOTREACHED*/ } ifcp->ifc_metric = ifr.ifr_metric; trace(1, "\tindex: %d, mtu: %d, metric: %d\n", ifcp->ifc_index, ifcp->ifc_mtu, ifcp->ifc_metric); } else ifcp->ifc_cflags |= IFC_CHANGED; TAILQ_INSERT_HEAD(&ifcp->ifc_ifac_head, ifac, ifac_next); return 0; } static void ifremove(int ifindex) { struct ifc *ifcp; struct riprt *rrt; TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if (ifcp->ifc_index == ifindex) break; } if (ifcp == NULL) return; tracet(1, "ifremove: %s is departed.\n", ifcp->ifc_name); TAILQ_REMOVE(&ifc_head, ifcp, ifc_next); TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { if (rrt->rrt_index == ifcp->ifc_index && rrt->rrt_rflags & RRTF_AGGREGATE) delroute(&rrt->rrt_info, &rrt->rrt_gw); } free(ifcp); } /* * Receive and process routing messages. * Update interface information as necesssary. */ static void rtrecv(void) { char buf[BUFSIZ]; char *p, *q = NULL; struct rt_msghdr *rtm; struct ifa_msghdr *ifam; struct if_msghdr *ifm; struct if_announcemsghdr *ifan; int len; struct ifc *ifcp, *ic; int iface = 0, rtable = 0; struct sockaddr_in6 *rta[RTAX_MAX]; struct sockaddr_in6 mask; int i, addrs = 0; struct riprt *rrt; if ((len = read(rtsock, buf, sizeof(buf))) < 0) { perror("read from rtsock"); exit(1); } if (len == 0) return; #if 0 if (len < sizeof(*rtm)) { trace(1, "short read from rtsock: %d (should be > %lu)\n", len, (u_long)sizeof(*rtm)); return; } #endif if (dflag >= 2) { fprintf(stderr, "rtmsg:\n"); for (i = 0; i < len; i++) { fprintf(stderr, "%02x ", buf[i] & 0xff); if (i % 16 == 15) fprintf(stderr, "\n"); } fprintf(stderr, "\n"); } for (p = buf; p - buf < len; p += ((struct rt_msghdr *)(void *)p)->rtm_msglen) { if (((struct rt_msghdr *)(void *)p)->rtm_version != RTM_VERSION) continue; /* safety against bogus message */ if (((struct rt_msghdr *)(void *)p)->rtm_msglen <= 0) { trace(1, "bogus rtmsg: length=%d\n", ((struct rt_msghdr *)(void *)p)->rtm_msglen); break; } rtm = NULL; ifam = NULL; ifm = NULL; switch (((struct rt_msghdr *)(void *)p)->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: ifam = (struct ifa_msghdr *)(void *)p; addrs = ifam->ifam_addrs; q = (char *)(ifam + 1); break; case RTM_IFINFO: ifm = (struct if_msghdr *)(void *)p; addrs = ifm->ifm_addrs; q = (char *)(ifm + 1); break; case RTM_IFANNOUNCE: ifan = (struct if_announcemsghdr *)(void *)p; switch (ifan->ifan_what) { case IFAN_ARRIVAL: iface++; break; case IFAN_DEPARTURE: ifremove(ifan->ifan_index); iface++; break; } break; default: rtm = (struct rt_msghdr *)(void *)p; if (rtm->rtm_version != RTM_VERSION) { trace(1, "unexpected rtmsg version %d " "(should be %d)\n", rtm->rtm_version, RTM_VERSION); continue; } /* * Only messages that use the struct rt_msghdr * format are allowed beyond this point. */ if (rtm->rtm_type > RTM_RESOLVE) { trace(1, "rtmsg type %d ignored\n", rtm->rtm_type); continue; } addrs = rtm->rtm_addrs; q = (char *)(rtm + 1); if (rtm->rtm_pid == pid) { #if 0 trace(1, "rtmsg looped back to me, ignored\n"); #endif continue; } break; } memset(&rta, 0, sizeof(rta)); for (i = 0; i < RTAX_MAX; i++) { if (addrs & (1 << i)) { rta[i] = (struct sockaddr_in6 *)(void *)q; q += ROUNDUP(rta[i]->sin6_len); } } trace(1, "rtsock: %s (addrs=%x)\n", rttypes((struct rt_msghdr *)(void *)p), addrs); if (dflag >= 2) { for (i = 0; i < ((struct rt_msghdr *)(void *)p)->rtm_msglen; i++) { fprintf(stderr, "%02x ", p[i] & 0xff); if (i % 16 == 15) fprintf(stderr, "\n"); } fprintf(stderr, "\n"); } /* * Easy ones first. * * We may be able to optimize by using ifm->ifm_index or * ifam->ifam_index. For simplicity we don't do that here. */ switch (((struct rt_msghdr *)(void *)p)->rtm_type) { case RTM_NEWADDR: case RTM_IFINFO: iface++; continue; case RTM_ADD: rtable++; continue; case RTM_LOSING: case RTM_MISS: case RTM_GET: case RTM_LOCK: /* nothing to be done here */ trace(1, "\tnothing to be done, ignored\n"); continue; } #if 0 if (rta[RTAX_DST] == NULL) { trace(1, "\tno destination, ignored\n"); continue; } if (rta[RTAX_DST]->sin6_family != AF_INET6) { trace(1, "\taf mismatch, ignored\n"); continue; } if (IN6_IS_ADDR_LINKLOCAL(&rta[RTAX_DST]->sin6_addr)) { trace(1, "\tlinklocal destination, ignored\n"); continue; } if (IN6_ARE_ADDR_EQUAL(&rta[RTAX_DST]->sin6_addr, &in6addr_loopback)) { trace(1, "\tloopback destination, ignored\n"); continue; /* Loopback */ } if (IN6_IS_ADDR_MULTICAST(&rta[RTAX_DST]->sin6_addr)) { trace(1, "\tmulticast destination, ignored\n"); continue; } #endif /* hard ones */ switch (((struct rt_msghdr *)(void *)p)->rtm_type) { case RTM_NEWADDR: case RTM_IFINFO: case RTM_ADD: case RTM_LOSING: case RTM_MISS: case RTM_GET: case RTM_LOCK: /* should already be handled */ fatal("rtrecv: never reach here"); /*NOTREACHED*/ case RTM_DELETE: if (!rta[RTAX_DST] || !rta[RTAX_GATEWAY]) { trace(1, "\tsome of dst/gw/netamsk are " "unavailable, ignored\n"); break; } if ((rtm->rtm_flags & RTF_HOST) != 0) { mask.sin6_len = sizeof(mask); memset(&mask.sin6_addr, 0xff, sizeof(mask.sin6_addr)); rta[RTAX_NETMASK] = &mask; } else if (!rta[RTAX_NETMASK]) { trace(1, "\tsome of dst/gw/netamsk are " "unavailable, ignored\n"); break; } if (rt_del(rta[RTAX_DST], rta[RTAX_GATEWAY], rta[RTAX_NETMASK]) == 0) { rtable++; /*just to be sure*/ } break; case RTM_CHANGE: case RTM_REDIRECT: trace(1, "\tnot supported yet, ignored\n"); break; case RTM_DELADDR: if (!rta[RTAX_NETMASK] || !rta[RTAX_IFA]) { trace(1, "\tno netmask or ifa given, ignored\n"); break; } if (ifam->ifam_index < nindex2ifc) ifcp = index2ifc[ifam->ifam_index]; else ifcp = NULL; if (!ifcp) { trace(1, "\tinvalid ifam_index %d, ignored\n", ifam->ifam_index); break; } if (!rt_deladdr(ifcp, rta[RTAX_IFA], rta[RTAX_NETMASK])) iface++; break; } } if (iface) { trace(1, "rtsock: reconfigure interfaces, refresh interface routes\n"); ifconfig(); TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if (ifcp->ifc_cflags & IFC_CHANGED) { if (ifrt(ifcp, 1)) { TAILQ_FOREACH(ic, &ifc_head, ifc_next) { if (ifcp->ifc_index == ic->ifc_index) continue; if (ic->ifc_flags & IFF_UP) ripsend(ic, &ic->ifc_ripsin, RRTF_CHANGED); } /* Reset the flag */ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { rrt->rrt_rflags &= ~RRTF_CHANGED; } } ifcp->ifc_cflags &= ~IFC_CHANGED; } } } if (rtable) { trace(1, "rtsock: read routing table again\n"); krtread(1); } } /* * remove specified route from the internal routing table. */ static int rt_del(const struct sockaddr_in6 *sdst, const struct sockaddr_in6 *sgw, const struct sockaddr_in6 *smask) { const struct in6_addr *dst = NULL; const struct in6_addr *gw = NULL; int prefix; struct netinfo6 ni6; struct riprt *rrt = NULL; time_t t_lifetime; if (sdst->sin6_family != AF_INET6) { trace(1, "\tother AF, ignored\n"); return -1; } if (IN6_IS_ADDR_LINKLOCAL(&sdst->sin6_addr) || IN6_ARE_ADDR_EQUAL(&sdst->sin6_addr, &in6addr_loopback) || IN6_IS_ADDR_MULTICAST(&sdst->sin6_addr)) { trace(1, "\taddress %s not interesting, ignored\n", inet6_n2p(&sdst->sin6_addr)); return -1; } dst = &sdst->sin6_addr; if (sgw->sin6_family == AF_INET6) { /* easy case */ gw = &sgw->sin6_addr; prefix = sin6mask2len(smask); } else if (sgw->sin6_family == AF_LINK) { /* * Interface route... a hard case. We need to get the prefix * length from the kernel, but we now are parsing rtmsg. * We'll purge matching routes from my list, then get the * fresh list. */ struct riprt *longest; trace(1, "\t%s is an interface route, guessing prefixlen\n", inet6_n2p(dst)); longest = NULL; TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { if (IN6_ARE_ADDR_EQUAL(&rrt->rrt_info.rip6_dest, &sdst->sin6_addr) && IN6_IS_ADDR_LOOPBACK(&rrt->rrt_gw)) { if (!longest || longest->rrt_info.rip6_plen < rrt->rrt_info.rip6_plen) { longest = rrt; } } } rrt = longest; if (!rrt) { trace(1, "\tno matching interface route found\n"); return -1; } gw = &in6addr_loopback; prefix = rrt->rrt_info.rip6_plen; } else { trace(1, "\tunsupported af: (gw=%d)\n", sgw->sin6_family); return -1; } trace(1, "\tdeleting %s/%d ", inet6_n2p(dst), prefix); trace(1, "gw %s\n", inet6_n2p(gw)); t_lifetime = time(NULL) - RIP_LIFETIME; /* age route for interface address */ memset(&ni6, 0, sizeof(ni6)); ni6.rip6_dest = *dst; ni6.rip6_plen = prefix; applyplen(&ni6.rip6_dest, ni6.rip6_plen); /*to be sure*/ trace(1, "\tfind route %s/%d\n", inet6_n2p(&ni6.rip6_dest), ni6.rip6_plen); if (!rrt && (rrt = rtsearch(&ni6)) == NULL) { trace(1, "\tno route found\n"); return -1; } #if 0 if ((rrt->rrt_flags & RTF_STATIC) == 0) { trace(1, "\tyou can delete static routes only\n"); } else #endif if (!IN6_ARE_ADDR_EQUAL(&rrt->rrt_gw, gw)) { trace(1, "\tgw mismatch: %s <-> ", inet6_n2p(&rrt->rrt_gw)); trace(1, "%s\n", inet6_n2p(gw)); } else { trace(1, "\troute found, age it\n"); if (rrt->rrt_t == 0 || rrt->rrt_t > t_lifetime) { rrt->rrt_t = t_lifetime; rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6; } } return 0; } /* * remove specified address from internal interface/routing table. */ static int rt_deladdr(struct ifc *ifcp, const struct sockaddr_in6 *sifa, const struct sockaddr_in6 *smask) { const struct in6_addr *addr = NULL; int prefix; struct ifac *ifac = NULL; struct netinfo6 ni6; struct riprt *rrt = NULL; time_t t_lifetime; int updated = 0; if (sifa->sin6_family != AF_INET6) { trace(1, "\tother AF, ignored\n"); return -1; } addr = &sifa->sin6_addr; prefix = sin6mask2len(smask); trace(1, "\tdeleting %s/%d from %s\n", inet6_n2p(addr), prefix, ifcp->ifc_name); ifac = ifa_match(ifcp, addr, prefix); if (!ifac) { trace(1, "\tno matching ifa found for %s/%d on %s\n", inet6_n2p(addr), prefix, ifcp->ifc_name); return -1; } if (ifac->ifac_ifc != ifcp) { trace(1, "\taddress table corrupt: back pointer does not match " "(%s != %s)\n", ifcp->ifc_name, ifac->ifac_ifc->ifc_name); return -1; } TAILQ_REMOVE(&ifcp->ifc_ifac_head, ifac, ifac_next); t_lifetime = time(NULL) - RIP_LIFETIME; /* age route for interface address */ memset(&ni6, 0, sizeof(ni6)); ni6.rip6_dest = ifac->ifac_addr; ni6.rip6_plen = ifac->ifac_plen; applyplen(&ni6.rip6_dest, ni6.rip6_plen); trace(1, "\tfind interface route %s/%d on %d\n", inet6_n2p(&ni6.rip6_dest), ni6.rip6_plen, ifcp->ifc_index); if ((rrt = rtsearch(&ni6)) != NULL) { struct in6_addr none; memset(&none, 0, sizeof(none)); if (rrt->rrt_index == ifcp->ifc_index && (IN6_ARE_ADDR_EQUAL(&rrt->rrt_gw, &none) || IN6_IS_ADDR_LOOPBACK(&rrt->rrt_gw))) { trace(1, "\troute found, age it\n"); if (rrt->rrt_t == 0 || rrt->rrt_t > t_lifetime) { rrt->rrt_t = t_lifetime; rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6; } updated++; } else { trace(1, "\tnon-interface route found: %s/%d on %d\n", inet6_n2p(&rrt->rrt_info.rip6_dest), rrt->rrt_info.rip6_plen, rrt->rrt_index); } } else trace(1, "\tno interface route found\n"); /* age route for p2p destination */ if (ifcp->ifc_flags & IFF_POINTOPOINT) { memset(&ni6, 0, sizeof(ni6)); ni6.rip6_dest = ifac->ifac_raddr; ni6.rip6_plen = 128; applyplen(&ni6.rip6_dest, ni6.rip6_plen); /*to be sure*/ trace(1, "\tfind p2p route %s/%d on %d\n", inet6_n2p(&ni6.rip6_dest), ni6.rip6_plen, ifcp->ifc_index); if ((rrt = rtsearch(&ni6)) != NULL) { if (rrt->rrt_index == ifcp->ifc_index && IN6_ARE_ADDR_EQUAL(&rrt->rrt_gw, &ifac->ifac_addr)) { trace(1, "\troute found, age it\n"); if (rrt->rrt_t == 0 || rrt->rrt_t > t_lifetime) { rrt->rrt_t = t_lifetime; rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6; updated++; } } else { trace(1, "\tnon-p2p route found: %s/%d on %d\n", inet6_n2p(&rrt->rrt_info.rip6_dest), rrt->rrt_info.rip6_plen, rrt->rrt_index); } } else trace(1, "\tno p2p route found\n"); } free(ifac); return ((updated) ? 0 : -1); } /* * Get each interface address and put those interface routes to the route * list. */ static int ifrt(struct ifc *ifcp, int again) { struct ifac *ifac; struct riprt *rrt = NULL, *search_rrt, *loop_rrt; struct netinfo6 *np; time_t t_lifetime; int need_trigger = 0; #if 0 if (ifcp->ifc_flags & IFF_LOOPBACK) return 0; /* ignore loopback */ #endif if (ifcp->ifc_flags & IFF_POINTOPOINT) { ifrt_p2p(ifcp, again); return 0; } TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) { if (IN6_IS_ADDR_LINKLOCAL(&ifac->ifac_addr)) { #if 0 trace(1, "route: %s on %s: " "skip linklocal interface address\n", inet6_n2p(&ifac->ifac_addr), ifcp->ifc_name); #endif continue; } if (IN6_IS_ADDR_UNSPECIFIED(&ifac->ifac_addr)) { #if 0 trace(1, "route: %s: skip unspec interface address\n", ifcp->ifc_name); #endif continue; } if (IN6_IS_ADDR_LOOPBACK(&ifac->ifac_addr)) { #if 0 trace(1, "route: %s: skip loopback address\n", ifcp->ifc_name); #endif continue; } if (ifcp->ifc_flags & IFF_UP) { if ((rrt = MALLOC(struct riprt)) == NULL) fatal("malloc: struct riprt"); memset(rrt, 0, sizeof(*rrt)); rrt->rrt_same = NULL; rrt->rrt_index = ifcp->ifc_index; rrt->rrt_t = 0; /* don't age */ rrt->rrt_info.rip6_dest = ifac->ifac_addr; rrt->rrt_info.rip6_tag = htons(routetag & 0xffff); rrt->rrt_info.rip6_metric = 1 + ifcp->ifc_metric; rrt->rrt_info.rip6_plen = ifac->ifac_plen; rrt->rrt_flags = RTF_HOST; rrt->rrt_rflags |= RRTF_CHANGED; applyplen(&rrt->rrt_info.rip6_dest, ifac->ifac_plen); memset(&rrt->rrt_gw, 0, sizeof(struct in6_addr)); rrt->rrt_gw = ifac->ifac_addr; np = &rrt->rrt_info; search_rrt = rtsearch(np); if (search_rrt != NULL) { if (search_rrt->rrt_info.rip6_metric <= rrt->rrt_info.rip6_metric) { /* Already have better route */ if (!again) { trace(1, "route: %s/%d: " "already registered (%s)\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, ifcp->ifc_name); } goto next; } TAILQ_REMOVE(&riprt_head, search_rrt, rrt_next); delroute(&search_rrt->rrt_info, &search_rrt->rrt_gw); free(search_rrt); } /* Attach the route to the list */ trace(1, "route: %s/%d: register route (%s)\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, ifcp->ifc_name); TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next); addroute(rrt, &rrt->rrt_gw, ifcp); rrt = NULL; sendrequest(ifcp); ripsend(ifcp, &ifcp->ifc_ripsin, 0); need_trigger = 1; } else { TAILQ_FOREACH(loop_rrt, &riprt_head, rrt_next) { if (loop_rrt->rrt_index == ifcp->ifc_index) { t_lifetime = time(NULL) - RIP_LIFETIME; if (loop_rrt->rrt_t == 0 || loop_rrt->rrt_t > t_lifetime) { loop_rrt->rrt_t = t_lifetime; loop_rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6; loop_rrt->rrt_rflags |= RRTF_CHANGED; need_trigger = 1; } } } } next: if (rrt) free(rrt); } return need_trigger; } /* * there are couple of p2p interface routing models. "behavior" lets * you pick one. it looks that gated behavior fits best with BSDs, * since BSD kernels do not look at prefix length on p2p interfaces. */ static void ifrt_p2p(struct ifc *ifcp, int again) { struct ifac *ifac; struct riprt *rrt, *orrt; struct netinfo6 *np; struct in6_addr addr, dest; int advert, ignore, i; #define P2PADVERT_NETWORK 1 #define P2PADVERT_ADDR 2 #define P2PADVERT_DEST 4 #define P2PADVERT_MAX 4 const enum { CISCO, GATED, ROUTE6D } behavior = GATED; const char *category = ""; const char *noadv; TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) { addr = ifac->ifac_addr; dest = ifac->ifac_raddr; applyplen(&addr, ifac->ifac_plen); applyplen(&dest, ifac->ifac_plen); advert = ignore = 0; switch (behavior) { case CISCO: /* * honor addr/plen, just like normal shared medium * interface. this may cause trouble if you reuse * addr/plen on other interfaces. * * advertise addr/plen. */ advert |= P2PADVERT_NETWORK; break; case GATED: /* * prefixlen on p2p interface is meaningless. * advertise addr/128 and dest/128. * * do not install network route to route6d routing * table (if we do, it would prevent route installation * for other p2p interface that shares addr/plen). * * XXX what should we do if dest is ::? it will not * get announced anyways (see following filter), * but we need to think. */ advert |= P2PADVERT_ADDR; advert |= P2PADVERT_DEST; ignore |= P2PADVERT_NETWORK; break; case ROUTE6D: /* * just for testing. actually the code is redundant * given the current p2p interface address assignment * rule for kame kernel. * * intent: * A/n -> announce A/n * A B/n, A and B share prefix -> A/n (= B/n) * A B/n, do not share prefix -> A/128 and B/128 * actually, A/64 and A B/128 are the only cases * permitted by the kernel: * A/64 -> A/64 * A B/128 -> A/128 and B/128 */ if (!IN6_IS_ADDR_UNSPECIFIED(&ifac->ifac_raddr)) { if (IN6_ARE_ADDR_EQUAL(&addr, &dest)) advert |= P2PADVERT_NETWORK; else { advert |= P2PADVERT_ADDR; advert |= P2PADVERT_DEST; ignore |= P2PADVERT_NETWORK; } } else advert |= P2PADVERT_NETWORK; break; } for (i = 1; i <= P2PADVERT_MAX; i *= 2) { if ((ignore & i) != 0) continue; if ((rrt = MALLOC(struct riprt)) == NULL) { fatal("malloc: struct riprt"); /*NOTREACHED*/ } memset(rrt, 0, sizeof(*rrt)); rrt->rrt_same = NULL; rrt->rrt_index = ifcp->ifc_index; rrt->rrt_t = 0; /* don't age */ switch (i) { case P2PADVERT_NETWORK: rrt->rrt_info.rip6_dest = ifac->ifac_addr; rrt->rrt_info.rip6_plen = ifac->ifac_plen; applyplen(&rrt->rrt_info.rip6_dest, ifac->ifac_plen); category = "network"; break; case P2PADVERT_ADDR: rrt->rrt_info.rip6_dest = ifac->ifac_addr; rrt->rrt_info.rip6_plen = 128; rrt->rrt_gw = in6addr_loopback; category = "addr"; break; case P2PADVERT_DEST: rrt->rrt_info.rip6_dest = ifac->ifac_raddr; rrt->rrt_info.rip6_plen = 128; rrt->rrt_gw = ifac->ifac_addr; category = "dest"; break; } if (IN6_IS_ADDR_UNSPECIFIED(&rrt->rrt_info.rip6_dest) || IN6_IS_ADDR_LINKLOCAL(&rrt->rrt_info.rip6_dest)) { #if 0 trace(1, "route: %s: skip unspec/linklocal " "(%s on %s)\n", category, ifcp->ifc_name); #endif free(rrt); continue; } if ((advert & i) == 0) { rrt->rrt_rflags |= RRTF_NOADVERTISE; noadv = ", NO-ADV"; } else noadv = ""; rrt->rrt_info.rip6_tag = htons(routetag & 0xffff); rrt->rrt_info.rip6_metric = 1 + ifcp->ifc_metric; np = &rrt->rrt_info; orrt = rtsearch(np); if (!orrt) { /* Attach the route to the list */ trace(1, "route: %s/%d: register route " "(%s on %s%s)\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, category, ifcp->ifc_name, noadv); TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next); } else if (rrt->rrt_index != orrt->rrt_index || rrt->rrt_info.rip6_metric != orrt->rrt_info.rip6_metric) { /* replace route */ TAILQ_INSERT_BEFORE(orrt, rrt, rrt_next); TAILQ_REMOVE(&riprt_head, orrt, rrt_next); free(orrt); trace(1, "route: %s/%d: update (%s on %s%s)\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, category, ifcp->ifc_name, noadv); } else { /* Already found */ if (!again) { trace(1, "route: %s/%d: " "already registered (%s on %s%s)\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, category, ifcp->ifc_name, noadv); } free(rrt); } } } #undef P2PADVERT_NETWORK #undef P2PADVERT_ADDR #undef P2PADVERT_DEST #undef P2PADVERT_MAX } static int getifmtu(int ifindex) { int mib[6]; char *buf; size_t msize; struct if_msghdr *ifm; int mtu; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = AF_INET6; mib[4] = NET_RT_IFLIST; mib[5] = ifindex; if (sysctl(mib, nitems(mib), NULL, &msize, NULL, 0) < 0) { fatal("sysctl estimate NET_RT_IFLIST"); /*NOTREACHED*/ } if ((buf = malloc(msize)) == NULL) { fatal("malloc"); /*NOTREACHED*/ } if (sysctl(mib, nitems(mib), buf, &msize, NULL, 0) < 0) { fatal("sysctl NET_RT_IFLIST"); /*NOTREACHED*/ } ifm = (struct if_msghdr *)(void *)buf; mtu = ifm->ifm_data.ifi_mtu; if (ifindex != ifm->ifm_index) { fatal("ifindex does not match with ifm_index"); /*NOTREACHED*/ } free(buf); return mtu; } static const char * rttypes(struct rt_msghdr *rtm) { #define RTTYPE(s, f) \ do { \ if (rtm->rtm_type == (f)) \ return (s); \ } while (0) RTTYPE("ADD", RTM_ADD); RTTYPE("DELETE", RTM_DELETE); RTTYPE("CHANGE", RTM_CHANGE); RTTYPE("GET", RTM_GET); RTTYPE("LOSING", RTM_LOSING); RTTYPE("REDIRECT", RTM_REDIRECT); RTTYPE("MISS", RTM_MISS); RTTYPE("LOCK", RTM_LOCK); RTTYPE("NEWADDR", RTM_NEWADDR); RTTYPE("DELADDR", RTM_DELADDR); RTTYPE("IFINFO", RTM_IFINFO); #ifdef RTM_OIFINFO RTTYPE("OIFINFO", RTM_OIFINFO); #endif #ifdef RTM_IFANNOUNCE RTTYPE("IFANNOUNCE", RTM_IFANNOUNCE); #endif #ifdef RTM_NEWMADDR RTTYPE("NEWMADDR", RTM_NEWMADDR); #endif #ifdef RTM_DELMADDR RTTYPE("DELMADDR", RTM_DELMADDR); #endif #undef RTTYPE return NULL; } static const char * rtflags(struct rt_msghdr *rtm) { static char buf[BUFSIZ]; /* * letter conflict should be okay. painful when *BSD diverges... */ strlcpy(buf, "", sizeof(buf)); #define RTFLAG(s, f) \ do { \ if (rtm->rtm_flags & (f)) \ strlcat(buf, (s), sizeof(buf)); \ } while (0) RTFLAG("U", RTF_UP); RTFLAG("G", RTF_GATEWAY); RTFLAG("H", RTF_HOST); RTFLAG("R", RTF_REJECT); RTFLAG("D", RTF_DYNAMIC); RTFLAG("M", RTF_MODIFIED); RTFLAG("d", RTF_DONE); #ifdef RTF_MASK RTFLAG("m", RTF_MASK); #endif #ifdef RTF_CLONED RTFLAG("c", RTF_CLONED); #endif RTFLAG("X", RTF_XRESOLVE); #ifdef RTF_LLINFO RTFLAG("L", RTF_LLINFO); #endif RTFLAG("S", RTF_STATIC); RTFLAG("B", RTF_BLACKHOLE); #ifdef RTF_PROTO3 RTFLAG("3", RTF_PROTO3); #endif RTFLAG("2", RTF_PROTO2); RTFLAG("1", RTF_PROTO1); #ifdef RTF_BROADCAST RTFLAG("b", RTF_BROADCAST); #endif #ifdef RTF_DEFAULT RTFLAG("d", RTF_DEFAULT); #endif #ifdef RTF_ISAROUTER RTFLAG("r", RTF_ISAROUTER); #endif #ifdef RTF_TUNNEL RTFLAG("T", RTF_TUNNEL); #endif #ifdef RTF_AUTH RTFLAG("A", RTF_AUTH); #endif #ifdef RTF_CRYPT RTFLAG("E", RTF_CRYPT); #endif #undef RTFLAG return buf; } static const char * ifflags(int flags) { static char buf[BUFSIZ]; strlcpy(buf, "", sizeof(buf)); #define IFFLAG(s, f) \ do { \ if (flags & (f)) { \ if (buf[0]) \ strlcat(buf, ",", sizeof(buf)); \ strlcat(buf, (s), sizeof(buf)); \ } \ } while (0) IFFLAG("UP", IFF_UP); IFFLAG("BROADCAST", IFF_BROADCAST); IFFLAG("DEBUG", IFF_DEBUG); IFFLAG("LOOPBACK", IFF_LOOPBACK); IFFLAG("POINTOPOINT", IFF_POINTOPOINT); #ifdef IFF_NOTRAILERS IFFLAG("NOTRAILERS", IFF_NOTRAILERS); #endif IFFLAG("RUNNING", IFF_RUNNING); IFFLAG("NOARP", IFF_NOARP); IFFLAG("PROMISC", IFF_PROMISC); IFFLAG("ALLMULTI", IFF_ALLMULTI); IFFLAG("OACTIVE", IFF_OACTIVE); IFFLAG("SIMPLEX", IFF_SIMPLEX); IFFLAG("LINK0", IFF_LINK0); IFFLAG("LINK1", IFF_LINK1); IFFLAG("LINK2", IFF_LINK2); IFFLAG("MULTICAST", IFF_MULTICAST); #undef IFFLAG return buf; } static void krtread(int again) { int mib[6]; size_t msize; char *buf, *p, *lim; struct rt_msghdr *rtm; int retry; const char *errmsg; retry = 0; buf = NULL; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = AF_INET6; /* Address family */ mib[4] = NET_RT_DUMP; /* Dump the kernel routing table */ mib[5] = 0; /* No flags */ do { if (retry) sleep(1); retry++; errmsg = NULL; if (buf) { free(buf); buf = NULL; } if (sysctl(mib, nitems(mib), NULL, &msize, NULL, 0) < 0) { errmsg = "sysctl estimate"; continue; } if ((buf = malloc(msize)) == NULL) { errmsg = "malloc"; continue; } if (sysctl(mib, nitems(mib), buf, &msize, NULL, 0) < 0) { errmsg = "sysctl NET_RT_DUMP"; continue; } } while (retry < RT_DUMP_MAXRETRY && errmsg != NULL); if (errmsg) { fatal("%s (with %d retries, msize=%lu)", errmsg, retry, (u_long)msize); /*NOTREACHED*/ } else if (1 < retry) syslog(LOG_INFO, "NET_RT_DUMP %d retires", retry); lim = buf + msize; for (p = buf; p < lim; p += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)(void *)p; rt_entry(rtm, again); } free(buf); } static void rt_entry(struct rt_msghdr *rtm, int again) { struct sockaddr_in6 *sin6_dst, *sin6_gw, *sin6_mask; struct sockaddr_in6 *sin6_genmask, *sin6_ifp; char *rtmp, *ifname = NULL; struct riprt *rrt, *orrt; struct netinfo6 *np; int ifindex; sin6_dst = sin6_gw = sin6_mask = sin6_genmask = sin6_ifp = 0; if ((rtm->rtm_flags & RTF_UP) == 0 || rtm->rtm_flags & (RTF_XRESOLVE|RTF_BLACKHOLE)) { return; /* not interested in the link route */ } /* do not look at cloned routes */ #ifdef RTF_WASCLONED if (rtm->rtm_flags & RTF_WASCLONED) return; #endif #ifdef RTF_CLONED if (rtm->rtm_flags & RTF_CLONED) return; #endif /* XXX: Ignore connected routes. */ if (!(rtm->rtm_flags & (RTF_GATEWAY|RTF_HOST|RTF_STATIC))) return; /* * do not look at dynamic routes. * netbsd/openbsd cloned routes have UGHD. */ if (rtm->rtm_flags & RTF_DYNAMIC) return; rtmp = (char *)(rtm + 1); /* Destination */ if ((rtm->rtm_addrs & RTA_DST) == 0) return; /* ignore routes without destination address */ sin6_dst = (struct sockaddr_in6 *)(void *)rtmp; rtmp += ROUNDUP(sin6_dst->sin6_len); if (rtm->rtm_addrs & RTA_GATEWAY) { sin6_gw = (struct sockaddr_in6 *)(void *)rtmp; rtmp += ROUNDUP(sin6_gw->sin6_len); } if (rtm->rtm_addrs & RTA_NETMASK) { sin6_mask = (struct sockaddr_in6 *)(void *)rtmp; rtmp += ROUNDUP(sin6_mask->sin6_len); } if (rtm->rtm_addrs & RTA_GENMASK) { sin6_genmask = (struct sockaddr_in6 *)(void *)rtmp; rtmp += ROUNDUP(sin6_genmask->sin6_len); } if (rtm->rtm_addrs & RTA_IFP) { sin6_ifp = (struct sockaddr_in6 *)(void *)rtmp; rtmp += ROUNDUP(sin6_ifp->sin6_len); } /* Destination */ if (sin6_dst->sin6_family != AF_INET6) return; if (IN6_IS_ADDR_LINKLOCAL(&sin6_dst->sin6_addr)) return; /* Link-local */ if (IN6_ARE_ADDR_EQUAL(&sin6_dst->sin6_addr, &in6addr_loopback)) return; /* Loopback */ if (IN6_IS_ADDR_MULTICAST(&sin6_dst->sin6_addr)) return; if ((rrt = MALLOC(struct riprt)) == NULL) { fatal("malloc: struct riprt"); /*NOTREACHED*/ } memset(rrt, 0, sizeof(*rrt)); np = &rrt->rrt_info; rrt->rrt_same = NULL; rrt->rrt_t = time(NULL); if (aflag == 0 && (rtm->rtm_flags & RTF_STATIC)) rrt->rrt_t = 0; /* Don't age static routes */ if (rtm->rtm_flags & Pflag) rrt->rrt_t = 0; /* Don't age PROTO[123] routes */ if ((rtm->rtm_flags & (RTF_HOST|RTF_GATEWAY)) == RTF_HOST) rrt->rrt_t = 0; /* Don't age non-gateway host routes */ np->rip6_tag = 0; np->rip6_metric = rtm->rtm_rmx.rmx_hopcount; if (np->rip6_metric < 1) np->rip6_metric = 1; rrt->rrt_flags = rtm->rtm_flags; np->rip6_dest = sin6_dst->sin6_addr; /* Mask or plen */ if (rtm->rtm_flags & RTF_HOST) np->rip6_plen = 128; /* Host route */ else if (sin6_mask) np->rip6_plen = sin6mask2len(sin6_mask); else np->rip6_plen = 0; orrt = rtsearch(np); if (orrt && orrt->rrt_info.rip6_metric != HOPCNT_INFINITY6) { /* Already found */ if (!again) { trace(1, "route: %s/%d flags %s: already registered\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, rtflags(rtm)); } free(rrt); return; } /* Gateway */ if (!sin6_gw) memset(&rrt->rrt_gw, 0, sizeof(struct in6_addr)); else { if (sin6_gw->sin6_family == AF_INET6) rrt->rrt_gw = sin6_gw->sin6_addr; else if (sin6_gw->sin6_family == AF_LINK) { /* XXX in case ppp link? */ rrt->rrt_gw = in6addr_loopback; } else memset(&rrt->rrt_gw, 0, sizeof(struct in6_addr)); } trace(1, "route: %s/%d flags %s", inet6_n2p(&np->rip6_dest), np->rip6_plen, rtflags(rtm)); trace(1, " gw %s", inet6_n2p(&rrt->rrt_gw)); /* Interface */ ifindex = rtm->rtm_index; if ((unsigned int)ifindex < nindex2ifc && index2ifc[ifindex]) ifname = index2ifc[ifindex]->ifc_name; else { trace(1, " not configured\n"); free(rrt); return; } trace(1, " if %s sock %d", ifname, ifindex); rrt->rrt_index = ifindex; trace(1, "\n"); /* Check gateway */ if (!IN6_IS_ADDR_LINKLOCAL(&rrt->rrt_gw) && !IN6_IS_ADDR_LOOPBACK(&rrt->rrt_gw) && (rrt->rrt_flags & RTF_LOCAL) == 0) { trace(0, "***** Gateway %s is not a link-local address.\n", inet6_n2p(&rrt->rrt_gw)); trace(0, "***** dest(%s) if(%s) -- Not optimized.\n", inet6_n2p(&rrt->rrt_info.rip6_dest), ifname); rrt->rrt_rflags |= RRTF_NH_NOT_LLADDR; } /* Put it to the route list */ if (orrt && orrt->rrt_info.rip6_metric == HOPCNT_INFINITY6) { /* replace route list */ TAILQ_INSERT_BEFORE(orrt, rrt, rrt_next); TAILQ_REMOVE(&riprt_head, orrt, rrt_next); trace(1, "route: %s/%d flags %s: replace new route\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, rtflags(rtm)); free(orrt); } else TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next); } static int addroute(struct riprt *rrt, const struct in6_addr *gw, struct ifc *ifcp) { struct netinfo6 *np; u_char buf[BUFSIZ], buf1[BUFSIZ], buf2[BUFSIZ]; struct rt_msghdr *rtm; struct sockaddr_in6 *sin6; int len; np = &rrt->rrt_info; inet_ntop(AF_INET6, (const void *)gw, (char *)buf1, sizeof(buf1)); inet_ntop(AF_INET6, (void *)&ifcp->ifc_mylladdr, (char *)buf2, sizeof(buf2)); tracet(1, "ADD: %s/%d gw %s [%d] ifa %s\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1, np->rip6_metric - 1, buf2); if (rtlog) fprintf(rtlog, "%s: ADD: %s/%d gw %s [%d] ifa %s\n", hms(), inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1, np->rip6_metric - 1, buf2); if (nflag) return 0; memset(buf, 0, sizeof(buf)); rtm = (struct rt_msghdr *)(void *)buf; rtm->rtm_type = RTM_ADD; rtm->rtm_version = RTM_VERSION; rtm->rtm_seq = ++seq; rtm->rtm_pid = pid; rtm->rtm_flags = rrt->rrt_flags; rtm->rtm_flags |= Qflag; rtm->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; rtm->rtm_rmx.rmx_hopcount = np->rip6_metric - 1; rtm->rtm_inits = RTV_HOPCOUNT; sin6 = (struct sockaddr_in6 *)(void *)&buf[sizeof(struct rt_msghdr)]; /* Destination */ sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = np->rip6_dest; sin6 = (struct sockaddr_in6 *)(void *)((char *)sin6 + ROUNDUP(sin6->sin6_len)); /* Gateway */ sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = *gw; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) sin6->sin6_scope_id = ifcp->ifc_index; sin6 = (struct sockaddr_in6 *)(void *)((char *)sin6 + ROUNDUP(sin6->sin6_len)); /* Netmask */ sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = *(plen2mask(np->rip6_plen)); sin6 = (struct sockaddr_in6 *)(void *)((char *)sin6 + ROUNDUP(sin6->sin6_len)); len = (char *)sin6 - (char *)buf; rtm->rtm_msglen = len; if (write(rtsock, buf, len) > 0) return 0; if (errno == EEXIST) { trace(0, "ADD: Route already exists %s/%d gw %s\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1); if (rtlog) fprintf(rtlog, "ADD: Route already exists %s/%d gw %s\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1); } else { trace(0, "Can not write to rtsock (addroute): %s\n", strerror(errno)); if (rtlog) fprintf(rtlog, "\tCan not write to rtsock: %s\n", strerror(errno)); } return -1; } static int delroute(struct netinfo6 *np, struct in6_addr *gw) { u_char buf[BUFSIZ], buf2[BUFSIZ]; struct rt_msghdr *rtm; struct sockaddr_in6 *sin6; int len; inet_ntop(AF_INET6, (void *)gw, (char *)buf2, sizeof(buf2)); tracet(1, "DEL: %s/%d gw %s\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, buf2); if (rtlog) fprintf(rtlog, "%s: DEL: %s/%d gw %s\n", hms(), inet6_n2p(&np->rip6_dest), np->rip6_plen, buf2); if (nflag) return 0; memset(buf, 0, sizeof(buf)); rtm = (struct rt_msghdr *)(void *)buf; rtm->rtm_type = RTM_DELETE; rtm->rtm_version = RTM_VERSION; rtm->rtm_seq = ++seq; rtm->rtm_pid = pid; rtm->rtm_flags = RTF_UP | RTF_GATEWAY; rtm->rtm_flags |= Qflag; if (np->rip6_plen == sizeof(struct in6_addr) * 8) rtm->rtm_flags |= RTF_HOST; rtm->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; sin6 = (struct sockaddr_in6 *)(void *)&buf[sizeof(struct rt_msghdr)]; /* Destination */ sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = np->rip6_dest; sin6 = (struct sockaddr_in6 *)(void *)((char *)sin6 + ROUNDUP(sin6->sin6_len)); /* Gateway */ sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = *gw; sin6 = (struct sockaddr_in6 *)(void *)((char *)sin6 + ROUNDUP(sin6->sin6_len)); /* Netmask */ sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = *(plen2mask(np->rip6_plen)); sin6 = (struct sockaddr_in6 *)(void *)((char *)sin6 + ROUNDUP(sin6->sin6_len)); len = (char *)sin6 - (char *)buf; rtm->rtm_msglen = len; if (write(rtsock, buf, len) >= 0) return 0; if (errno == ESRCH) { trace(0, "RTDEL: Route does not exist: %s/%d gw %s\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, buf2); if (rtlog) fprintf(rtlog, "RTDEL: Route does not exist: %s/%d gw %s\n", inet6_n2p(&np->rip6_dest), np->rip6_plen, buf2); } else { trace(0, "Can not write to rtsock (delroute): %s\n", strerror(errno)); if (rtlog) fprintf(rtlog, "\tCan not write to rtsock: %s\n", strerror(errno)); } return -1; } #if 0 static struct in6_addr * getroute(struct netinfo6 *np, struct in6_addr *gw) { u_char buf[BUFSIZ]; int myseq; int len; struct rt_msghdr *rtm; struct sockaddr_in6 *sin6; rtm = (struct rt_msghdr *)(void *)buf; len = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in6); memset(rtm, 0, len); rtm->rtm_type = RTM_GET; rtm->rtm_version = RTM_VERSION; myseq = ++seq; rtm->rtm_seq = myseq; rtm->rtm_addrs = RTA_DST; rtm->rtm_msglen = len; sin6 = (struct sockaddr_in6 *)(void *)&buf[sizeof(struct rt_msghdr)]; sin6->sin6_len = sizeof(struct sockaddr_in6); sin6->sin6_family = AF_INET6; sin6->sin6_addr = np->rip6_dest; if (write(rtsock, buf, len) < 0) { if (errno == ESRCH) /* No such route found */ return NULL; perror("write to rtsock"); exit(1); } do { if ((len = read(rtsock, buf, sizeof(buf))) < 0) { perror("read from rtsock"); exit(1); } rtm = (struct rt_msghdr *)(void *)buf; } while (rtm->rtm_type != RTM_GET || rtm->rtm_seq != myseq || rtm->rtm_pid != pid); sin6 = (struct sockaddr_in6 *)(void *)&buf[sizeof(struct rt_msghdr)]; if (rtm->rtm_addrs & RTA_DST) { sin6 = (struct sockaddr_in6 *)(void *) ((char *)sin6 + ROUNDUP(sin6->sin6_len)); } if (rtm->rtm_addrs & RTA_GATEWAY) { *gw = sin6->sin6_addr; return gw; } return NULL; } #endif static const char * inet6_n2p(const struct in6_addr *p) { static char buf[BUFSIZ]; return inet_ntop(AF_INET6, (const void *)p, buf, sizeof(buf)); } static void ifrtdump(int sig) { ifdump(sig); rtdump(sig); } static void ifdump(int sig) { struct ifc *ifcp; FILE *dump; int nifc = 0; if (sig == 0) dump = stderr; else if ((dump = fopen(ROUTE6D_DUMP, "a")) == NULL) dump = stderr; fprintf(dump, "%s: Interface Table Dump\n", hms()); TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) nifc++; fprintf(dump, " Number of interfaces: %d\n", nifc); fprintf(dump, " advertising interfaces:\n"); TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if ((ifcp->ifc_flags & IFF_UP) == 0) continue; if (iff_find(ifcp, IFIL_TYPE_N) != NULL) continue; ifdump0(dump, ifcp); } fprintf(dump, "\n"); fprintf(dump, " non-advertising interfaces:\n"); TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if ((ifcp->ifc_flags & IFF_UP) && (iff_find(ifcp, IFIL_TYPE_N) == NULL)) continue; ifdump0(dump, ifcp); } fprintf(dump, "\n"); if (dump != stderr) fclose(dump); } static void ifdump0(FILE *dump, const struct ifc *ifcp) { struct ifac *ifac; struct iff *iffp; char buf[BUFSIZ]; const char *ft; int addr; fprintf(dump, " %s: index(%d) flags(%s) addr(%s) mtu(%d) metric(%d)\n", ifcp->ifc_name, ifcp->ifc_index, ifflags(ifcp->ifc_flags), inet6_n2p(&ifcp->ifc_mylladdr), ifcp->ifc_mtu, ifcp->ifc_metric); TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) { if (ifcp->ifc_flags & IFF_POINTOPOINT) { inet_ntop(AF_INET6, (void *)&ifac->ifac_raddr, buf, sizeof(buf)); fprintf(dump, "\t%s/%d -- %s\n", inet6_n2p(&ifac->ifac_addr), ifac->ifac_plen, buf); } else { fprintf(dump, "\t%s/%d\n", inet6_n2p(&ifac->ifac_addr), ifac->ifac_plen); } } fprintf(dump, "\tFilter:\n"); TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) { addr = 0; switch (iffp->iff_type) { case IFIL_TYPE_A: ft = "Aggregate"; addr++; break; case IFIL_TYPE_N: ft = "No-use"; break; case IFIL_TYPE_O: ft = "Advertise-only"; addr++; break; case IFIL_TYPE_T: ft = "Default-only"; break; case IFIL_TYPE_L: ft = "Listen-only"; addr++; break; default: snprintf(buf, sizeof(buf), "Unknown-%c", iffp->iff_type); ft = buf; addr++; break; } fprintf(dump, "\t\t%s", ft); if (addr) fprintf(dump, "(%s/%d)", inet6_n2p(&iffp->iff_addr), iffp->iff_plen); fprintf(dump, "\n"); } fprintf(dump, "\n"); } static void rtdump(int sig) { struct riprt *rrt; char buf[BUFSIZ]; FILE *dump; time_t t, age; if (sig == 0) dump = stderr; else if ((dump = fopen(ROUTE6D_DUMP, "a")) == NULL) dump = stderr; t = time(NULL); fprintf(dump, "\n%s: Routing Table Dump\n", hms()); TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { if (rrt->rrt_t == 0) age = 0; else age = t - rrt->rrt_t; inet_ntop(AF_INET6, (void *)&rrt->rrt_info.rip6_dest, buf, sizeof(buf)); fprintf(dump, " %s/%d if(%d:%s) gw(%s) [%d] age(%ld)", buf, rrt->rrt_info.rip6_plen, rrt->rrt_index, index2ifc[rrt->rrt_index]->ifc_name, inet6_n2p(&rrt->rrt_gw), rrt->rrt_info.rip6_metric, (long)age); if (rrt->rrt_info.rip6_tag) { fprintf(dump, " tag(0x%04x)", ntohs(rrt->rrt_info.rip6_tag) & 0xffff); } if (rrt->rrt_rflags & RRTF_NH_NOT_LLADDR) fprintf(dump, " NOT-LL"); if (rrt->rrt_rflags & RRTF_NOADVERTISE) fprintf(dump, " NO-ADV"); fprintf(dump, "\n"); } fprintf(dump, "\n"); if (dump != stderr) fclose(dump); } /* * Parse the -A (and -O) options and put corresponding filter object to the * specified interface structures. Each of the -A/O option has the following * syntax: -A 5f09:c400::/32,ef0,ef1 (aggregate) * -O 5f09:c400::/32,ef0,ef1 (only when match) */ static void filterconfig(void) { int i; char *p, *ap, *iflp, *ifname, *ep; struct iff iff, *iffp; struct ifc *ifcp; struct riprt *rrt; #if 0 struct in6_addr gw; #endif u_long plen; for (i = 0; i < nfilter; i++) { ap = filter[i]; iflp = NULL; iffp = ⇔ memset(iffp, 0, sizeof(*iffp)); if (filtertype[i] == 'N' || filtertype[i] == 'T') { iflp = ap; goto ifonly; } if ((p = strchr(ap, ',')) != NULL) { *p++ = '\0'; iflp = p; } if ((p = strchr(ap, '/')) == NULL) { fatal("no prefixlen specified for '%s'", ap); /*NOTREACHED*/ } *p++ = '\0'; if (inet_pton(AF_INET6, ap, &iffp->iff_addr) != 1) { fatal("invalid prefix specified for '%s'", ap); /*NOTREACHED*/ } errno = 0; ep = NULL; plen = strtoul(p, &ep, 10); if (errno || !*p || *ep || plen > sizeof(iffp->iff_addr) * 8) { fatal("invalid prefix length specified for '%s'", ap); /*NOTREACHED*/ } iffp->iff_plen = plen; applyplen(&iffp->iff_addr, iffp->iff_plen); ifonly: iffp->iff_type = filtertype[i]; if (iflp == NULL || *iflp == '\0') { fatal("no interface specified for '%s'", ap); /*NOTREACHED*/ } /* parse the interface listing portion */ while (iflp) { ifname = iflp; if ((iflp = strchr(iflp, ',')) != NULL) *iflp++ = '\0'; TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if (fnmatch(ifname, ifcp->ifc_name, 0) != 0) continue; iffp = malloc(sizeof(*iffp)); if (iffp == NULL) { fatal("malloc of iff"); /*NOTREACHED*/ } memcpy(iffp, &iff, sizeof(*iffp)); #if 0 syslog(LOG_INFO, "Add filter: type %d, ifname %s.", iffp->iff_type, ifname); #endif TAILQ_INSERT_HEAD(&ifcp->ifc_iff_head, iffp, iff_next); } } /* * -A: aggregate configuration. */ if (filtertype[i] != IFIL_TYPE_A) continue; /* put the aggregate to the kernel routing table */ rrt = (struct riprt *)malloc(sizeof(struct riprt)); if (rrt == NULL) { fatal("malloc: rrt"); /*NOTREACHED*/ } memset(rrt, 0, sizeof(struct riprt)); rrt->rrt_info.rip6_dest = iff.iff_addr; rrt->rrt_info.rip6_plen = iff.iff_plen; rrt->rrt_info.rip6_metric = 1; rrt->rrt_info.rip6_tag = htons(routetag & 0xffff); rrt->rrt_gw = in6addr_loopback; rrt->rrt_flags = RTF_UP | RTF_REJECT; rrt->rrt_rflags = RRTF_AGGREGATE; rrt->rrt_t = 0; rrt->rrt_index = loopifcp->ifc_index; #if 0 if (getroute(&rrt->rrt_info, &gw)) { #if 0 /* * When the address has already been registered in the * kernel routing table, it should be removed */ delroute(&rrt->rrt_info, &gw); #else /* it is safer behavior */ errno = EINVAL; fatal("%s/%u already in routing table, " "cannot aggregate", inet6_n2p(&rrt->rrt_info.rip6_dest), rrt->rrt_info.rip6_plen); /*NOTREACHED*/ #endif } #endif /* Put the route to the list */ TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next); trace(1, "Aggregate: %s/%d for %s\n", inet6_n2p(&iff.iff_addr), iff.iff_plen, loopifcp->ifc_name); /* Add this route to the kernel */ if (nflag) /* do not modify kernel routing table */ continue; addroute(rrt, &in6addr_loopback, loopifcp); } } /***************** utility functions *****************/ /* * Returns a pointer to ifac whose address and prefix length matches * with the address and prefix length specified in the arguments. */ static struct ifac * ifa_match(const struct ifc *ifcp, const struct in6_addr *ia, int plen) { struct ifac *ifac; TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) { if (IN6_ARE_ADDR_EQUAL(&ifac->ifac_addr, ia) && ifac->ifac_plen == plen) break; } return (ifac); } /* * Return a pointer to riprt structure whose address and prefix length * matches with the address and prefix length found in the argument. * Note: This is not a rtalloc(). Therefore exact match is necessary. */ static struct riprt * rtsearch(struct netinfo6 *np) { struct riprt *rrt; TAILQ_FOREACH(rrt, &riprt_head, rrt_next) { if (rrt->rrt_info.rip6_plen == np->rip6_plen && IN6_ARE_ADDR_EQUAL(&rrt->rrt_info.rip6_dest, &np->rip6_dest)) break; } return (rrt); } static int sin6mask2len(const struct sockaddr_in6 *sin6) { return mask2len(&sin6->sin6_addr, sin6->sin6_len - offsetof(struct sockaddr_in6, sin6_addr)); } static int mask2len(const struct in6_addr *addr, int lenlim) { int i = 0, j; const u_char *p = (const u_char *)addr; for (j = 0; j < lenlim; j++, p++) { if (*p != 0xff) break; i += 8; } if (j < lenlim) { switch (*p) { #define MASKLEN(m, l) case m: do { i += l; break; } while (0) MASKLEN(0xfe, 7); break; MASKLEN(0xfc, 6); break; MASKLEN(0xf8, 5); break; MASKLEN(0xf0, 4); break; MASKLEN(0xe0, 3); break; MASKLEN(0xc0, 2); break; MASKLEN(0x80, 1); break; #undef MASKLEN } } return i; } static const u_char plent[8] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; static void applyplen(struct in6_addr *ia, int plen) { u_char *p; int i; p = ia->s6_addr; for (i = 0; i < 16; i++) { if (plen <= 0) *p = 0; else if (plen < 8) *p &= plent[plen]; p++, plen -= 8; } } static const int pl2m[9] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; static struct in6_addr * plen2mask(int n) { static struct in6_addr ia; u_char *p; int i; memset(&ia, 0, sizeof(struct in6_addr)); p = (u_char *)&ia; for (i = 0; i < 16; i++, p++, n -= 8) { if (n >= 8) { *p = 0xff; continue; } *p = pl2m[n]; break; } return &ia; } static char * allocopy(char *p) { int len = strlen(p) + 1; char *q = (char *)malloc(len); if (!q) { fatal("malloc"); /*NOTREACHED*/ } strlcpy(q, p, len); return q; } static char * hms(void) { static char buf[BUFSIZ]; time_t t; struct tm *tm; t = time(NULL); if ((tm = localtime(&t)) == 0) { fatal("localtime"); /*NOTREACHED*/ } snprintf(buf, sizeof(buf), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); return buf; } #define RIPRANDDEV 1.0 /* 30 +- 15, max - min = 30 */ static int ripinterval(int timer) { double r = rand(); interval = (int)(timer + timer * RIPRANDDEV * (r / RAND_MAX - 0.5)); nextalarm = time(NULL) + interval; return interval; } #if 0 static time_t ripsuptrig(void) { time_t t; double r = rand(); t = (int)(RIP_TRIG_INT6_MIN + (RIP_TRIG_INT6_MAX - RIP_TRIG_INT6_MIN) * (r / RAND_MAX)); sup_trig_update = time(NULL) + t; return t; } #endif static void fatal(const char *fmt, ...) { va_list ap; char buf[1024]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); perror(buf); if (errno) syslog(LOG_ERR, "%s: %s", buf, strerror(errno)); else syslog(LOG_ERR, "%s", buf); rtdexit(); } static void tracet(int level, const char *fmt, ...) { va_list ap; if (level <= dflag) { va_start(ap, fmt); fprintf(stderr, "%s: ", hms()); vfprintf(stderr, fmt, ap); va_end(ap); } if (dflag) { va_start(ap, fmt); if (level > 0) vsyslog(LOG_DEBUG, fmt, ap); else vsyslog(LOG_WARNING, fmt, ap); va_end(ap); } } static void trace(int level, const char *fmt, ...) { va_list ap; if (level <= dflag) { va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } if (dflag) { va_start(ap, fmt); if (level > 0) vsyslog(LOG_DEBUG, fmt, ap); else vsyslog(LOG_WARNING, fmt, ap); va_end(ap); } } static struct ifc * ifc_find(char *name) { struct ifc *ifcp; TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) { if (strcmp(name, ifcp->ifc_name) == 0) break; } return (ifcp); } static struct iff * iff_find(struct ifc *ifcp, int type) { struct iff *iffp; TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) { if (type == IFIL_TYPE_ANY || type == iffp->iff_type) break; } return (iffp); } static void setindex2ifc(int idx, struct ifc *ifcp) { int n, nsize; struct ifc **p; if (!index2ifc) { nindex2ifc = 5; /*initial guess*/ index2ifc = (struct ifc **) malloc(sizeof(*index2ifc) * nindex2ifc); if (index2ifc == NULL) { fatal("malloc"); /*NOTREACHED*/ } memset(index2ifc, 0, sizeof(*index2ifc) * nindex2ifc); } n = nindex2ifc; for (nsize = nindex2ifc; nsize <= idx; nsize *= 2) ; if (n != nsize) { p = (struct ifc **)realloc(index2ifc, sizeof(*index2ifc) * nsize); if (p == NULL) { fatal("realloc"); /*NOTREACHED*/ } memset(p + n, 0, sizeof(*index2ifc) * (nindex2ifc - n)); index2ifc = p; nindex2ifc = nsize; } index2ifc[idx] = ifcp; }