diff options
Diffstat (limited to 'sbin')
-rw-r--r-- | sbin/routed/parms.c | 621 | ||||
-rw-r--r-- | sbin/routed/rdisc.c | 1031 | ||||
-rw-r--r-- | sbin/routed/rtquery/Makefile | 8 | ||||
-rw-r--r-- | sbin/routed/rtquery/rtquery.8 | 101 | ||||
-rw-r--r-- | sbin/routed/rtquery/rtquery.c | 646 | ||||
-rw-r--r-- | sbin/routed/table.c | 1973 |
6 files changed, 4380 insertions, 0 deletions
diff --git a/sbin/routed/parms.c b/sbin/routed/parms.c new file mode 100644 index 00000000000..281dbcde332 --- /dev/null +++ b/sbin/routed/parms.c @@ -0,0 +1,621 @@ +/* $OpenBSD */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. + */ + +#if !defined(lint) +static char sccsid[] = "@(#)if.c 8.1 (Berkeley) 6/5/93"; +#endif + +#include "defs.h" +#include "pathnames.h" + + +struct parm *parms; +struct intnet *intnets; + + +/* use configured parameters + */ +void +get_parms(struct interface *ifp) +{ + struct parm *parmp; + + /* get all relevant parameters + */ + for (parmp = parms; parmp != 0; parmp = parmp->parm_next) { + if ((parmp->parm_name[0] == '\0' + && on_net(ifp->int_addr, + parmp->parm_addr_h, parmp->parm_mask)) + || (parmp->parm_name[0] != '\0' + && !strcmp(ifp->int_name, parmp->parm_name))) { + /* this group of parameters is relevant, + * so get its settings + */ + ifp->int_state |= parmp->parm_int_state; + if (parmp->parm_passwd[0] != '\0') + bcopy(parmp->parm_passwd, ifp->int_passwd, + sizeof(ifp->int_passwd)); + if (parmp->parm_rdisc_pref != 0) + ifp->int_rdisc_pref = parmp->parm_rdisc_pref; + if (parmp->parm_rdisc_int != 0) + ifp->int_rdisc_int = parmp->parm_rdisc_int; + if (parmp->parm_d_metric != 0) + ifp->int_d_metric = parmp->parm_d_metric; + } + } + /* default poor-man's router discovery to a metric that will + * be heard by old versions of routed. + */ + if ((ifp->int_state & IS_PM_RDISC) + && ifp->int_d_metric == 0) + ifp->int_d_metric = HOPCNT_INFINITY-2; + + if (IS_RIP_IN_OFF(ifp->int_state)) + ifp->int_state |= IS_NO_RIP_OUT; + + if (ifp->int_rdisc_int == 0) + ifp->int_rdisc_int = DefMaxAdvertiseInterval; + + if (!(ifp->int_if_flags & IFF_MULTICAST) + && !(ifp->int_if_flags & IFF_POINTOPOINT)) + ifp->int_state |= IS_NO_RIPV2_OUT; + + if (!(ifp->int_if_flags & IFF_MULTICAST)) + ifp->int_state |= IS_BCAST_RDISC; + + if (ifp->int_if_flags & IFF_POINTOPOINT) { + ifp->int_state |= IS_BCAST_RDISC; + /* By default, point-to-point links should be passive + * about router-discovery for the sake of demand-dialing. + */ + if (0 == (ifp->int_state & GROUP_IS_SOL)) + ifp->int_state |= IS_NO_SOL_OUT; + if (0 == (ifp->int_state & GROUP_IS_ADV)) + ifp->int_state |= IS_NO_ADV_OUT; + } + + if (0 != (ifp->int_state & (IS_PASSIVE | IS_REMOTE))) + ifp->int_state |= IS_NO_RDISC; + if (ifp->int_state & IS_PASSIVE) + ifp->int_state |= (IS_NO_RIP | IS_NO_RDISC); + if ((ifp->int_state & (IS_NO_RIP | IS_NO_RDISC)) + == (IS_NO_RIP|IS_NO_RDISC)) + ifp->int_state |= IS_PASSIVE; +} + + +/* Read a list of gateways from /etc/gateways and add them to our tables. + * + * This file contains a list of "remote" gateways. That is usually + * a gateway which we cannot immediately determine if it is present or + * not as we can do for those provided by directly connected hardware. + * + * If a gateway is marked "passive" in the file, then we assume it + * does not understand RIP and assume it is always present. Those + * not marked passive are treated as if they were directly connected + * and assumed to be broken if they do not send us advertisements. + * All remote interfaces are added to our list, and those not marked + * passive are sent routing updates. + * + * A passive interface can also be local, hardware interface exempt + * from RIP. + */ +void +gwkludge(void) +{ + FILE *fp; + char *p, *lptr; + char lbuf[200], net_host[5], dname[64+1+64+1], gname[64+1], qual[9]; + struct interface *ifp; + naddr dst, netmask, gate; + int metric, n; + u_int state; + char *type; + struct parm *parmp; + + + fp = fopen(_PATH_GATEWAYS, "r"); + if (fp == 0) + return; + + for (;;) { + if (0 == fgets(lbuf, sizeof(lbuf)-1, fp)) + break; + lptr = lbuf; + while (*lptr == ' ') + lptr++; + if (*lptr == '\n' /* ignore null and comment lines */ + || *lptr == '#') + continue; + p = lptr+strlen(lptr)-1; + while (*p == '\n' + || *p == ' ') + *p-- = '\0'; + + /* notice newfangled parameter lines + */ + if (strncasecmp("net", lptr, 3) + && strncasecmp("host", lptr, 4)) { + p = parse_parms(lptr); + if (p != 0) { + if (strcmp(p,lptr)) + msglog("bad \"%s\" in "_PATH_GATEWAYS + " entry \"%s\"", lptr, p); + else + msglog("bad \"%s\" in "_PATH_GATEWAYS, + lptr); + } + continue; + } + +/* {net | host} XX[/M] XX gateway XX metric DD [passive | external]\n */ + n = sscanf(lptr, "%4s %129[^ \t] gateway" + " %64[^ / \t] metric %d %8s\n", + net_host, dname, gname, &metric, qual); + if (n != 5) { + msglog("bad "_PATH_GATEWAYS" entry \"%s\"", lptr); + continue; + } + if (metric < 0 || metric >= HOPCNT_INFINITY) { + msglog("bad metric in "_PATH_GATEWAYS" entry \"%s\"", + lptr); + continue; + } + if (!strcmp(net_host, "host")) { + if (!gethost(dname, &dst)) { + msglog("bad host \"%s\" in "_PATH_GATEWAYS + " entry \"%s\"", dname, lptr); + continue; + } + netmask = HOST_MASK; + } else if (!strcmp(net_host, "net")) { + if (!getnet(dname, &dst, &netmask)) { + msglog("bad net \"%s\" in "_PATH_GATEWAYS + " entry \"%s\"", dname, lptr); + continue; + } + } else { + msglog("bad \"%s\" in "_PATH_GATEWAYS + " entry \"%s\"", lptr); + continue; + } + + if (!gethost(gname, &gate)) { + msglog("bad gateway \"%s\" in "_PATH_GATEWAYS + " entry \"%s\"", gname, lptr); + continue; + } + + if (strcmp(qual, type = "passive") == 0) { + /* Passive entries are not placed in our tables, + * only the kernel's, so we don't copy all of the + * external routing information within a net. + * Internal machines should use the default + * route to a suitable gateway (like us). + */ + state = IS_REMOTE | IS_PASSIVE; + if (metric == 0) + metric = 1; + + } else if (strcmp(qual, type = "external") == 0) { + /* External entries are handled by other means + * such as EGP, and are placed only in the daemon + * tables to prevent overriding them with something + * else. + */ + state = IS_REMOTE | IS_PASSIVE | IS_EXTERNAL; + if (metric == 0) + metric = 1; + + } else if (qual[0] == '\0') { + if (metric != 0) { + /* Entries that are neither "passive" nor + * "external" are "remote" and must behave + * like physical interfaces. If they are not + * heard from regularly, they are deleted. + */ + state = IS_REMOTE; + type = "remote"; + } else { + /* "remote" entries with a metric of 0 + * are aliases for our own interfaces + */ + state = IS_REMOTE | IS_PASSIVE; + type = "alias"; + } + + } else { + msglog("bad "_PATH_GATEWAYS" entry \"%s\"", lptr); + continue; + } + + /* Remember to advertise the corresponding logical network. + */ + if (!(state & IS_EXTERNAL) + && netmask != std_mask(dst)) + state |= IS_SUBNET; + + if (0 != (state & (IS_PASSIVE | IS_REMOTE))) + state |= IS_NO_RDISC; + if (state & IS_PASSIVE) + state |= (IS_NO_RIP | IS_NO_RDISC); + if ((state & (IS_NO_RIP | IS_NO_RDISC)) + == (IS_NO_RIP|IS_NO_RDISC)) + state |= IS_PASSIVE; + + parmp = (struct parm*)malloc(sizeof(*parmp)); + bzero(parmp, sizeof(*parmp)); + parmp->parm_next = parms; + parms = parmp; + parmp->parm_addr_h = ntohl(dst); + parmp->parm_mask = -1; + parmp->parm_d_metric = 0; + parmp->parm_int_state = state; + + /* See if this new interface duplicates an existing + * interface. + */ + for (ifp = ifnet; 0 != ifp; ifp = ifp->int_next) { + if (ifp->int_mask == netmask + && ((ifp->int_addr == dst + && netmask != HOST_MASK) + || (ifp->int_dstaddr == dst + && netmask == HOST_MASK))) + break; + } + if (ifp != 0) { + /* Let one of our real interfaces be marked passive. + */ + if ((state & IS_PASSIVE) && !(state & IS_EXTERNAL)) { + ifp->int_state |= state; + } else { + msglog("%s is duplicated in "_PATH_GATEWAYS + " by %s", + ifp->int_name, lptr); + } + continue; + } + + tot_interfaces++; + + ifp = (struct interface *)malloc(sizeof(*ifp)); + bzero(ifp, sizeof(*ifp)); + if (ifnet != 0) { + ifp->int_next = ifnet; + ifnet->int_prev = ifp; + } + ifnet = ifp; + + ifp->int_state = state; + ifp->int_net = ntohl(dst) & netmask; + ifp->int_mask = netmask; + if (netmask == HOST_MASK) + ifp->int_if_flags |= IFF_POINTOPOINT; + ifp->int_dstaddr = dst; + ifp->int_addr = gate; + ifp->int_metric = metric; + (void)sprintf(ifp->int_name, "%s-%s", type, naddr_ntoa(dst)); + ifp->int_index = -1; + + get_parms(ifp); + + trace_if("Add", ifp); + } +} + + +/* parse a set of parameters for an interface + */ +char * /* 0 or error message */ +parse_parms(char *line) +{ +#define PARS(str) (0 == (tgt = str, strcasecmp(tok, tgt))) +#define PARSE(str) (0 == (tgt = str, strncasecmp(tok, str "=", sizeof(str)))) +#define CKF(g,b) {if (0 != (parm.parm_int_state & ((g) & ~(b)))) break; \ + parm.parm_int_state |= (b);} +#define DELIMS " ,\t\n" + struct parm parm; + struct intnet *intnetp; + char *tok, *tgt, *p; + + + /* "subnet=x.y.z.u/mask" must be alone on the line */ + if (!strncasecmp("subnet=",line,7)) { + intnetp = (struct intnet*)malloc(sizeof(*intnetp)); + intnetp->intnet_metric = 1; + if ((p = strrchr(line,','))) { + *p++ = '\0'; + intnetp->intnet_metric = (int)strtol(p,&p,0); + if (*p != '\0' + || intnetp->intnet_metric <= 0 + || intnetp->intnet_metric >= HOPCNT_INFINITY) + return line; + } + if (!getnet(&line[7], &intnetp->intnet_addr, + &intnetp->intnet_mask) + || intnetp->intnet_mask == HOST_MASK + || intnetp->intnet_addr == RIP_DEFAULT) { + free(intnetp); + return line; + } + intnetp->intnet_next = intnets; + intnets = intnetp; + return 0; + } + + bzero(&parm, sizeof(parm)); + + tgt = "null"; + for (tok = strtok(line, DELIMS); + tok != 0 && tok[0] != '\0'; + tgt = 0, tok = strtok(0,DELIMS)) { + if (PARSE("if")) { + if (parm.parm_name[0] != '\0' + || tok[3] == '\0' + || strlen(tok) > IFNAMSIZ+3) + break; + strcpy(parm.parm_name, tok+3); + + } else if (PARSE("passwd")) { + if (tok[7] == '\0' + || strlen(tok) > RIP_AUTH_PW_LEN+7) + break; + strcpy(parm.parm_passwd, tok+7); + + } else if (PARS("no_ag")) { + parm.parm_int_state |= (IS_NO_AG | IS_NO_SUPER_AG); + + } else if (PARS("no_super_ag")) { + parm.parm_int_state |= IS_NO_SUPER_AG; + + } else if (PARS("no_ripv1_in")) { + parm.parm_int_state |= IS_NO_RIPV1_IN; + + } else if (PARS("no_ripv2_in")) { + parm.parm_int_state |= IS_NO_RIPV2_IN; + + } else if (PARS("ripv2_out")) { + if (parm.parm_int_state & IS_NO_RIPV2_OUT) + break; + parm.parm_int_state |= IS_NO_RIPV1_OUT; + + } else if (PARS("no_rip")) { + parm.parm_int_state |= IS_NO_RIP; + + } else if (PARS("no_rdisc")) { + CKF((GROUP_IS_SOL|GROUP_IS_ADV), IS_NO_RDISC); + + } else if (PARS("no_solicit")) { + CKF(GROUP_IS_SOL, IS_NO_SOL_OUT); + + } else if (PARS("send_solicit")) { + CKF(GROUP_IS_SOL, IS_SOL_OUT); + + } else if (PARS("no_rdisc_adv")) { + CKF(GROUP_IS_ADV, IS_NO_ADV_OUT); + + } else if (PARS("rdisc_adv")) { + CKF(GROUP_IS_ADV, IS_ADV_OUT); + + } else if (PARS("bcast_rdisc")) { + parm.parm_int_state |= IS_BCAST_RDISC; + + } else if (PARS("passive")) { + CKF((GROUP_IS_SOL|GROUP_IS_ADV), IS_NO_RDISC); + parm.parm_int_state |= IS_NO_RIP; + + } else if (PARSE("rdisc_pref")) { + if (parm.parm_rdisc_pref != 0 + || tok[11] == '\0' + || (parm.parm_rdisc_pref = (int)strtol(&tok[11], + &p,0), + *p != '\0')) + break; + + } else if (PARS("pm_rdisc")) { + parm.parm_int_state |= IS_PM_RDISC; + + } else if (PARSE("rdisc_interval")) { + if (parm.parm_rdisc_int != 0 + || tok[15] == '\0' + || (parm.parm_rdisc_int = (int)strtol(&tok[15], + &p,0), + *p != '\0') + || parm.parm_rdisc_int < MinMaxAdvertiseInterval + || parm.parm_rdisc_int > MaxMaxAdvertiseInterval) + break; + + } else if (PARSE("fake_default")) { + if (parm.parm_d_metric != 0 + || tok[13] == '\0' + || (parm.parm_d_metric=(int)strtol(&tok[13],&p,0), + *p != '\0') + || parm.parm_d_metric > HOPCNT_INFINITY-1) + break; + + } else { + tgt = tok; + break; + } + } + if (tgt != 0) + return tgt; + + if (parm.parm_int_state & IS_NO_ADV_IN) + parm.parm_int_state |= IS_NO_SOL_OUT; + + if ((parm.parm_int_state & (IS_NO_RIP | IS_NO_RDISC)) + == (IS_NO_RIP | IS_NO_RDISC)) + parm.parm_int_state |= IS_PASSIVE; + + return check_parms(&parm); +#undef DELIMS +#undef PARS +#undef PARSE +} + + +/* check for duplicate parameter specifications */ +char * /* 0 or error message */ +check_parms(struct parm *new) +{ + struct parm *parmp; + + + for (parmp = parms; parmp != 0; parmp = parmp->parm_next) { + if (strcmp(new->parm_name, parmp->parm_name)) + continue; + if (!on_net(htonl(parmp->parm_addr_h), + new->parm_addr_h, new->parm_mask) + && !on_net(htonl(new->parm_addr_h), + parmp->parm_addr_h, parmp->parm_mask)) + continue; + + if (strcmp(parmp->parm_passwd, new->parm_passwd) + || (0 != (new->parm_int_state & GROUP_IS_SOL) + && 0 != (parmp->parm_int_state & GROUP_IS_SOL) + && 0 != ((new->parm_int_state ^ parmp->parm_int_state) + && GROUP_IS_SOL)) + || (0 != (new->parm_int_state & GROUP_IS_ADV) + && 0 != (parmp->parm_int_state & GROUP_IS_ADV) + && 0 != ((new->parm_int_state ^ parmp->parm_int_state) + && GROUP_IS_ADV)) + || (new->parm_rdisc_pref != 0 + && parmp->parm_rdisc_pref != 0 + && new->parm_rdisc_pref != parmp->parm_rdisc_pref) + || (new->parm_rdisc_int != 0 + && parmp->parm_rdisc_int != 0 + && new->parm_rdisc_int != parmp->parm_rdisc_int) + || (new->parm_d_metric != 0 + && parmp->parm_d_metric != 0 + && new->parm_d_metric != parmp->parm_d_metric)) + return "duplicate"; + } + + parmp = (struct parm*)malloc(sizeof(*parmp)); + bcopy(new, parmp, sizeof(*parmp)); + parmp->parm_next = parms; + parms = parmp; + + return 0; +} + + +/* get a network number as a name or a number, with an optional "/xx" + * netmask. + */ +int /* 0=bad */ +getnet(char *name, + naddr *addrp, /* host byte order */ + naddr *maskp) +{ + int i; + struct netent *np; + naddr mask; + struct in_addr in; + char hname[MAXHOSTNAMELEN+1]; + char *mname, *p; + + + /* Detect and separate "1.2.3.4/24" + */ + if (0 != (mname = rindex(name,'/'))) { + i = (int)(mname - name); + if (i > sizeof(hname)-1) /* name too long */ + return 0; + bcopy(name, hname, i); + hname[i] = '\0'; + mname++; + name = hname; + } + + np = getnetbyname(name); + if (np != 0) { + in.s_addr = (naddr)np->n_net; + } else if (inet_aton(name, &in) == 1) { + HTONL(in.s_addr); + } else { + return 0; + } + + if (mname == 0) { + /* we cannot use the interfaces here because we have not + * looked at them yet. + */ + mask = std_mask(in.s_addr); + if ((~mask & ntohl(in.s_addr)) != 0) + mask = HOST_MASK; + } else { + mask = (naddr)strtoul(mname, &p, 0); + if (*p != '\0' || mask > 32) + return 0; + mask = HOST_MASK << (32-mask); + } + if (mask != 0 && in.s_addr == RIP_DEFAULT) + return 0; + if ((~mask & ntohl(in.s_addr)) != 0) + return 0; + + *addrp = in.s_addr; + *maskp = mask; + return 1; +} + + +int /* 0=bad */ +gethost(char *name, + naddr *addrp) +{ + struct hostent *hp; + struct in_addr in; + + + /* Try for a number first, even in IRIX where gethostbyname() + * is smart. This avoids hitting the name server which + * might be sick because routing is. + */ + if (inet_aton(name, &in) == 1) { + *addrp = in.s_addr; + return 1; + } + + hp = gethostbyname(name); + if (hp) { + bcopy(hp->h_addr, addrp, sizeof(*addrp)); + return 1; + } + + return 0; +} diff --git a/sbin/routed/rdisc.c b/sbin/routed/rdisc.c new file mode 100644 index 00000000000..a0b1690c83d --- /dev/null +++ b/sbin/routed/rdisc.c @@ -0,0 +1,1031 @@ +/* $OpenBSD: rdisc.c,v 1.1 1996/09/05 13:58:56 mickey Exp $ */ + +/* + * Copyright (c) 1995 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. + */ + +#if !defined(lint) +static char sccsid[] = "@(#)rdisc.c 8.1 (Berkeley) x/y/95"; +#endif + +#include "defs.h" +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> + +/* router advertisement ICMP packet */ +struct icmp_ad { + u_int8_t icmp_type; /* type of message */ + u_int8_t icmp_code; /* type sub code */ + u_int16_t icmp_cksum; /* ones complement cksum of struct */ + u_int8_t icmp_ad_num; /* # of following router addresses */ + u_int8_t icmp_ad_asize; /* 2--words in each advertisement */ + u_int16_t icmp_ad_life; /* seconds of validity */ + struct icmp_ad_info { + n_long icmp_ad_addr; + n_long icmp_ad_pref; + } icmp_ad_info[1]; +}; + +/* router solicitation ICMP packet */ +struct icmp_so { + u_int8_t icmp_type; /* type of message */ + u_int8_t icmp_code; /* type sub code */ + u_int16_t icmp_cksum; /* ones complement cksum of struct */ + n_long icmp_so_rsvd; +}; + +union ad_u { + struct icmp icmp; + struct icmp_ad ad; + struct icmp_so so; +}; + + +int rdisc_sock = -1; /* router-discovery raw socket */ +struct interface *rdisc_sock_mcast; /* current multicast interface */ + +struct timeval rdisc_timer; +int rdisc_ok; /* using solicited route */ + + +#define MAX_ADS 5 +struct dr { /* accumulated advertisements */ + struct interface *dr_ifp; + naddr dr_gate; /* gateway */ + time_t dr_ts; /* when received */ + time_t dr_life; /* lifetime */ + n_long dr_recv_pref; /* received but biased preference */ + n_long dr_pref; /* preference adjusted by metric */ +} *cur_drp, drs[MAX_ADS]; + +/* adjust preference by interface metric without driving it to infinity */ +#define PREF(p, ifp) ((p) <= (ifp)->int_metric ? ((p) != 0 ? 1 : 0) \ + : (p) - ((ifp)->int_metric)) + +static void rdisc_sort(void); + + +/* dump an ICMP Router Discovery Advertisement Message + */ +static void +trace_rdisc(char *act, + naddr from, + naddr to, + struct interface *ifp, + union ad_u *p, + u_int len) +{ + int i; + n_long *wp, *lim; + + + if (!TRACEPACKETS || ftrace == 0) + return; + + lastlog(); + + if (p->icmp.icmp_type == ICMP_ROUTERADVERT) { + (void)fprintf(ftrace, "%s Router Ad" + " from %s to %s via %s life=%d\n", + act, naddr_ntoa(from), naddr_ntoa(to), + ifp ? ifp->int_name : "?", + ntohs(p->ad.icmp_ad_life)); + if (!TRACECONTENTS) + return; + + wp = &p->ad.icmp_ad_info[0].icmp_ad_addr; + lim = &wp[(len - sizeof(p->ad)) / sizeof(*wp)]; + for (i = 0; i < p->ad.icmp_ad_num && wp <= lim; i++) { + (void)fprintf(ftrace, "\t%s preference=%#x", + naddr_ntoa(wp[0]), (int)ntohl(wp[1])); + wp += p->ad.icmp_ad_asize; + } + (void)fputc('\n',ftrace); + + } else { + trace_act("%s Router Solic. from %s to %s via %s" + " value=%#x\n", + act, naddr_ntoa(from), naddr_ntoa(to), + ifp ? ifp->int_name : "?", + ntohl(p->so.icmp_so_rsvd)); + } +} + +/* prepare Router Discovery socket. + */ +static void +get_rdisc_sock(void) +{ + if (rdisc_sock < 0) { + rdisc_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (rdisc_sock < 0) + BADERR(1,"rdisc_sock = socket()"); + fix_sock(rdisc_sock,"rdisc_sock"); + fix_select(); + } +} + + +/* Pick multicast group for router-discovery socket + */ +void +set_rdisc_mg(struct interface *ifp, + int on) { /* 0=turn it off */ + struct ip_mreq m; + + if (rdisc_sock < 0) { + /* Create the raw socket so that we can hear at least + * broadcast router discovery packets. + */ + if ((ifp->int_state & IS_NO_RDISC) == IS_NO_RDISC + || !on) + return; + get_rdisc_sock(); + } + + if (!(ifp->int_if_flags & IFF_MULTICAST) + || (ifp->int_state & IS_ALIAS)) { + ifp->int_state &= ~(IS_ALL_HOSTS | IS_ALL_ROUTERS); + return; + } + +#ifdef MCAST_PPP_BUG + if (ifp->int_if_flags & IFF_POINTOPOINT) + return; +#endif + bzero(&m, sizeof(m)); + m.imr_interface.s_addr = ((ifp->int_if_flags & IFF_POINTOPOINT) + ? ifp->int_dstaddr + : ifp->int_addr); + if (supplier + || (ifp->int_state & IS_NO_ADV_IN) + || !on) { + /* stop listening to advertisements + */ + if (ifp->int_state & IS_ALL_HOSTS) { + m.imr_multiaddr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); + if (setsockopt(rdisc_sock, IPPROTO_IP, + IP_DROP_MEMBERSHIP, + &m, sizeof(m)) < 0) + LOGERR("IP_DROP_MEMBERSHIP ALLHOSTS"); + ifp->int_state &= ~IS_ALL_HOSTS; + } + + } else if (!(ifp->int_state & IS_ALL_HOSTS)) { + /* start listening to advertisements + */ + m.imr_multiaddr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); + if (setsockopt(rdisc_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &m, sizeof(m)) < 0) { + LOGERR("IP_ADD_MEMBERSHIP ALLHOSTS"); + } else { + ifp->int_state |= IS_ALL_HOSTS; + } + } + + if (!supplier + || (ifp->int_state & IS_NO_ADV_OUT) + || !on) { + /* stop listening to solicitations + */ + if (ifp->int_state & IS_ALL_ROUTERS) { + m.imr_multiaddr.s_addr=htonl(INADDR_ALLROUTERS_GROUP); + if (setsockopt(rdisc_sock, IPPROTO_IP, + IP_DROP_MEMBERSHIP, + &m, sizeof(m)) < 0) + LOGERR("IP_DROP_MEMBERSHIP ALLROUTERS"); + ifp->int_state &= ~IS_ALL_ROUTERS; + } + + } else if (!(ifp->int_state & IS_ALL_ROUTERS)) { + /* start hearing solicitations + */ + m.imr_multiaddr.s_addr=htonl(INADDR_ALLROUTERS_GROUP); + if (setsockopt(rdisc_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &m, sizeof(m)) < 0) { + LOGERR("IP_ADD_MEMBERSHIP ALLROUTERS"); + } else { + ifp->int_state |= IS_ALL_ROUTERS; + } + } +} + + +/* start supplying routes + */ +void +set_supplier(void) +{ + struct interface *ifp; + struct dr *drp; + + if (supplier_set) + return; + + trace_act("start suppying routes\n"); + + /* Forget discovered routes. + */ + for (drp = drs; drp < &drs[MAX_ADS]; drp++) { + drp->dr_recv_pref = 0; + drp->dr_life = 0; + } + rdisc_age(0); + + supplier_set = 1; + supplier = 1; + + /* Do not start advertising until we have heard some RIP routes */ + LIM_SEC(rdisc_timer, now.tv_sec+MIN_WAITTIME); + + /* Switch router discovery multicast groups from soliciting + * to advertising. + */ + for (ifp = ifnet; ifp; ifp = ifp->int_next) { + if (ifp->int_state & IS_BROKE) + continue; + ifp->int_rdisc_cnt = 0; + ifp->int_rdisc_timer.tv_usec = rdisc_timer.tv_usec; + ifp->int_rdisc_timer.tv_sec = now.tv_sec+MIN_WAITTIME; + set_rdisc_mg(ifp, 1); + } + + /* get rid of any redirects */ + del_redirects(0,0); +} + + +/* age discovered routes and find the best one + */ +void +rdisc_age(naddr bad_gate) +{ + time_t sec; + struct dr *drp; + + + /* If only adverising, then do only that. */ + if (supplier) { + /* if switching from client to server, get rid of old + * default routes. + */ + if (cur_drp != 0) + rdisc_sort(); + rdisc_adv(); + return; + } + + /* If we are being told about a bad router, + * then age the discovered default route, and if there is + * no alternative, solicite a replacement. + */ + if (bad_gate != 0) { + /* Look for the bad discovered default route. + * Age it and note its interface. + */ + for (drp = drs; drp < &drs[MAX_ADS]; drp++) { + if (drp->dr_ts == 0) + continue; + + /* When we find the bad router, then age the route + * to at most SUPPLY_INTERVAL. + * This is contrary to RFC 1256, but defends against + * black holes. + */ + if (drp->dr_gate == bad_gate) { + sec = (now.tv_sec - drp->dr_life + + SUPPLY_INTERVAL); + if (drp->dr_ts > sec) { + trace_act("age 0.0.0.0 --> %s" + " via %s\n", + naddr_ntoa(drp->dr_gate), + drp->dr_ifp->int_name); + drp->dr_ts = sec; + } + break; + } + } + } + + /* delete old redirected routes to keep the kernel table small + */ + sec = (cur_drp == 0) ? MaxMaxAdvertiseInterval : cur_drp->dr_life; + del_redirects(bad_gate, now.tv_sec-sec); + + rdisc_sol(); + + rdisc_sort(); +} + + +/* Zap all routes discovered via an interface that has gone bad + * This should only be called when !(ifp->int_state & IS_ALIAS) + */ +void +if_bad_rdisc(struct interface *ifp) +{ + struct dr *drp; + + for (drp = drs; drp < &drs[MAX_ADS]; drp++) { + if (drp->dr_ifp != ifp) + continue; + drp->dr_recv_pref = 0; + drp->dr_life = 0; + } + + rdisc_sort(); +} + + +/* mark an interface ok for router discovering. + */ +void +if_ok_rdisc(struct interface *ifp) +{ + set_rdisc_mg(ifp, 1); + + ifp->int_rdisc_cnt = 0; + ifp->int_rdisc_timer.tv_sec = now.tv_sec + (supplier + ? MIN_WAITTIME + : MAX_SOLICITATION_DELAY); + if (timercmp(&rdisc_timer, &ifp->int_rdisc_timer, >)) + rdisc_timer = ifp->int_rdisc_timer; +} + + +/* get rid of a dead discovered router + */ +static void +del_rdisc(struct dr *drp) +{ + struct interface *ifp; + int i; + + + del_redirects(drp->dr_gate, 0); + drp->dr_ts = 0; + drp->dr_life = 0; + + + /* Count the other discovered routes on the interface. + */ + i = 0; + ifp = drp->dr_ifp; + for (drp = drs; drp < &drs[MAX_ADS]; drp++) { + if (drp->dr_ts != 0 + && drp->dr_ifp == ifp) + i++; + } + + /* If that was the last good discovered router on the interface, + * then solicit a new one. + * This is contrary to RFC 1256, but defends against black holes. + */ + if (i == 0 + && ifp->int_rdisc_cnt >= MAX_SOLICITATIONS) { + trace_act("discovered route is bad" + "--re-solicit routers via %s\n", ifp->int_name); + ifp->int_rdisc_cnt = 0; + ifp->int_rdisc_timer.tv_sec = 0; + rdisc_sol(); + } +} + + +/* Find the best discovered route, + * and discard stale routers. + */ +static void +rdisc_sort(void) +{ + struct dr *drp, *new_drp; + struct rt_entry *rt; + struct interface *ifp; + u_int new_st; + n_long new_pref; + + + /* Find the best discovered route. + */ + new_drp = 0; + for (drp = drs; drp < &drs[MAX_ADS]; drp++) { + if (drp->dr_ts == 0) + continue; + ifp = drp->dr_ifp; + + /* Get rid of expired discovered routers. + */ + if (drp->dr_ts + drp->dr_life <= now.tv_sec) { + del_rdisc(drp); + continue; + } + + LIM_SEC(rdisc_timer, drp->dr_ts+drp->dr_life+1); + + /* Update preference with possibly changed interface + * metric. + */ + drp->dr_pref = PREF(drp->dr_recv_pref, ifp); + + /* Prefer the current route to prevent thrashing. + * Prefer shorter lifetimes to speed the detection of + * bad routers. + * Avoid sick interfaces. + */ + if (new_drp == 0 + || (!((new_st ^ drp->dr_ifp->int_state) & IS_SICK) + && (new_pref < drp->dr_pref + || (new_pref == drp->dr_pref + && (drp == cur_drp + || (new_drp != cur_drp + && new_drp->dr_life > drp->dr_life))))) + || ((new_st & IS_SICK) + && !(drp->dr_ifp->int_state & IS_SICK))) { + new_drp = drp; + new_st = drp->dr_ifp->int_state; + new_pref = drp->dr_pref; + } + } + + /* switch to a better default route + */ + if (new_drp != cur_drp) { + rt = rtget(RIP_DEFAULT, 0); + + /* Stop using discovered routes if they are all bad + */ + if (new_drp == 0) { + trace_act("turn off Router Discovery client\n"); + rdisc_ok = 0; + + if (rt != 0 + && (rt->rt_state & RS_RDISC)) { + rtchange(rt, rt->rt_state & ~RS_RDISC, + rt->rt_gate, rt->rt_router, + HOPCNT_INFINITY, 0, rt->rt_ifp, + now.tv_sec - GARBAGE_TIME, 0); + rtswitch(rt, 0); + } + + /* turn on RIP if permitted */ + rip_on(0); + + } else { + if (cur_drp == 0) { + trace_act("turn on Router Discovery client" + " using %s via %s\n", + naddr_ntoa(new_drp->dr_gate), + new_drp->dr_ifp->int_name); + + rdisc_ok = 1; + + } else { + trace_act("switch Router Discovery from" + " %s via %s to %s via %s\n", + naddr_ntoa(cur_drp->dr_gate), + cur_drp->dr_ifp->int_name, + naddr_ntoa(new_drp->dr_gate), + new_drp->dr_ifp->int_name); + } + + if (rt != 0) { + rtchange(rt, rt->rt_state | RS_RDISC, + new_drp->dr_gate, new_drp->dr_gate, + 0,0, new_drp->dr_ifp, + now.tv_sec, 0); + } else { + rtadd(RIP_DEFAULT, 0, + new_drp->dr_gate, new_drp->dr_gate, + 0, 0, RS_RDISC, new_drp->dr_ifp); + } + + /* Now turn off RIP and delete RIP routes, + * which might otherwise include the default + * we just modified. + */ + rip_off(); + } + + cur_drp = new_drp; + } +} + + +/* handle a single address in an advertisement + */ +static void +parse_ad(naddr from, + naddr gate, + n_long pref, + u_short life, + struct interface *ifp) +{ + static naddr bad_gate; + struct dr *drp, *new_drp; + + + if (gate == RIP_DEFAULT + || !check_dst(gate)) { + if (bad_gate != from) { + msglog("router %s advertising bad gateway %s", + naddr_ntoa(from), + naddr_ntoa(gate)); + bad_gate = from; + } + return; + } + + /* ignore pointers to ourself and routes via unreachable networks + */ + if (ifwithaddr(gate, 1, 0) != 0) { + trace_pkt("\tdiscard Router Discovery Ad pointing at us\n"); + return; + } + if (!on_net(gate, ifp->int_net, ifp->int_mask)) { + trace_pkt("\tdiscard Router Discovery Ad" + " toward unreachable net\n"); + return; + } + + /* Convert preference to an unsigned value + * and later bias it by the metric of the interface. + */ + pref = ntohl(pref) ^ MIN_PreferenceLevel; + + if (pref == 0 || life == 0) { + pref = 0; + life = 0; + } + + for (new_drp = 0, drp = drs; drp < &drs[MAX_ADS]; drp++) { + /* accept new info for a familiar entry + */ + if (drp->dr_gate == gate) { + new_drp = drp; + break; + } + + if (life == 0) + continue; /* do not worry about dead ads */ + + if (drp->dr_ts == 0) { + new_drp = drp; /* use unused entry */ + + } else if (new_drp == 0) { + /* look for an entry worse than the new one to + * reuse. + */ + if ((!(ifp->int_state & IS_SICK) + && (drp->dr_ifp->int_state & IS_SICK)) + || (pref > drp->dr_pref + && !((ifp->int_state ^ drp->dr_ifp->int_state) + & IS_SICK))) + new_drp = drp; + + } else if (new_drp->dr_ts != 0) { + /* look for the least valueable entry to reuse + */ + if ((!(new_drp->dr_ifp->int_state & IS_SICK) + && (drp->dr_ifp->int_state & IS_SICK)) + || (new_drp->dr_pref > drp->dr_pref + && !((new_drp->dr_ifp->int_state + ^ drp->dr_ifp->int_state) + & IS_SICK))) + new_drp = drp; + } + } + + /* forget it if all of the current entries are better */ + if (new_drp == 0) + return; + + new_drp->dr_ifp = ifp; + new_drp->dr_gate = gate; + new_drp->dr_ts = now.tv_sec; + new_drp->dr_life = ntohs(life); + new_drp->dr_recv_pref = pref; + /* bias functional preference by metric of the interface */ + new_drp->dr_pref = PREF(pref,ifp); + + /* after hearing a good advertisement, stop asking + */ + if (!(ifp->int_state & IS_SICK)) + ifp->int_rdisc_cnt = MAX_SOLICITATIONS; +} + + +/* Compute the IP checksum + * This assumes the packet is less than 32K long. + */ +static u_short +in_cksum(u_short *p, + u_int len) +{ + u_int sum = 0; + int nwords = len >> 1; + + while (nwords-- != 0) + sum += *p++; + + if (len & 1) + sum += *(u_char *)p; + + /* end-around-carry */ + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + return (~sum); +} + + +/* Send a router discovery advertisement or solicitation ICMP packet. + */ +static void +send_rdisc(union ad_u *p, + int p_size, + struct interface *ifp, + naddr dst, /* 0 or unicast destination */ + int type) /* 0=unicast, 1=bcast, 2=mcast */ +{ + struct sockaddr_in sin; + int flags; + char *msg; + naddr tgt_mcast; + + + bzero(&sin, sizeof(sin)); + sin.sin_addr.s_addr = dst; + sin.sin_family = AF_INET; +#ifdef _HAVE_SIN_LEN + sin.sin_len = sizeof(sin); +#endif + flags = MSG_DONTROUTE; + + switch (type) { + case 0: /* unicast */ + msg = "Send"; + break; + + case 1: /* broadcast */ + if (ifp->int_if_flags & IFF_POINTOPOINT) { + msg = "Send pt-to-pt"; + sin.sin_addr.s_addr = ifp->int_dstaddr; + } else { + msg = "Send broadcast"; + sin.sin_addr.s_addr = ifp->int_brdaddr; + } + break; + + case 2: /* multicast */ + msg = "Send multicast"; + if (ifp->int_state & IS_DUP) { + trace_act("abort multicast output via %s" + " with duplicate address\n", + ifp->int_name); + return; + } + if (rdisc_sock_mcast != ifp) { + /* select the right interface. */ +#ifdef MCAST_PPP_BUG + /* Do not specifiy the primary interface explicitly + * if we have the multicast point-to-point kernel + * bug, since the kernel will do the wrong thing + * if the local address of a point-to-point link + * is the same as the address of an ordinary + * interface. + */ + if (ifp->int_addr == myaddr) { + tgt_mcast = 0; + } else +#endif + tgt_mcast = ifp->int_addr; + if (0 > setsockopt(rdisc_sock, + IPPROTO_IP, IP_MULTICAST_IF, + &tgt_mcast, sizeof(tgt_mcast))) { + LOGERR("setsockopt(rdisc_sock," + "IP_MULTICAST_IF)"); + rdisc_sock_mcast = 0; + return; + } + rdisc_sock_mcast = ifp; + } + flags = 0; + break; + } + + if (rdisc_sock < 0) + get_rdisc_sock(); + + trace_rdisc(msg, ifp->int_addr, sin.sin_addr.s_addr, ifp, + p, p_size); + + if (0 > sendto(rdisc_sock, p, p_size, flags, + (struct sockaddr *)&sin, sizeof(sin))) { + if (ifp == 0 || !(ifp->int_state & IS_BROKE)) + msglog("sendto(%s%s%s): %s", + ifp != 0 ? ifp->int_name : "", + ifp != 0 ? ", " : "", + inet_ntoa(sin.sin_addr), + strerror(errno)); + if (ifp != 0) + if_sick(ifp); + } +} + + +/* Send an advertisement + */ +static void +send_adv(struct interface *ifp, + naddr dst, /* 0 or unicast destination */ + int type) /* 0=unicast, 1=bcast, 2=mcast */ +{ + union ad_u u; + n_long pref; + + + bzero(&u,sizeof(u.ad)); + + u.ad.icmp_type = ICMP_ROUTERADVERT; + u.ad.icmp_ad_num = 1; + u.ad.icmp_ad_asize = sizeof(u.ad.icmp_ad_info[0])/4; + + u.ad.icmp_ad_life = stopint ? 0 : htons(ifp->int_rdisc_int*3); + pref = ifp->int_rdisc_pref ^ MIN_PreferenceLevel; + pref = PREF(pref, ifp) ^ MIN_PreferenceLevel; + u.ad.icmp_ad_info[0].icmp_ad_pref = htonl(pref); + + u.ad.icmp_ad_info[0].icmp_ad_addr = ifp->int_addr; + + u.ad.icmp_cksum = in_cksum((u_short*)&u.ad, sizeof(u.ad)); + + send_rdisc(&u, sizeof(u.ad), ifp, dst, type); +} + + +/* Advertise for Router Discovery + */ +void +rdisc_adv(void) +{ + struct interface *ifp; + + + rdisc_timer.tv_sec = now.tv_sec + NEVER; + + for (ifp = ifnet; ifp; ifp = ifp->int_next) { + if (0 != (ifp->int_state & (IS_NO_ADV_OUT + | IS_PASSIVE + | IS_ALIAS + | IS_BROKE))) + continue; + + if (!timercmp(&ifp->int_rdisc_timer, &now, >) + || stopint) { + send_adv(ifp, htonl(INADDR_ALLHOSTS_GROUP), + (ifp->int_state&IS_BCAST_RDISC) ? 1 : 2); + ifp->int_rdisc_cnt++; + + intvl_random(&ifp->int_rdisc_timer, + (ifp->int_rdisc_int*3)/4, + ifp->int_rdisc_int); + if (ifp->int_rdisc_cnt < MAX_INITIAL_ADVERTS + && (ifp->int_rdisc_timer.tv_sec + > MAX_INITIAL_ADVERT_INTERVAL)) { + ifp->int_rdisc_timer.tv_sec + = MAX_INITIAL_ADVERT_INTERVAL; + } + timevaladd(&ifp->int_rdisc_timer, &now); + } + + if (timercmp(&rdisc_timer, &ifp->int_rdisc_timer, >)) + rdisc_timer = ifp->int_rdisc_timer; + } +} + + +/* Solicit for Router Discovery + */ +void +rdisc_sol(void) +{ + struct interface *ifp; + union ad_u u; + + + rdisc_timer.tv_sec = now.tv_sec + NEVER; + + for (ifp = ifnet; ifp; ifp = ifp->int_next) { + if (0 != (ifp->int_state & (IS_NO_SOL_OUT + | IS_PASSIVE + | IS_ALIAS + | IS_BROKE)) + || ifp->int_rdisc_cnt >= MAX_SOLICITATIONS) + continue; + + if (!timercmp(&ifp->int_rdisc_timer, &now, >)) { + bzero(&u,sizeof(u.so)); + u.so.icmp_type = ICMP_ROUTERSOLICIT; + u.so.icmp_cksum = in_cksum((u_short*)&u.so, + sizeof(u.so)); + send_rdisc(&u, sizeof(u.so), ifp, + htonl(INADDR_ALLROUTERS_GROUP), + ((ifp->int_state&IS_BCAST_RDISC) ? 1 : 2)); + + if (++ifp->int_rdisc_cnt >= MAX_SOLICITATIONS) + continue; + + ifp->int_rdisc_timer.tv_sec = SOLICITATION_INTERVAL; + ifp->int_rdisc_timer.tv_usec = 0; + timevaladd(&ifp->int_rdisc_timer, &now); + } + + if (timercmp(&rdisc_timer, &ifp->int_rdisc_timer, >)) + rdisc_timer = ifp->int_rdisc_timer; + } +} + + +/* check the IP header of a possible Router Discovery ICMP packet */ +static struct interface * /* 0 if bad */ +ck_icmp(char *act, + naddr from, + naddr to, + union ad_u *p, + u_int len) +{ + struct interface *ifp; + char *type; + + + /* If we could tell the interface on which a packet from address 0 + * arrived, we could deal with such solicitations. + */ + + ifp = ((from == 0) ? 0 : iflookup(from)); + + if (p->icmp.icmp_type == ICMP_ROUTERADVERT) { + type = "advertisement"; + } else if (p->icmp.icmp_type == ICMP_ROUTERSOLICIT) { + type = "solicitation"; + } else { + return 0; + } + + if (p->icmp.icmp_code != 0) { + trace_pkt("unrecognized ICMP Router" + " %s code=%d from %s to %s\n", + type, p->icmp.icmp_code, + naddr_ntoa(from), naddr_ntoa(to)); + return 0; + } + + trace_rdisc(act, from, to, ifp, p, len); + + if (ifp == 0) + trace_pkt("unknown interface for router-discovery %s" + " from %s to %s", + type, naddr_ntoa(from), naddr_ntoa(to)); + + return ifp; +} + + +/* read packets from the router discovery socket + */ +void +read_d(void) +{ + static naddr bad_asize, bad_len; + struct sockaddr_in from; + int n, fromlen, cc, hlen; + union { + struct ip ip; + u_short s[512/2]; + u_char b[512]; + } pkt; + union ad_u *p; + n_long *wp; + struct interface *ifp; + + + for (;;) { + fromlen = sizeof(from); + cc = recvfrom(rdisc_sock, &pkt, sizeof(pkt), 0, + (struct sockaddr*)&from, + &fromlen); + if (cc <= 0) { + if (cc < 0 && errno != EWOULDBLOCK) + LOGERR("recvfrom(rdisc_sock)"); + break; + } + if (fromlen != sizeof(struct sockaddr_in)) + logbad(1,"impossible recvfrom(rdisc_sock) fromlen=%d", + fromlen); + + hlen = pkt.ip.ip_hl << 2; + if (cc < hlen + ICMP_MINLEN) + continue; + p = (union ad_u *)&pkt.b[hlen]; + cc -= hlen; + + ifp = ck_icmp("Recv", + from.sin_addr.s_addr, pkt.ip.ip_dst.s_addr, + p, cc); + if (ifp == 0) + continue; + if (ifwithaddr(from.sin_addr.s_addr, 0, 0)) { + trace_pkt("\tdiscard our own Router Discovery msg\n"); + continue; + } + + switch (p->icmp.icmp_type) { + case ICMP_ROUTERADVERT: + if (p->ad.icmp_ad_asize*4 + < sizeof(p->ad.icmp_ad_info[0])) { + if (bad_asize != from.sin_addr.s_addr) { + msglog("intolerable rdisc address" + " size=%d", + p->ad.icmp_ad_asize); + bad_asize = from.sin_addr.s_addr; + } + continue; + } + if (p->ad.icmp_ad_num == 0) { + trace_pkt("\tempty?\n"); + continue; + } + if (cc != (sizeof(p->ad) - sizeof(p->ad.icmp_ad_info) + + (p->ad.icmp_ad_num + * sizeof(p->ad.icmp_ad_info[0])))) { + if (bad_len != from.sin_addr.s_addr) { + msglog("rdisc length %d does not" + " match ad_num %d", + cc, p->ad.icmp_ad_num); + bad_len = from.sin_addr.s_addr; + } + continue; + } + if (supplier) + continue; + if (ifp->int_state & IS_NO_ADV_IN) + continue; + + wp = &p->ad.icmp_ad_info[0].icmp_ad_addr; + for (n = 0; n < p->ad.icmp_ad_num; n++) { + parse_ad(from.sin_addr.s_addr, + wp[0], wp[1], + ntohs(p->ad.icmp_ad_life), + ifp); + wp += p->ad.icmp_ad_asize; + } + break; + + + case ICMP_ROUTERSOLICIT: + if (!supplier) + continue; + if (ifp->int_state & IS_NO_ADV_OUT) + continue; + + /* XXX + * We should handle messages from address 0. + */ + + /* Respond with a point-to-point advertisement */ + send_adv(ifp, from.sin_addr.s_addr, 0); + break; + } + } + + rdisc_sort(); +} diff --git a/sbin/routed/rtquery/Makefile b/sbin/routed/rtquery/Makefile new file mode 100644 index 00000000000..331841cf82f --- /dev/null +++ b/sbin/routed/rtquery/Makefile @@ -0,0 +1,8 @@ +# $OpenBSD: Makefile,v 1.1 1996/09/05 13:59:00 mickey Exp $ +# @(#)Makefile 8.1 (Berkeley) 6/5/93 + +PROG= rtquery +MAN= rtquery.8 + +.include "../../Makefile.inc" +.include <bsd.prog.mk> diff --git a/sbin/routed/rtquery/rtquery.8 b/sbin/routed/rtquery/rtquery.8 new file mode 100644 index 00000000000..1afb8b9478b --- /dev/null +++ b/sbin/routed/rtquery/rtquery.8 @@ -0,0 +1,101 @@ +.Dd June 1, 1996 +.Dt RTQUERY 8 +.Os BSD 4.4 +.Sh NAME +.Nm rtquery +.Nd query routing daemons for their routing tables +.Sh SYNOPSIS +.Nm +.Op Fl np1 +.Op Fl w Ar timeout +.Op Fl r Ar addr +.Ar host ... +.Sh DESCRIPTION +.Nm Rtquery +is used to query a network routing daemon, +.Xr routed 8 +or +.Xr gated 8 , +for its routing table by sending a +.Em request +or +.Em poll +command. The routing information in any routing +.Em response +packets returned is displayed numerically and symbolically. +.Pp +.Em Rtquery +by default uses the +.Em request +command. +When the +.Ar -p +option is specified, +.Nm rtquery +uses the +.Em poll +command, an +undocumented extension to the RIP protocol supported by +.Xr gated 8 . +When querying gated, the +.Em poll +command is preferred over the +.I Request +command because the response is not subject to Split Horizon and/or +Poisoned Reverse, and because some versions of gated do not answer +the Request command. Routed does not answer the Poll command, but +recognizes Requests coming from rtquery and so answers completely. +.Pp +.Em Rtquery +is also used to turn tracing on or off in +.Em routed . +.Pp +Options supported by +.Nm rtquery : +.Bl -tag -width Ds +.It Fl n +Normally network and host numbers are displayed both symbolically +and numerically. +The +.Fl n +option displays only the numeric network and host numbers. +.It Fl p +Uses the +.Em Poll +command to request full routing information from +.Xr gated 8 , +This is an undocumented extension RIP protocol supported only by +.Xr gated 8 . +.It Fl 1 +query using RIP version 1 instead of RIP version 2. +.It Fl w Ar timeout +changes the delay for an answer from each host. +By default, each host is given 15 seconds to respond. +.It Fl r Ar addr +ask about the route to destination +.Em addr . +.It Fl t Ar op +change tracing, where +.Em op +is one of the following. +Requests from processes not running with UID 0 or on distant networks +are generally ignored. +.El +.Bl -tag -width Ds -offset indent-two +.It Em on=filename +turn tracing on into the specified file. That file must usually +have been specified when the daemon was started or be the same +as a fixed name, often +.Pa /tmp/routed.log . +.It Em more +increases the debugging level. +.It Em off +turns off tracing. +.El +.Sh SEE ALSO +.Xr routed 8 , +.Xr gated 8 . +.br +RFC\ 1058 - Routing Information Protocol, RIPv1 +.br +RFC\ 1723 - Routing Information Protocol, RIPv2 diff --git a/sbin/routed/rtquery/rtquery.c b/sbin/routed/rtquery/rtquery.c new file mode 100644 index 00000000000..1437334b286 --- /dev/null +++ b/sbin/routed/rtquery/rtquery.c @@ -0,0 +1,646 @@ +/* $OpenBSD: rtquery.c,v 1.1 1996/09/05 13:59:00 mickey Exp $ */ + +/*- + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. + */ + +char copyright[] = +"@(#) Copyright (c) 1982, 1986, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; + +#if !defined(lint) +static char sccsid[] = "@(#)query.c 8.1 (Berkeley) 6/5/93"; +#endif + +#include <sys/param.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <netinet/in.h> +#define RIPVERSION RIPv2 +#include <protocols/routed.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef sgi +#include <strings.h> +#include <bstring.h> +#endif + +#ifndef sgi +#define _HAVE_SIN_LEN +#endif + +#define WTIME 15 /* Time to wait for all responses */ +#define STIME (250*1000) /* usec to wait for another response */ + +int s; + +char *pgmname; + +union { + struct rip rip; + char packet[MAXPACKETSIZE+MAXPATHLEN]; +} omsg_buf; +#define OMSG omsg_buf.rip +int omsg_len = sizeof(struct rip); + +union { + struct rip rip; + char packet[MAXPACKETSIZE+1024]; + } imsg_buf; +#define IMSG imsg_buf.rip + +int nflag; /* numbers, no names */ +int pflag; /* play the `gated` game */ +int ripv2 = 1; /* use RIP version 2 */ +int wtime = WTIME; +int rflag; /* 1=ask about a particular route */ +int trace; +int not_trace; + +struct timeval sent; /* when query sent */ + +static void rip_input(struct sockaddr_in*, int); +static int out(char *); +static void trace_loop(char *argv[]); +static void query_loop(char *argv[], int); +static int getnet(char *, struct netinfo *); +static u_int std_mask(u_int); + + +int +main(int argc, + char *argv[]) +{ + int ch, bsize; + char *p, *options, *value; + + OMSG.rip_nets[0].n_dst = RIP_DEFAULT; + OMSG.rip_nets[0].n_family = RIP_AF_UNSPEC; + OMSG.rip_nets[0].n_metric = htonl(HOPCNT_INFINITY); + + pgmname = argv[0]; + while ((ch = getopt(argc, argv, "np1w:r:t:")) != EOF) + switch (ch) { + case 'n': + not_trace = 1; + nflag = 1; + break; + + case 'p': + not_trace = 1; + pflag = 1; + break; + + case '1': + ripv2 = 0; + break; + + case 'w': + not_trace = 1; + wtime = (int)strtoul(optarg, &p, 0); + if (*p != '\0' + || wtime <= 0) + goto usage; + break; + + case 'r': + not_trace = 1; + if (rflag) + goto usage; + rflag = getnet(optarg, &OMSG.rip_nets[0]); + if (!rflag) { + struct hostent *hp = gethostbyname(optarg); + if (hp == 0) { + fprintf(stderr, "%s: %s:", + pgmname, optarg); + herror(0); + exit(1); + } + bcopy(hp->h_addr, &OMSG.rip_nets[0].n_dst, + sizeof(OMSG.rip_nets[0].n_dst)); + OMSG.rip_nets[0].n_family = RIP_AF_INET; + OMSG.rip_nets[0].n_mask = -1; + rflag = 1; + } + break; + + case 't': + trace = 1; + options = optarg; + while (*options != '\0') { + char *traceopts[] = { +# define TRACE_ON 0 + "on", +# define TRACE_MORE 1 + "more", +# define TRACE_OFF 2 + "off", + 0 + }; + switch (getsubopt(&options,traceopts,&value)) { + case TRACE_ON: + OMSG.rip_cmd = RIPCMD_TRACEON; + if (!value + || strlen(value) > MAXPATHLEN) + goto usage; + strcpy((char*)OMSG.rip_tracefile,value); + omsg_len += (strlen(value) + - sizeof(OMSG.ripun)); + break; + case TRACE_MORE: + if (value) + goto usage; + OMSG.rip_cmd = RIPCMD_TRACEON; + OMSG.rip_tracefile[0] = '\0'; + break; + case TRACE_OFF: + if (value) + goto usage; + OMSG.rip_cmd = RIPCMD_TRACEOFF; + OMSG.rip_tracefile[0] = '\0'; + break; + default: + goto usage; + } + } + break; + + default: + goto usage; + } + argv += optind; + argc -= optind; + if ((not_trace && trace) || argc == 0) { +usage: fprintf(stderr, "%s: [-np1v] [-r tgt_rt] [-w wtime]" + " host1 [host2 ...]\n" + "or\t-t {on=filename|more|off} host1 host2 ...\n", + pgmname); + exit(1); + } + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket"); + exit(2); + } + + /* be prepared to receive a lot of routes */ + for (bsize = 127*1024; ; bsize -= 1024) { + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, + &bsize, sizeof(bsize)) == 0) + break; + if (bsize <= 4*1024) { + perror("setsockopt SO_RCVBUF"); + break; + } + } + + if (trace) + trace_loop(argv); + else + query_loop(argv, argc); + /* NOTREACHED */ +} + + +/* tell the target hosts about tracing + */ +static void +trace_loop(char *argv[]) +{ + struct sockaddr_in myaddr; + int res; + + if (geteuid() != 0) { + (void)fprintf(stderr, "-t requires UID 0\n"); + exit(1); + } + + if (ripv2) { + OMSG.rip_vers = RIPv2; + } else { + OMSG.rip_vers = RIPv1; + } + + bzero(&myaddr, sizeof(myaddr)); + myaddr.sin_family = AF_INET; +#ifdef _HAVE_SIN_LEN + myaddr.sin_len = sizeof(myaddr); +#endif + myaddr.sin_port = htons(IPPORT_RESERVED-1); + while (bind(s, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) { + if (errno != EADDRINUSE + || myaddr.sin_port == 0) { + perror("bind"); + exit(2); + } + myaddr.sin_port = htons(ntohs(myaddr.sin_port)-1); + } + + res = 1; + while (*argv != 0) { + if (out(*argv++) <= 0) + res = 0; + } + exit(res); +} + + +/* query all of the listed hosts + */ +static void +query_loop(char *argv[], int argc) +{ + struct seen { + struct seen *next; + struct in_addr addr; + } *seen, *sp; + int answered = 0; + int cc; + fd_set bits; + struct timeval now, delay; + struct sockaddr_in from; + int fromlen; + + + OMSG.rip_cmd = (pflag) ? RIPCMD_POLL : RIPCMD_REQUEST; + if (ripv2) { + OMSG.rip_vers = RIPv2; + } else { + OMSG.rip_vers = RIPv1; + OMSG.rip_nets[0].n_mask = 0; + } + + /* ask the first (valid) host */ + seen = 0; + while (0 > out(*argv++)) { + if (*argv == 0) + exit(-1); + answered++; + } + + FD_ZERO(&bits); + for (;;) { + FD_SET(s, &bits); + delay.tv_sec = 0; + delay.tv_usec = STIME; + cc = select(s+1, &bits, 0,0, &delay); + if (cc > 0) { + fromlen = sizeof(from); + cc = recvfrom(s, imsg_buf.packet, + sizeof(imsg_buf.packet), 0, + (struct sockaddr *)&from, &fromlen); + if (cc < 0) { + perror("recvfrom"); + exit(1); + } + /* count the distinct responding hosts. + * You cannot match responding hosts with + * addresses to which queries were transmitted, + * because a router might respond with a + * different source address. + */ + for (sp = seen; sp != 0; sp = sp->next) { + if (sp->addr.s_addr == from.sin_addr.s_addr) + break; + } + if (sp == 0) { + sp = malloc(sizeof(*sp)); + sp->addr = from.sin_addr; + sp->next = seen; + seen = sp; + answered++; + } + + rip_input(&from, cc); + continue; + } + + if (cc < 0) { + if ( errno == EINTR) + continue; + perror("select"); + exit(1); + } + + /* After a pause in responses, probe another host. + * This reduces the intermingling of answers. + */ + while (*argv != 0 && 0 > out(*argv++)) + answered++; + + /* continue until no more packets arrive + * or we have heard from all hosts + */ + if (answered >= argc) + break; + + /* or until we have waited a long time + */ + if (gettimeofday(&now, 0) < 0) { + perror("gettimeofday(now)"); + exit(1); + } + if (sent.tv_sec + wtime <= now.tv_sec) + break; + } + + /* fail if there was no answer */ + exit (answered >= argc ? 0 : 1); +} + + +/* sent do one host + */ +static int +out(char *host) +{ + struct sockaddr_in router; + struct hostent *hp; + + if (gettimeofday(&sent, 0) < 0) { + perror("gettimeofday(sent)"); + return -1; + } + + bzero(&router, sizeof(router)); + router.sin_family = AF_INET; +#ifdef _HAVE_SIN_LEN + router.sin_len = sizeof(router); +#endif + if (!inet_aton(host, &router.sin_addr)) { + hp = gethostbyname(host); + if (hp == 0) { + herror(host); + return -1; + } + bcopy(hp->h_addr, &router.sin_addr, sizeof(router.sin_addr)); + } + router.sin_port = htons(RIP_PORT); + + if (sendto(s, &omsg_buf, omsg_len, 0, + (struct sockaddr *)&router, sizeof(router)) < 0) { + perror(host); + return -1; + } + + return 0; +} + + +/* + * Handle an incoming RIP packet. + */ +static void +rip_input(struct sockaddr_in *from, + int size) +{ + struct netinfo *n, *lim; + struct in_addr in; + char *name; + char net_buf[80]; + u_int mask, dmask; + char *sp; + int i; + struct hostent *hp; + struct netent *np; + struct netauth *a; + + + if (nflag) { + printf("%s:", inet_ntoa(from->sin_addr)); + } else { + hp = gethostbyaddr((char*)&from->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hp == 0) { + printf("%s:", + inet_ntoa(from->sin_addr)); + } else { + printf("%s (%s):", hp->h_name, + inet_ntoa(from->sin_addr)); + } + } + if (IMSG.rip_cmd != RIPCMD_RESPONSE) { + printf("\n unexpected response type %d\n", IMSG.rip_cmd); + return; + } + printf(" RIPv%d%s %d bytes\n", IMSG.rip_vers, + (IMSG.rip_vers != RIPv1 && IMSG.rip_vers != RIPv2) ? " ?" : "", + size); + if (size > MAXPACKETSIZE) { + if (size > sizeof(imsg_buf) - sizeof(*n)) { + printf(" at least %d bytes too long\n", + size-MAXPACKETSIZE); + size = sizeof(imsg_buf) - sizeof(*n); + } else { + printf(" %d bytes too long\n", + size-MAXPACKETSIZE); + } + } else if (size%sizeof(*n) != sizeof(struct rip)%sizeof(*n)) { + printf(" response of bad length=%d\n", size); + } + + n = IMSG.rip_nets; + lim = (struct netinfo *)((char*)n + size) - 1; + for (; n <= lim; n++) { + name = ""; + if (n->n_family == RIP_AF_INET) { + in.s_addr = n->n_dst; + (void)strcpy(net_buf, inet_ntoa(in)); + + mask = ntohl(n->n_mask); + dmask = mask & -mask; + if (mask != 0) { + sp = &net_buf[strlen(net_buf)]; + if (IMSG.rip_vers == RIPv1) { + (void)sprintf(sp," mask=%#x ? ",mask); + mask = 0; + } else if (mask + dmask == 0) { + for (i = 0; + (i != 32 + && ((1<<i)&mask) == 0); + i++) + continue; + (void)sprintf(sp, "/%d",32-i); + } else { + (void)sprintf(sp," (mask %#x)", mask); + } + } + + if (!nflag) { + if (mask == 0) { + mask = std_mask(in.s_addr); + if ((ntohl(in.s_addr) & ~mask) != 0) + mask = 0; + } + /* Without a netmask, do not worry about + * whether the destination is a host or a + * network. Try both and use the first name + * we get. + * + * If we have a netmask we can make a + * good guess. + */ + if ((in.s_addr & ~mask) == 0) { + np = getnetbyaddr((long)in.s_addr, + AF_INET); + if (np != 0) + name = np->n_name; + else if (in.s_addr == 0) + name = "default"; + } + if (name[0] == '\0' + && ((in.s_addr & ~mask) != 0 + || mask == 0xffffffff)) { + hp = gethostbyaddr((char*)&in, + sizeof(in), + AF_INET); + if (hp != 0) + name = hp->h_name; + } + } + + } else if (n->n_family == RIP_AF_AUTH) { + a = (struct netauth*)n; + (void)printf(" authentication type %d: ", + a->a_type); + for (i = 0; i < sizeof(a->au.au_pw); i++) + (void)printf("%02x ", a->au.au_pw[i]); + putc('\n', stdout); + continue; + + } else { + (void)sprintf(net_buf, "(af %#x) %d.%d.%d.%d", + ntohs(n->n_family), + (char)(n->n_dst >> 24), + (char)(n->n_dst >> 16), + (char)(n->n_dst >> 8), + (char)n->n_dst); + } + + (void)printf(" %-18s metric %2d %-10s", + net_buf, ntohl(n->n_metric), name); + + if (n->n_nhop != 0) { + in.s_addr = n->n_nhop; + if (nflag) + hp = 0; + else + hp = gethostbyaddr((char*)&in, sizeof(in), + AF_INET); + (void)printf(" nhop=%-15s%s", + (hp != 0) ? hp->h_name : inet_ntoa(in), + (IMSG.rip_vers == RIPv1) ? " ?" : ""); + } + if (n->n_tag != 0) + (void)printf(" tag=%#x%s", n->n_tag, + (IMSG.rip_vers == RIPv1) ? " ?" : ""); + putc('\n', stdout); + } +} + + +/* Return the classical netmask for an IP address. + */ +static u_int +std_mask(u_int addr) /* in network order */ +{ + NTOHL(addr); /* was a host, not a network */ + + if (addr == 0) /* default route has mask 0 */ + return 0; + if (IN_CLASSA(addr)) + return IN_CLASSA_NET; + if (IN_CLASSB(addr)) + return IN_CLASSB_NET; + return IN_CLASSC_NET; +} + + +/* get a network number as a name or a number, with an optional "/xx" + * netmask. + */ +static int /* 0=bad */ +getnet(char *name, + struct netinfo *rt) +{ + int i; + struct netent *nentp; + u_int mask; + struct in_addr in; + char hname[MAXHOSTNAMELEN+1]; + char *mname, *p; + + + /* Detect and separate "1.2.3.4/24" + */ + if (0 != (mname = rindex(name,'/'))) { + i = (int)(mname - name); + if (i > sizeof(hname)-1) /* name too long */ + return 0; + bcopy(name, hname, i); + hname[i] = '\0'; + mname++; + name = hname; + } + + nentp = getnetbyname(name); + if (nentp != 0) { + in.s_addr = nentp->n_net; + } else if (inet_aton(name, &in) == 1) { + NTOHL(in.s_addr); + } else { + return 0; + } + + if (mname == 0) { + mask = std_mask(in.s_addr); + if ((~mask & in.s_addr) != 0) + mask = 0xffffffff; + } else { + mask = (u_int)strtoul(mname, &p, 0); + if (*p != '\0' || mask > 32) + return 0; + mask = 0xffffffff << (32-mask); + } + + rt->n_dst = htonl(in.s_addr); + rt->n_family = RIP_AF_INET; + rt->n_mask = htonl(mask); + return 1; +} diff --git a/sbin/routed/table.c b/sbin/routed/table.c new file mode 100644 index 00000000000..a8a18118875 --- /dev/null +++ b/sbin/routed/table.c @@ -0,0 +1,1973 @@ +/* $OpenBSD: table.c,v 1.1 1996/09/05 13:58:58 mickey Exp $ */ + +/* + * Copyright (c) 1983, 1988, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. + */ + +#if !defined(lint) +static char sccsid[] = "@(#)tables.c 8.1 (Berkeley) 6/5/93"; +#endif + +#include "defs.h" + +static struct rt_spare *rts_better(struct rt_entry *); + +struct radix_node_head *rhead; /* root of the radix tree */ + +int need_flash = 1; /* flash update needed + * start =1 to suppress the 1st + */ + +struct timeval age_timer; /* next check of old routes */ +struct timeval need_kern = { /* need to update kernel table */ + EPOCH+MIN_WAITTIME-1 +}; + +int stopint; + +int total_routes; + +naddr age_bad_gate; + + +/* It is desirable to "aggregate" routes, to combine differing routes of + * the same metric and next hop into a common route with a smaller netmask + * or to suppress redundant routes, routes that add no information to + * routes with smaller netmasks. + * + * A route is redundant if and only if any and all routes with smaller + * but matching netmasks and nets are the same. Since routes are + * kept sorted in the radix tree, redundant routes always come second. + * + * There are two kinds of aggregations. First, two routes of the same bit + * mask and differing only in the least significant bit of the network + * number can be combined into a single route with a coarser mask. + * + * Second, a route can be suppressed in favor of another route with a more + * coarse mask provided no incompatible routes with intermediate masks + * are present. The second kind of aggregation involves suppressing routes. + * A route must not be suppressed if an incompatible route exists with + * an intermediate mask, since the suppressed route would be covered + * by the intermediate. + * + * This code relies on the radix tree walk encountering routes + * sorted first by address, with the smallest address first. + */ + +struct ag_info ag_slots[NUM_AG_SLOTS], *ag_avail, *ag_corsest, *ag_finest; + +/* #define DEBUG_AG */ +#ifdef DEBUG_AG +#define CHECK_AG() {int acnt = 0; struct ag_info *cag; \ + for (cag = ag_avail; cag != 0; cag = cag->ag_fine) \ + acnt++; \ + for (cag = ag_corsest; cag != 0; cag = cag->ag_fine) \ + acnt++; \ + if (acnt != NUM_AG_SLOTS) { \ + (void)fflush(stderr); \ + abort(); \ + } \ +} +#else +#define CHECK_AG() +#endif + + +/* Output the contents of an aggregation table slot. + * This function must always be immediately followed with the deletion + * of the target slot. + */ +static void +ag_out(struct ag_info *ag, + void (*out)(struct ag_info *)) +{ + struct ag_info *ag_cors; + naddr bit; + + + /* If we output both the even and odd twins, then the immediate parent, + * if it is present, is redundant, unless the parent manages to + * aggregate into something coarser. + * On successive calls, this code detects the even and odd twins, + * and marks the parent. + * + * Note that the order in which the radix tree code emits routes + * ensures that the twins are seen before the parent is emitted. + */ + ag_cors = ag->ag_cors; + if (ag_cors != 0 + && ag_cors->ag_mask == ag->ag_mask<<1 + && ag_cors->ag_dst_h == (ag->ag_dst_h & ag_cors->ag_mask)) { + ag_cors->ag_state |= ((ag_cors->ag_dst_h == ag->ag_dst_h) + ? AGS_REDUN0 + : AGS_REDUN1); + } + + /* Skip it if this route is itself redundant. + * + * It is ok to change the contents of the slot here, since it is + * always deleted next. + */ + if (ag->ag_state & AGS_REDUN0) { + if (ag->ag_state & AGS_REDUN1) + return; + bit = (-ag->ag_mask) >> 1; + ag->ag_dst_h |= bit; + ag->ag_mask |= bit; + + } else if (ag->ag_state & AGS_REDUN1) { + bit = (-ag->ag_mask) >> 1; + ag->ag_mask |= bit; + } + out(ag); +} + + +static void +ag_del(struct ag_info *ag) +{ + CHECK_AG(); + + if (ag->ag_cors == 0) + ag_corsest = ag->ag_fine; + else + ag->ag_cors->ag_fine = ag->ag_fine; + + if (ag->ag_fine == 0) + ag_finest = ag->ag_cors; + else + ag->ag_fine->ag_cors = ag->ag_cors; + + ag->ag_fine = ag_avail; + ag_avail = ag; + + CHECK_AG(); +} + + +/* Flush routes waiting for aggretation. + * This must not suppress a route unless it is known that among all + * routes with coarser masks that match it, the one with the longest + * mask is appropriate. This is ensured by scanning the routes + * in lexical order, and with the most restritive mask first + * among routes to the same destination. + */ +void +ag_flush(naddr lim_dst_h, /* flush routes to here */ + naddr lim_mask, /* matching this mask */ + void (*out)(struct ag_info *)) +{ + struct ag_info *ag, *ag_cors; + naddr dst_h; + + + for (ag = ag_finest; + ag != 0 && ag->ag_mask >= lim_mask; + ag = ag_cors) { + ag_cors = ag->ag_cors; + + /* work on only the specified routes */ + dst_h = ag->ag_dst_h; + if ((dst_h & lim_mask) != lim_dst_h) + continue; + + if (!(ag->ag_state & AGS_SUPPRESS)) + ag_out(ag, out); + + else for ( ; ; ag_cors = ag_cors->ag_cors) { + /* Look for a route that can suppress the + * current route */ + if (ag_cors == 0) { + /* failed, so output it and look for + * another route to work on + */ + ag_out(ag, out); + break; + } + + if ((dst_h & ag_cors->ag_mask) == ag_cors->ag_dst_h) { + /* We found a route with a coarser mask that + * aggregates the current target. + * + * If it has a different next hop, it + * cannot replace the target, so output + * the target. + */ + if (ag->ag_gate != ag_cors->ag_gate + && !(ag->ag_state & AGS_FINE_GATE) + && !(ag_cors->ag_state & AGS_CORS_GATE)) { + ag_out(ag, out); + break; + } + + /* If the coarse route has a good enough + * metric, it suppresses the target. + */ + if (ag_cors->ag_pref <= ag->ag_pref) { + if (ag_cors->ag_seqno > ag->ag_seqno) + ag_cors->ag_seqno = ag->ag_seqno; + if (AG_IS_REDUN(ag->ag_state) + && ag_cors->ag_mask==ag->ag_mask<<1) { + if (ag_cors->ag_dst_h == dst_h) + ag_cors->ag_state |= AGS_REDUN0; + else + ag_cors->ag_state |= AGS_REDUN1; + } + if (ag->ag_tag != ag_cors->ag_tag) + ag_cors->ag_tag = 0; + if (ag->ag_nhop != ag_cors->ag_nhop) + ag_cors->ag_nhop = 0; + break; + } + } + } + + /* That route has either been output or suppressed */ + ag_cors = ag->ag_cors; + ag_del(ag); + } + + CHECK_AG(); +} + + +/* Try to aggregate a route with previous routes. + */ +void +ag_check(naddr dst, + naddr mask, + naddr gate, + naddr nhop, + char metric, + char pref, + u_int seqno, + u_short tag, + u_short state, + void (*out)(struct ag_info *)) /* output using this */ +{ + struct ag_info *ag, *nag, *ag_cors; + naddr xaddr; + int x; + + NTOHL(dst); + + /* Punt non-contiguous subnet masks. + * + * (X & -X) contains a single bit if and only if X is a power of 2. + * (X + (X & -X)) == 0 if and only if X is a power of 2. + */ + if ((mask & -mask) + mask != 0) { + struct ag_info nc_ag; + + nc_ag.ag_dst_h = dst; + nc_ag.ag_mask = mask; + nc_ag.ag_gate = gate; + nc_ag.ag_nhop = nhop; + nc_ag.ag_metric = metric; + nc_ag.ag_pref = pref; + nc_ag.ag_tag = tag; + nc_ag.ag_state = state; + nc_ag.ag_seqno = seqno; + out(&nc_ag); + return; + } + + /* Search for the right slot in the aggregation table. + */ + ag_cors = 0; + ag = ag_corsest; + while (ag != 0) { + if (ag->ag_mask >= mask) + break; + + /* Suppress old routes (i.e. combine with compatible routes + * with coarser masks) as we look for the right slot in the + * aggregation table for the new route. + * A route to an address less than the current destination + * will not be affected by the current route or any route + * seen hereafter. That means it is safe to suppress it. + * This check keeps poor routes (eg. with large hop counts) + * from preventing suppresion of finer routes. + */ + if (ag_cors != 0 + && ag->ag_dst_h < dst + && (ag->ag_state & AGS_SUPPRESS) + && ag_cors->ag_pref <= ag->ag_pref + && (ag->ag_dst_h & ag_cors->ag_mask) == ag_cors->ag_dst_h + && (ag_cors->ag_gate == ag->ag_gate + || (ag->ag_state & AGS_FINE_GATE) + || (ag_cors->ag_state & AGS_CORS_GATE))) { + if (ag_cors->ag_seqno > ag->ag_seqno) + ag_cors->ag_seqno = ag->ag_seqno; + if (AG_IS_REDUN(ag->ag_state) + && ag_cors->ag_mask==ag->ag_mask<<1) { + if (ag_cors->ag_dst_h == dst) + ag_cors->ag_state |= AGS_REDUN0; + else + ag_cors->ag_state |= AGS_REDUN1; + } + if (ag->ag_tag != ag_cors->ag_tag) + ag_cors->ag_tag = 0; + if (ag->ag_nhop != ag_cors->ag_nhop) + ag_cors->ag_nhop = 0; + ag_del(ag); + CHECK_AG(); + } else { + ag_cors = ag; + } + ag = ag_cors->ag_fine; + } + + /* If we find the even/odd twin of the new route, and if the + * masks and so forth are equal, we can aggregate them. + * We can probably promote one of the pair. + * + * Since the routes are encountered in lexical order, + * the new route must be odd. However, the second or later + * times around this loop, it could be the even twin promoted + * from the even/odd pair of twins of the finer route. + */ + while (ag != 0 + && ag->ag_mask == mask + && ((ag->ag_dst_h ^ dst) & (mask<<1)) == 0) { + + /* Here we know the target route and the route in the current + * slot have the same netmasks and differ by at most the + * last bit. They are either for the same destination, or + * for an even/odd pair of destinations. + */ + if (ag->ag_dst_h == dst) { + /* We have two routes to the same destination. + * Routes are encountered in lexical order, so a + * route is never promoted until the parent route is + * already present. So we know that the new route is + * a promoted pair and the route already in the slot + * is the explicit route. + * + * Prefer the best route if their metrics differ, + * or the promoted one if not, following a sort + * of longest-match rule. + */ + if (pref <= ag->ag_pref) { + ag->ag_gate = gate; + ag->ag_nhop = nhop; + ag->ag_tag = tag; + ag->ag_metric = metric; + ag->ag_pref = pref; + x = ag->ag_state; + ag->ag_state = state; + state = x; + } + + /* The sequence number controls flash updating, + * and should be the smaller of the two. + */ + if (ag->ag_seqno > seqno) + ag->ag_seqno = seqno; + + /* some bits are set if they are set on either route */ + ag->ag_state |= (state & (AGS_PROMOTE_EITHER + | AGS_REDUN0 | AGS_REDUN1)); + return; + } + + /* If one of the routes can be promoted and the other can + * be suppressed, it may be possible to combine them or + * worthwhile to promote one. + * + * Note that any route that can be promoted is always + * marked to be eligible to be suppressed. + */ + if (!((state & AGS_PROMOTE) + && (ag->ag_state & AGS_SUPPRESS)) + && !((ag->ag_state & AGS_PROMOTE) + && (state & AGS_SUPPRESS))) + break; + + /* A pair of even/odd twin routes can be combined + * if either is redundant, or if they are via the + * same gateway and have the same metric. + */ + if (AG_IS_REDUN(ag->ag_state) + || AG_IS_REDUN(state) + || (ag->ag_gate == gate + && ag->ag_pref == pref + && (state & ag->ag_state & AGS_PROMOTE) != 0)) { + + /* We have both the even and odd pairs. + * Since the routes are encountered in order, + * the route in the slot must be the even twin. + * + * Combine and promote the pair of routes. + */ + if (seqno > ag->ag_seqno) + seqno = ag->ag_seqno; + if (!AG_IS_REDUN(state)) + state &= ~AGS_REDUN1; + if (AG_IS_REDUN(ag->ag_state)) + state |= AGS_REDUN0; + else + state &= ~AGS_REDUN0; + state |= (ag->ag_state & AGS_PROMOTE_EITHER); + if (ag->ag_tag != tag) + tag = 0; + if (ag->ag_nhop != nhop) + nhop = 0; + + /* Get rid of the even twin that was already + * in the slot. + */ + ag_del(ag); + + } else if (ag->ag_pref >= pref + && (ag->ag_state & AGS_PROMOTE)) { + /* If we cannot combine the pair, maybe the route + * with the worse metric can be promoted. + * + * Promote the old, even twin, by giving its slot + * in the table to the new, odd twin. + */ + ag->ag_dst_h = dst; + + xaddr = ag->ag_gate; + ag->ag_gate = gate; + gate = xaddr; + + xaddr = ag->ag_nhop; + ag->ag_nhop = nhop; + nhop = xaddr; + + x = ag->ag_tag; + ag->ag_tag = tag; + tag = x; + + x = ag->ag_state; + ag->ag_state = state; + state = x; + if (!AG_IS_REDUN(state)) + state &= ~AGS_REDUN0; + + x = ag->ag_metric; + ag->ag_metric = metric; + metric = x; + + x = ag->ag_pref; + ag->ag_pref = pref; + pref = x; + + if (seqno >= ag->ag_seqno) + seqno = ag->ag_seqno; + else + ag->ag_seqno = seqno; + + } else { + if (!(state & AGS_PROMOTE)) + break; /* cannot promote either twin */ + + /* promote the new, odd twin by shaving its + * mask and address. + */ + if (seqno > ag->ag_seqno) + seqno = ag->ag_seqno; + else + ag->ag_seqno = seqno; + if (!AG_IS_REDUN(state)) + state &= ~AGS_REDUN1; + } + + mask <<= 1; + dst &= mask; + + if (ag_cors == 0) { + ag = ag_corsest; + break; + } + ag = ag_cors; + ag_cors = ag->ag_cors; + } + + /* When we can no longer promote and combine routes, + * flush the old route in the target slot. Also flush + * any finer routes that we know will never be aggregated by + * the new route. + * + * In case we moved toward coarser masks, + * get back where we belong + */ + if (ag != 0 + && ag->ag_mask < mask) { + ag_cors = ag; + ag = ag->ag_fine; + } + + /* Empty the target slot + */ + if (ag != 0 && ag->ag_mask == mask) { + ag_flush(ag->ag_dst_h, ag->ag_mask, out); + ag = (ag_cors == 0) ? ag_corsest : ag_cors->ag_fine; + } + +#ifdef DEBUG_AG + (void)fflush(stderr); + if (ag == 0 && ag_cors != ag_finest) + abort(); + if (ag_cors == 0 && ag != ag_corsest) + abort(); + if (ag != 0 && ag->ag_cors != ag_cors) + abort(); + if (ag_cors != 0 && ag_cors->ag_fine != ag) + abort(); + CHECK_AG(); +#endif + + /* Save the new route on the end of the table. + */ + nag = ag_avail; + ag_avail = nag->ag_fine; + + nag->ag_dst_h = dst; + nag->ag_mask = mask; + nag->ag_gate = gate; + nag->ag_nhop = nhop; + nag->ag_metric = metric; + nag->ag_pref = pref; + nag->ag_tag = tag; + nag->ag_state = state; + nag->ag_seqno = seqno; + + nag->ag_fine = ag; + if (ag != 0) + ag->ag_cors = nag; + else + ag_finest = nag; + nag->ag_cors = ag_cors; + if (ag_cors == 0) + ag_corsest = nag; + else + ag_cors->ag_fine = nag; + CHECK_AG(); +} + + +static char * +rtm_type_name(u_char type) +{ + static char *rtm_types[] = { + "RTM_ADD", + "RTM_DELETE", + "RTM_CHANGE", + "RTM_GET", + "RTM_LOSING", + "RTM_REDIRECT", + "RTM_MISS", + "RTM_LOCK", + "RTM_OLDADD", + "RTM_OLDDEL", + "RTM_RESOLVE", + "RTM_NEWADDR", + "RTM_DELADDR", + "RTM_IFINFO" + }; + static char name0[10]; + + + if (type > sizeof(rtm_types)/sizeof(rtm_types[0]) + || type == 0) { + sprintf(name0, "RTM type %#x", type); + return name0; + } else { + return rtm_types[type-1]; + } +} + + +/* Trim a mask in a sockaddr + * Produce a length of 0 for an address of 0. + * Otherwise produce the index of the first zero byte. + */ +void +#ifdef _HAVE_SIN_LEN +masktrim(struct sockaddr_in *ap) +#else +masktrim(struct sockaddr_in_new *ap) +#endif +{ + register char *cp; + + if (ap->sin_addr.s_addr == 0) { + ap->sin_len = 0; + return; + } + cp = (char *)(&ap->sin_addr.s_addr+1); + while (*--cp == 0) + continue; + ap->sin_len = cp - (char*)ap + 1; +} + + +/* Tell the kernel to add, delete or change a route + */ +static void +rtioctl(int action, /* RTM_DELETE, etc */ + naddr dst, + naddr gate, + naddr mask, + int metric, + int flags) +{ + struct { + struct rt_msghdr w_rtm; + struct sockaddr_in w_dst; + struct sockaddr_in w_gate; +#ifdef _HAVE_SA_LEN + struct sockaddr_in w_mask; +#else + struct sockaddr_in_new w_mask; +#endif + } w; + long cc; + +again: + bzero(&w, sizeof(w)); + w.w_rtm.rtm_msglen = sizeof(w); + w.w_rtm.rtm_version = RTM_VERSION; + w.w_rtm.rtm_type = action; + w.w_rtm.rtm_flags = flags; + w.w_rtm.rtm_seq = ++rt_sock_seqno; + w.w_rtm.rtm_addrs = RTA_DST|RTA_GATEWAY; + if (metric != 0) { + w.w_rtm.rtm_rmx.rmx_hopcount = metric; + w.w_rtm.rtm_inits |= RTV_HOPCOUNT; + } + w.w_dst.sin_family = AF_INET; + w.w_dst.sin_addr.s_addr = dst; + w.w_gate.sin_family = AF_INET; + w.w_gate.sin_addr.s_addr = gate; +#ifdef _HAVE_SA_LEN + w.w_dst.sin_len = sizeof(w.w_dst); + w.w_gate.sin_len = sizeof(w.w_gate); +#endif + if (mask == HOST_MASK) { + w.w_rtm.rtm_flags |= RTF_HOST; + w.w_rtm.rtm_msglen -= sizeof(w.w_mask); + } else { + w.w_rtm.rtm_addrs |= RTA_NETMASK; + w.w_mask.sin_addr.s_addr = htonl(mask); +#ifdef _HAVE_SA_LEN + masktrim(&w.w_mask); + if (w.w_mask.sin_len == 0) + w.w_mask.sin_len = sizeof(long); + w.w_rtm.rtm_msglen -= (sizeof(w.w_mask) - w.w_mask.sin_len); +#endif + } + + if (TRACEKERNEL) + trace_kernel("write kernel %s %s->%s metric=%d flags=%#x\n", + rtm_type_name(action), + addrname(dst, mask, 0), naddr_ntoa(gate), + metric, flags); + +#ifndef NO_INSTALL + cc = write(rt_sock, &w, w.w_rtm.rtm_msglen); + if (cc == w.w_rtm.rtm_msglen) + return; + if (cc < 0) { + if (errno == ESRCH + && (action == RTM_CHANGE || action == RTM_DELETE)) { + trace_act("route to %s disappeared before %s\n", + addrname(dst, mask, 0), + rtm_type_name(action)); + if (action == RTM_CHANGE) { + action = RTM_ADD; + goto again; + } + return; + } + msglog("write(rt_sock) %s %s --> %s: %s", + rtm_type_name(action), + addrname(dst, mask, 0), naddr_ntoa(gate), + strerror(errno)); + } else { + msglog("write(rt_sock) wrote %d instead of %d", + cc, w.w_rtm.rtm_msglen); + } +#endif +} + + +#define KHASH_SIZE 71 /* should be prime */ +#define KHASH(a,m) khash_bins[((a) ^ (m)) % KHASH_SIZE] +static struct khash { + struct khash *k_next; + naddr k_dst; + naddr k_mask; + naddr k_gate; + short k_metric; + u_short k_state; +#define KS_NEW 0x001 +#define KS_DELETE 0x002 +#define KS_ADD 0x004 /* add to the kernel */ +#define KS_CHANGE 0x008 /* tell kernel to change the route */ +#define KS_DEL_ADD 0x010 /* delete & add to change the kernel */ +#define KS_STATIC 0x020 /* Static flag in kernel */ +#define KS_GATEWAY 0x040 /* G flag in kernel */ +#define KS_DYNAMIC 0x080 /* result of redirect */ +#define KS_DELETED 0x100 /* already deleted */ + time_t k_keep; +#define K_KEEP_LIM 30 + time_t k_redirect_time; +} *khash_bins[KHASH_SIZE]; + + +static struct khash* +kern_find(naddr dst, naddr mask, struct khash ***ppk) +{ + struct khash *k, **pk; + + for (pk = &KHASH(dst,mask); (k = *pk) != 0; pk = &k->k_next) { + if (k->k_dst == dst && k->k_mask == mask) + break; + } + if (ppk != 0) + *ppk = pk; + return k; +} + + +static struct khash* +kern_add(naddr dst, naddr mask) +{ + struct khash *k, **pk; + + k = kern_find(dst, mask, &pk); + if (k != 0) + return k; + + k = (struct khash *)malloc(sizeof(*k)); + + bzero(k, sizeof(*k)); + k->k_dst = dst; + k->k_mask = mask; + k->k_state = KS_NEW; + k->k_keep = now.tv_sec; + *pk = k; + + return k; +} + + +/* If a kernel route has a non-zero metric, check that it is still in the + * daemon table, and not deleted by interfaces coming and going. + */ +static void +kern_check_static(struct khash *k, + struct interface *ifp) +{ + struct rt_entry *rt; + naddr int_addr; + + if (k->k_metric == 0) + return; + + int_addr = (ifp != 0) ? ifp->int_addr : loopaddr; + + rt = rtget(k->k_dst, k->k_mask); + if (rt != 0) { + if (!(rt->rt_state & RS_STATIC)) + rtchange(rt, rt->rt_state | RS_STATIC, + k->k_gate, int_addr, + k->k_metric, 0, ifp, now.tv_sec, 0); + } else { + rtadd(k->k_dst, k->k_mask, k->k_gate, int_addr, + k->k_metric, 0, RS_STATIC, ifp); + } +} + + +/* add a route the kernel told us + */ +static void +rtm_add(struct rt_msghdr *rtm, + struct rt_addrinfo *info, + time_t keep) +{ + struct khash *k; + struct interface *ifp; + naddr mask; + + + if (rtm->rtm_flags & RTF_HOST) { + mask = HOST_MASK; + } else if (INFO_MASK(info) != 0) { + mask = ntohl(S_ADDR(INFO_MASK(info))); + } else { + msglog("punt %s without mask", + rtm_type_name(rtm->rtm_type)); + return; + } + + if (INFO_GATE(info) == 0 + || INFO_GATE(info)->sa_family != AF_INET) { + msglog("punt %s without gateway", + rtm_type_name(rtm->rtm_type)); + return; + } + + k = kern_add(S_ADDR(INFO_DST(info)), mask); + if (k->k_state & KS_NEW) + k->k_keep = now.tv_sec+keep; + k->k_gate = S_ADDR(INFO_GATE(info)); + k->k_metric = rtm->rtm_rmx.rmx_hopcount; + if (k->k_metric < 0) + k->k_metric = 0; + else if (k->k_metric > HOPCNT_INFINITY) + k->k_metric = HOPCNT_INFINITY; + k->k_state &= ~(KS_DELETED | KS_GATEWAY | KS_STATIC | KS_NEW); + if (rtm->rtm_flags & RTF_GATEWAY) + k->k_state |= KS_GATEWAY; + if (rtm->rtm_flags & RTF_STATIC) + k->k_state |= KS_STATIC; + + if (0 != (rtm->rtm_flags & (RTF_DYNAMIC | RTF_MODIFIED))) { + if (supplier) { + /* Routers are not supposed to listen to redirects, + * so delete it. + */ + k->k_state &= ~KS_DYNAMIC; + k->k_state |= KS_DELETE; + LIM_SEC(need_kern, 0); + trace_act("mark redirected %s --> %s for deletion" + " since this is a router\n", + addrname(k->k_dst, k->k_mask, 0), + naddr_ntoa(k->k_gate)); + } else { + k->k_state |= KS_DYNAMIC; + k->k_redirect_time = now.tv_sec; + } + return; + } + + /* If it is not a static route, quit until the next comparison + * between the kernel and daemon tables, when it will be deleted. + */ + if (!(k->k_state & KS_STATIC)) { + k->k_state |= KS_DELETE; + LIM_SEC(need_kern, k->k_keep); + return; + } + + /* Put static routes with real metrics into the daemon table so + * they can be advertised. + * + * Find the interface concerned + */ + ifp = iflookup(k->k_gate); + if (ifp == 0) { + /* if there is no known interface, + * maybe there is a new interface + */ + ifinit(); + ifp = iflookup(k->k_gate); + if (ifp == 0) + msglog("static route %s --> %s impossibly lacks ifp", + addrname(S_ADDR(INFO_DST(info)), mask, 0), + naddr_ntoa(k->k_gate)); + } + + kern_check_static(k, ifp); +} + + +/* deal with packet loss + */ +static void +rtm_lose(struct rt_msghdr *rtm, + struct rt_addrinfo *info) +{ + if (INFO_GATE(info) == 0 + || INFO_GATE(info)->sa_family != AF_INET) { + msglog("punt %s without gateway", + rtm_type_name(rtm->rtm_type)); + return; + } + + if (!supplier) + rdisc_age(S_ADDR(INFO_GATE(info))); + + age(S_ADDR(INFO_GATE(info))); +} + + +/* Clean the kernel table by copying it to the daemon image. + * Eventually the daemon will delete any extra routes. + */ +void +flush_kern(void) +{ + size_t needed; + int mib[6]; + char *buf, *next, *lim; + struct rt_msghdr *rtm; + struct interface *ifp; + static struct sockaddr_in gate_sa; + struct rt_addrinfo info; + + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; /* protocol */ + mib[3] = 0; /* wildcard address family */ + mib[4] = NET_RT_DUMP; + mib[5] = 0; /* no flags */ + if (sysctl(mib, 6, 0, &needed, 0, 0) < 0) { + DBGERR(1,"RT_DUMP-sysctl-estimate"); + return; + } + buf = malloc(needed); + if (sysctl(mib, 6, buf, &needed, 0, 0) < 0) + BADERR(1,"RT_DUMP"); + lim = buf + needed; + for (next = buf; next < lim; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)next; + + rt_xaddrs(&info, + (struct sockaddr *)(rtm+1), + (struct sockaddr *)(next + rtm->rtm_msglen), + rtm->rtm_addrs); + + if (INFO_DST(&info) == 0 + || INFO_DST(&info)->sa_family != AF_INET) + continue; + + /* ignore ARP table entries on systems with a merged route + * and ARP table. + */ + if (rtm->rtm_flags & RTF_LLINFO) + continue; + + if (INFO_GATE(&info) == 0) + continue; + if (INFO_GATE(&info)->sa_family != AF_INET) { + if (INFO_GATE(&info)->sa_family != AF_LINK) + continue; + ifp = ifwithindex(((struct sockaddr_dl *) + INFO_GATE(&info))->sdl_index); + if (ifp == 0) + continue; + if ((ifp->int_if_flags & IFF_POINTOPOINT) + || S_ADDR(INFO_DST(&info)) == ifp->int_addr) + gate_sa.sin_addr.s_addr = ifp->int_addr; + else + gate_sa.sin_addr.s_addr = htonl(ifp->int_net); +#ifdef _HAVE_SA_LEN + gate_sa.sin_len = sizeof(gate_sa); +#endif + gate_sa.sin_family = AF_INET; + INFO_GATE(&info) = (struct sockaddr *)&gate_sa; + } + + /* ignore multicast addresses + */ + if (IN_MULTICAST(ntohl(S_ADDR(INFO_DST(&info))))) + continue; + + /* Note static routes and interface routes, and also + * preload the image of the kernel table so that + * we can later clean it, as well as avoid making + * unneeded changes. Keep the old kernel routes for a + * few seconds to allow a RIP or router-discovery + * response to be heard. + */ + rtm_add(rtm,&info,MIN_WAITTIME); + } + free(buf); +} + + +/* Listen to announcements from the kernel + */ +void +read_rt(void) +{ + long cc; + struct interface *ifp; + naddr mask; + union { + struct { + struct rt_msghdr rtm; + struct sockaddr addrs[RTAX_MAX]; + } r; + struct if_msghdr ifm; + } m; + char str[100], *strp; + struct rt_addrinfo info; + + + for (;;) { + cc = read(rt_sock, &m, sizeof(m)); + if (cc <= 0) { + if (cc < 0 && errno != EWOULDBLOCK) + LOGERR("read(rt_sock)"); + return; + } + + if (m.r.rtm.rtm_version != RTM_VERSION) { + msglog("bogus routing message version %d", + m.r.rtm.rtm_version); + continue; + } + + /* Ignore our own results. + */ + if (m.r.rtm.rtm_type <= RTM_CHANGE + && m.r.rtm.rtm_pid == mypid) { + static int complained = 0; + if (!complained) { + msglog("receiving our own change messages"); + complained = 1; + } + continue; + } + + if (m.r.rtm.rtm_type == RTM_IFINFO + || m.r.rtm.rtm_type == RTM_NEWADDR + || m.r.rtm.rtm_type == RTM_DELADDR) { + ifp = ifwithindex(m.ifm.ifm_index); + if (ifp == 0) + trace_act("note %s with flags %#x" + " for index #%d\n", + rtm_type_name(m.r.rtm.rtm_type), + m.ifm.ifm_flags, + m.ifm.ifm_index); + else + trace_act("note %s with flags %#x for %s\n", + rtm_type_name(m.r.rtm.rtm_type), + m.ifm.ifm_flags, + ifp->int_name); + + /* After being informed of a change to an interface, + * check them all now if the check would otherwise + * be a long time from now, if the interface is + * not known, or if the interface has been turned + * off or on. + */ + if (ifinit_timer.tv_sec-now.tv_sec>=CHECK_BAD_INTERVAL + || ifp == 0 + || ((ifp->int_if_flags ^ m.ifm.ifm_flags) + & IFF_UP_RUNNING) != 0) + ifinit_timer.tv_sec = now.tv_sec; + continue; + } + + strcpy(str, rtm_type_name(m.r.rtm.rtm_type)); + strp = &str[strlen(str)]; + if (m.r.rtm.rtm_type <= RTM_CHANGE) + strp += sprintf(strp," from pid %d",m.r.rtm.rtm_pid); + + rt_xaddrs(&info, m.r.addrs, &m.r.addrs[RTAX_MAX], + m.r.rtm.rtm_addrs); + + if (INFO_DST(&info) == 0) { + trace_act("ignore %s without dst\n", str); + continue; + } + + if (INFO_DST(&info)->sa_family != AF_INET) { + trace_act("ignore %s for AF %d\n", str, + INFO_DST(&info)->sa_family); + continue; + } + + mask = ((INFO_MASK(&info) != 0) + ? ntohl(S_ADDR(INFO_MASK(&info))) + : (m.r.rtm.rtm_flags & RTF_HOST) + ? HOST_MASK + : std_mask(S_ADDR(INFO_DST(&info)))); + + strp += sprintf(strp, ": %s", + addrname(S_ADDR(INFO_DST(&info)), mask, 0)); + + if (IN_MULTICAST(ntohl(S_ADDR(INFO_DST(&info))))) { + trace_act("ignore multicast %s\n", str); + continue; + } + + if (INFO_GATE(&info) != 0 + && INFO_GATE(&info)->sa_family == AF_INET) + strp += sprintf(strp, " --> %s", + saddr_ntoa(INFO_GATE(&info))); + + if (INFO_AUTHOR(&info) != 0) + strp += sprintf(strp, " by authority of %s", + saddr_ntoa(INFO_AUTHOR(&info))); + + switch (m.r.rtm.rtm_type) { + case RTM_ADD: + case RTM_CHANGE: + case RTM_REDIRECT: + if (m.r.rtm.rtm_errno != 0) { + trace_act("ignore %s with \"%s\" error\n", + str, strerror(m.r.rtm.rtm_errno)); + } else { + trace_act("%s\n", str); + rtm_add(&m.r.rtm,&info,0); + } + break; + + case RTM_DELETE: + if (m.r.rtm.rtm_errno != 0) { + trace_act("ignore %s with \"%s\" error\n", + str, strerror(m.r.rtm.rtm_errno)); + } else { + trace_act("%s\n", str); + del_static(S_ADDR(INFO_DST(&info)), mask, 1); + } + break; + + case RTM_LOSING: + trace_act("%s\n", str); + rtm_lose(&m.r.rtm,&info); + break; + + default: + trace_act("ignore %s\n", str); + break; + } + } +} + + +/* after aggregating, note routes that belong in the kernel + */ +static void +kern_out(struct ag_info *ag) +{ + struct khash *k; + + + /* Do not install bad routes if they are not already present. + * This includes routes that had RS_NET_SYN for interfaces that + * recently died. + */ + if (ag->ag_metric == HOPCNT_INFINITY) { + k = kern_find(htonl(ag->ag_dst_h), ag->ag_mask, 0); + if (k == 0) + return; + } else { + k = kern_add(htonl(ag->ag_dst_h), ag->ag_mask); + } + + if (k->k_state & KS_NEW) { + /* will need to add new entry to the kernel table */ + k->k_state = KS_ADD; + if (ag->ag_state & AGS_GATEWAY) + k->k_state |= KS_GATEWAY; + k->k_gate = ag->ag_gate; + k->k_metric = ag->ag_metric; + return; + } + + if (k->k_state & KS_STATIC) + return; + + /* modify existing kernel entry if necessary */ + if (k->k_gate != ag->ag_gate + || k->k_metric != ag->ag_metric) { + k->k_gate = ag->ag_gate; + k->k_metric = ag->ag_metric; + k->k_state |= KS_CHANGE; + } + + if (k->k_state & KS_DYNAMIC) { + k->k_state &= ~KS_DYNAMIC; + k->k_state |= (KS_ADD | KS_DEL_ADD); + } + + if ((k->k_state & KS_GATEWAY) + && !(ag->ag_state & AGS_GATEWAY)) { + k->k_state &= ~KS_GATEWAY; + k->k_state |= (KS_ADD | KS_DEL_ADD); + } else if (!(k->k_state & KS_GATEWAY) + && (ag->ag_state & AGS_GATEWAY)) { + k->k_state |= KS_GATEWAY; + k->k_state |= (KS_ADD | KS_DEL_ADD); + } + + /* Deleting-and-adding is necessary to change aspects of a route. + * Just delete instead of deleting and then adding a bad route. + * Otherwise, we want to keep the route in the kernel. + */ + if (k->k_metric == HOPCNT_INFINITY + && (k->k_state & KS_DEL_ADD)) + k->k_state |= KS_DELETE; + else + k->k_state &= ~KS_DELETE; +#undef RT +} + + +/* ARGSUSED */ +static int +walk_kern(struct radix_node *rn, + void *w) +{ +#define RT ((struct rt_entry *)rn) + char metric, pref; + u_int ags = 0; + + + /* Do not install synthetic routes */ + if (RT->rt_state & RS_NET_SYN) + return 0; + + if (!(RT->rt_state & RS_IF)) { + ags |= (AGS_GATEWAY | AGS_SUPPRESS | AGS_PROMOTE); + + } else { + /* Do not install routes for "external" remote interfaces. + */ + if (RT->rt_ifp != 0 && (RT->rt_ifp->int_state & IS_EXTERNAL)) + return 0; + + ags |= AGS_IF; + + /* If it is not an interface, or an alias for an interface, + * it must be a "gateway." + * + * If it is a "remote" interface, it is also a "gateway" to + * the kernel if is not a alias. + */ + if (RT->rt_ifp == 0 + || ((RT->rt_ifp->int_state & IS_REMOTE) + && RT->rt_ifp->int_metric == 0)) + ags |= (AGS_GATEWAY | AGS_SUPPRESS | AGS_PROMOTE); + } + + if (RT->rt_state & RS_RDISC) + ags |= AGS_CORS_GATE; + + /* aggregate good routes without regard to their metric */ + pref = 1; + metric = RT->rt_metric; + if (metric == HOPCNT_INFINITY) { + /* if the route is dead, so try hard to aggregate. */ + pref = HOPCNT_INFINITY; + ags |= (AGS_FINE_GATE | AGS_SUPPRESS); + } + + ag_check(RT->rt_dst, RT->rt_mask, RT->rt_gate, 0, + metric,pref, 0, 0, ags, kern_out); + return 0; +#undef RT +} + + +/* Update the kernel table to match the daemon table. + */ +static void +fix_kern(void) +{ + int i, flags; + struct khash *k, **pk; + + + need_kern = age_timer; + + /* Walk daemon table, updating the copy of the kernel table. + */ + (void)rn_walktree(rhead, walk_kern, 0); + ag_flush(0,0,kern_out); + + for (i = 0; i < KHASH_SIZE; i++) { + for (pk = &khash_bins[i]; (k = *pk) != 0; ) { + /* Do not touch static routes */ + if (k->k_state & KS_STATIC) { + kern_check_static(k,0); + pk = &k->k_next; + continue; + } + + /* check hold on routes deleted by the operator */ + if (k->k_keep > now.tv_sec) { + LIM_SEC(need_kern, k->k_keep); + k->k_state |= KS_DELETE; + pk = &k->k_next; + continue; + } + + if ((k->k_state & (KS_DELETE | KS_DYNAMIC)) + == KS_DELETE) { + if (!(k->k_state & KS_DELETED)) + rtioctl(RTM_DELETE, + k->k_dst, k->k_gate, k->k_mask, + 0, 0); + *pk = k->k_next; + free(k); + continue; + } + + if (0 != (k->k_state&(KS_ADD|KS_CHANGE|KS_DEL_ADD))) { + if (k->k_state & KS_DEL_ADD) { + rtioctl(RTM_DELETE, + k->k_dst,k->k_gate,k->k_mask, + 0, 0); + k->k_state &= ~KS_DYNAMIC; + } + + flags = 0; + if (0 != (k->k_state&(KS_GATEWAY|KS_DYNAMIC))) + flags |= RTF_GATEWAY; + + if (k->k_state & KS_ADD) { + rtioctl(RTM_ADD, + k->k_dst, k->k_gate, k->k_mask, + k->k_metric, flags); + } else if (k->k_state & KS_CHANGE) { + rtioctl(RTM_CHANGE, + k->k_dst,k->k_gate,k->k_mask, + k->k_metric, flags); + } + k->k_state &= ~(KS_ADD|KS_CHANGE|KS_DEL_ADD); + } + + /* Mark this route to be deleted in the next cycle. + * This deletes routes that disappear from the + * daemon table, since the normal aging code + * will clear the bit for routes that have not + * disappeared from the daemon table. + */ + k->k_state |= KS_DELETE; + pk = &k->k_next; + } + } +} + + +/* Delete a static route in the image of the kernel table. + */ +void +del_static(naddr dst, + naddr mask, + int gone) +{ + struct khash *k; + struct rt_entry *rt; + + /* Just mark it in the table to be deleted next time the kernel + * table is updated. + * If it has already been deleted, mark it as such, and set its + * keep-timer so that it will not be deleted again for a while. + * This lets the operator delete a route added by the daemon + * and add a replacement. + */ + k = kern_find(dst, mask, 0); + if (k != 0) { + k->k_state &= ~(KS_STATIC | KS_DYNAMIC); + k->k_state |= KS_DELETE; + if (gone) { + k->k_state |= KS_DELETED; + k->k_keep = now.tv_sec + K_KEEP_LIM; + } + } + + rt = rtget(dst, mask); + if (rt != 0 && (rt->rt_state & RS_STATIC)) + rtbad(rt); +} + + +/* Delete all routes generated from ICMP Redirects that use a given gateway, + * as well as old redirected routes. + */ +void +del_redirects(naddr bad_gate, + time_t old) +{ + int i; + struct khash *k; + + + for (i = 0; i < KHASH_SIZE; i++) { + for (k = khash_bins[i]; k != 0; k = k->k_next) { + if (!(k->k_state & KS_DYNAMIC) + || (k->k_state & KS_STATIC)) + continue; + + if (k->k_gate != bad_gate + && k->k_redirect_time > old + && !supplier) + continue; + + k->k_state |= KS_DELETE; + k->k_state &= ~KS_DYNAMIC; + need_kern.tv_sec = now.tv_sec; + trace_act("mark redirected %s --> %s for deletion\n", + addrname(k->k_dst, k->k_mask, 0), + naddr_ntoa(k->k_gate)); + } + } +} + + +/* Start the daemon tables. + */ +void +rtinit(void) +{ + extern int max_keylen; + int i; + struct ag_info *ag; + + /* Initialize the radix trees */ + max_keylen = sizeof(struct sockaddr_in); + rn_init(); + rn_inithead((void**)&rhead, 32); + + /* mark all of the slots in the table free */ + ag_avail = ag_slots; + for (ag = ag_slots, i = 1; i < NUM_AG_SLOTS; i++) { + ag->ag_fine = ag+1; + ag++; + } +} + + +#ifdef _HAVE_SIN_LEN +static struct sockaddr_in dst_sock = {sizeof(dst_sock), AF_INET}; +static struct sockaddr_in mask_sock = {sizeof(mask_sock), AF_INET}; +#else +static struct sockaddr_in_new dst_sock = {_SIN_ADDR_SIZE, AF_INET}; +static struct sockaddr_in_new mask_sock = {_SIN_ADDR_SIZE, AF_INET}; +#endif + + +void +set_need_flash(void) +{ + if (!need_flash) { + need_flash = 1; + /* Do not send the flash update immediately. Wait a little + * while to hear from other routers. + */ + no_flash.tv_sec = now.tv_sec + MIN_WAITTIME; + } +} + + +/* Get a particular routing table entry + */ +struct rt_entry * +rtget(naddr dst, naddr mask) +{ + struct rt_entry *rt; + + dst_sock.sin_addr.s_addr = dst; + mask_sock.sin_addr.s_addr = mask; + masktrim(&mask_sock); + rt = (struct rt_entry *)rhead->rnh_lookup(&dst_sock,&mask_sock,rhead); + if (!rt + || rt->rt_dst != dst + || rt->rt_mask != mask) + return 0; + + return rt; +} + + +/* Find a route to dst as the kernel would. + */ +struct rt_entry * +rtfind(naddr dst) +{ + dst_sock.sin_addr.s_addr = dst; + return (struct rt_entry *)rhead->rnh_matchaddr(&dst_sock, rhead); +} + + +/* add a route to the table + */ +void +rtadd(naddr dst, + naddr mask, + naddr gate, /* forward packets here */ + naddr router, /* on the authority of this router */ + int metric, + u_short tag, + u_int state, /* rs_state for the entry */ + struct interface *ifp) +{ + struct rt_entry *rt; + naddr smask; + int i; + struct rt_spare *rts; + + rt = (struct rt_entry *)rtmalloc(sizeof (*rt), "rtadd"); + bzero(rt, sizeof(*rt)); + for (rts = rt->rt_spares, i = NUM_SPARES; i != 0; i--, rts++) + rts->rts_metric = HOPCNT_INFINITY; + + rt->rt_nodes->rn_key = (caddr_t)&rt->rt_dst_sock; + rt->rt_dst = dst; + rt->rt_dst_sock.sin_family = AF_INET; +#ifdef _HAVE_SIN_LEN + rt->rt_dst_sock.sin_len = dst_sock.sin_len; +#endif + if (mask != HOST_MASK) { + smask = std_mask(dst); + if ((smask & ~mask) == 0 && mask > smask) + state |= RS_SUBNET; + } + mask_sock.sin_addr.s_addr = mask; + masktrim(&mask_sock); + rt->rt_mask = mask; + rt->rt_state = state; + rt->rt_gate = gate; + rt->rt_router = router; + rt->rt_time = now.tv_sec; + rt->rt_metric = metric; + rt->rt_poison_metric = HOPCNT_INFINITY; + rt->rt_tag = tag; + rt->rt_ifp = ifp; + rt->rt_seqno = update_seqno; + + if (++total_routes == MAX_ROUTES) + msglog("have maximum (%d) routes", total_routes); + if (TRACEACTIONS) + trace_add_del("Add", rt); + + need_kern.tv_sec = now.tv_sec; + set_need_flash(); + + if (0 == rhead->rnh_addaddr(&rt->rt_dst_sock, &mask_sock, + rhead, rt->rt_nodes)) { + msglog("rnh_addaddr() failed for %s mask=%#x", + naddr_ntoa(dst), mask); + } +} + + +/* notice a changed route + */ +void +rtchange(struct rt_entry *rt, + u_int state, /* new state bits */ + naddr gate, /* now forward packets here */ + naddr router, /* on the authority of this router */ + int metric, /* new metric */ + u_short tag, + struct interface *ifp, + time_t new_time, + char *label) +{ + if (rt->rt_metric != metric) { + /* Fix the kernel immediately if it seems the route + * has gone bad, since there may be a working route that + * aggregates this route. + */ + if (metric == HOPCNT_INFINITY) { + need_kern.tv_sec = now.tv_sec; + if (new_time >= now.tv_sec - EXPIRE_TIME) + new_time = now.tv_sec - EXPIRE_TIME; + } + rt->rt_seqno = update_seqno; + set_need_flash(); + } + + if (rt->rt_gate != gate) { + need_kern.tv_sec = now.tv_sec; + rt->rt_seqno = update_seqno; + set_need_flash(); + } + + state |= (rt->rt_state & RS_SUBNET); + + /* Keep various things from deciding ageless routes are stale. + */ + if (!AGE_RT(state, ifp)) + new_time = now.tv_sec; + + if (TRACEACTIONS) + trace_change(rt, state, gate, router, metric, tag, ifp, + new_time, + label ? label : "Chg "); + + rt->rt_state = state; + rt->rt_gate = gate; + rt->rt_router = router; + rt->rt_metric = metric; + rt->rt_tag = tag; + rt->rt_ifp = ifp; + rt->rt_time = new_time; +} + + +/* check for a better route among the spares + */ +static struct rt_spare * +rts_better(struct rt_entry *rt) +{ + struct rt_spare *rts, *rts1; + int i; + + /* find the best alternative among the spares */ + rts = rt->rt_spares+1; + for (i = NUM_SPARES, rts1 = rts+1; i > 2; i--, rts1++) { + if (BETTER_LINK(rt,rts1,rts)) + rts = rts1; + } + + return rts; +} + + +/* switch to a backup route + */ +void +rtswitch(struct rt_entry *rt, + struct rt_spare *rts) +{ + struct rt_spare swap; + char label[10]; + + + /* Do not change permanent routes */ + if (0 != (rt->rt_state & RS_PERMANENT)) + return; + + /* Do not discard synthetic routes until they go bad */ + if ((rt->rt_state & RS_NET_SYN) + && rt->rt_metric < HOPCNT_INFINITY) + return; + + /* find the best alternative among the spares */ + if (rts == 0) + rts = rts_better(rt); + + /* Do not bother if it is not worthwhile. + */ + if (!BETTER_LINK(rt, rts, rt->rt_spares)) + return; + + swap = rt->rt_spares[0]; + (void)sprintf(label, "Use #%d", rts - rt->rt_spares); + rtchange(rt, rt->rt_state & ~(RS_NET_SYN | RS_RDISC), + rts->rts_gate, rts->rts_router, rts->rts_metric, + rts->rts_tag, rts->rts_ifp, rts->rts_time, label); + *rts = swap; +} + + +void +rtdelete(struct rt_entry *rt) +{ + struct khash *k; + + + if (TRACEACTIONS) + trace_add_del("Del", rt); + + k = kern_find(rt->rt_dst, rt->rt_mask, 0); + if (k != 0) { + k->k_state |= KS_DELETE; + need_kern.tv_sec = now.tv_sec; + } + + dst_sock.sin_addr.s_addr = rt->rt_dst; + mask_sock.sin_addr.s_addr = rt->rt_mask; + masktrim(&mask_sock); + if (rt != (struct rt_entry *)rhead->rnh_deladdr(&dst_sock, &mask_sock, + rhead)) { + msglog("rnh_deladdr() failed"); + } else { + free(rt); + total_routes--; + } +} + + +/* Get rid of a bad route, and try to switch to a replacement. + */ +void +rtbad(struct rt_entry *rt) +{ + /* Poison the route */ + rtchange(rt, rt->rt_state & ~(RS_IF | RS_LOCAL | RS_STATIC), + rt->rt_gate, rt->rt_router, HOPCNT_INFINITY, rt->rt_tag, + 0, rt->rt_time, 0); + + rtswitch(rt, 0); +} + + +/* Junk a RS_NET_SYN or RS_LOCAL route, + * unless it is needed by another interface. + */ +void +rtbad_sub(struct rt_entry *rt) +{ + struct interface *ifp, *ifp1; + struct intnet *intnetp; + u_int state; + + + ifp1 = 0; + state = 0; + + if (rt->rt_state & RS_LOCAL) { + /* Is this the route through loopback for the interface? + * If so, see if it is used by any other interfaces, such + * as a point-to-point interface with the same local address. + */ + for (ifp = ifnet; ifp != 0; ifp = ifp->int_next) { + /* Retain it if another interface needs it. + */ + if (ifp->int_addr == rt->rt_ifp->int_addr) { + state |= RS_LOCAL; + ifp1 = ifp; + break; + } + } + + } + + if (!(state & RS_LOCAL)) { + /* Retain RIPv1 logical network route if there is another + * interface that justifies it. + */ + if (rt->rt_state & RS_NET_SYN) { + for (ifp = ifnet; ifp != 0; ifp = ifp->int_next) { + if ((ifp->int_state & IS_NEED_NET_SYN) + && rt->rt_mask == ifp->int_std_mask + && rt->rt_dst == ifp->int_std_addr) { + state |= RS_NET_SYN; + ifp1 = ifp; + break; + } + } + } + + /* or if there is an authority route that needs it. */ + for (intnetp = intnets; + intnetp != 0; + intnetp = intnetp->intnet_next) { + if (intnetp->intnet_addr == rt->rt_dst + && intnetp->intnet_mask == rt->rt_mask) { + state |= (RS_NET_SYN | RS_NET_INT); + break; + } + } + } + + if (ifp1 != 0 || (state & RS_NET_SYN)) { + rtchange(rt, ((rt->rt_state & ~(RS_NET_SYN | RS_LOCAL)) + | state), + rt->rt_gate, rt->rt_router, rt->rt_metric, + rt->rt_tag, ifp1, rt->rt_time, 0); + } else { + rtbad(rt); + } +} + + +/* Called while walking the table looking for sick interfaces + * or after a time change. + */ +/* ARGSUSED */ +int +walk_bad(struct radix_node *rn, + void *w) +{ +#define RT ((struct rt_entry *)rn) + struct rt_spare *rts; + int i; + time_t new_time; + + + /* fix any spare routes through the interface + */ + rts = RT->rt_spares; + for (i = NUM_SPARES; i != 1; i--) { + rts++; + + if (rts->rts_ifp != 0 + && (rts->rts_ifp->int_state & IS_BROKE)) { + /* mark the spare route to be deleted immediately */ + new_time = rts->rts_time; + if (new_time >= now_garbage) + new_time = now_garbage-1; + trace_upslot(RT, rts, rts->rts_gate, + rts->rts_router, 0, + HOPCNT_INFINITY, rts->rts_tag, + new_time); + rts->rts_ifp = 0; + rts->rts_metric = HOPCNT_INFINITY; + rts->rts_time = new_time; + } + } + + /* Deal with the main route + */ + /* finished if it has been handled before or if its interface is ok + */ + if (RT->rt_ifp == 0 || !(RT->rt_ifp->int_state & IS_BROKE)) + return 0; + + /* Bad routes for other than interfaces are easy. + */ + if (0 == (RT->rt_state & (RS_IF | RS_NET_SYN | RS_LOCAL))) { + rtbad(RT); + return 0; + } + + rtbad_sub(RT); + return 0; +#undef RT +} + + +/* Check the age of an individual route. + */ +/* ARGSUSED */ +static int +walk_age(struct radix_node *rn, + void *w) +{ +#define RT ((struct rt_entry *)rn) + struct interface *ifp; + struct rt_spare *rts; + int i; + + + /* age all of the spare routes, including the primary route + * currently in use + */ + rts = RT->rt_spares; + for (i = NUM_SPARES; i != 0; i--, rts++) { + + ifp = rts->rts_ifp; + if (i == NUM_SPARES) { + if (!AGE_RT(RT->rt_state, ifp)) { + /* Keep various things from deciding ageless + * routes are stale + */ + rts->rts_time = now.tv_sec; + continue; + } + + /* forget RIP routes after RIP has been turned off. + */ + if (rip_sock < 0) { + rtdelete(RT); + return 0; + } + } + + /* age failing routes + */ + if (age_bad_gate == rts->rts_gate + && rts->rts_time >= now_stale) { + rts->rts_time -= SUPPLY_INTERVAL; + } + + /* trash the spare routes when they go bad */ + if (rts->rts_metric < HOPCNT_INFINITY + && now_garbage > rts->rts_time) { + trace_upslot(RT, rts, rts->rts_gate, + rts->rts_router, rts->rts_ifp, + HOPCNT_INFINITY, rts->rts_tag, + rts->rts_time); + rts->rts_metric = HOPCNT_INFINITY; + } + } + + + /* finished if the active route is still fresh */ + if (now_stale <= RT->rt_time) + return 0; + + /* try to switch to an alternative */ + rtswitch(RT, 0); + + /* Delete a dead route after it has been publically mourned. */ + if (now_garbage > RT->rt_time) { + rtdelete(RT); + return 0; + } + + /* Start poisoning a bad route before deleting it. */ + if (now.tv_sec - RT->rt_time > EXPIRE_TIME) + rtchange(RT, RT->rt_state, RT->rt_gate, RT->rt_router, + HOPCNT_INFINITY, RT->rt_tag, RT->rt_ifp, + RT->rt_time, 0); + return 0; +} + + +/* Watch for dead routes and interfaces. + */ +void +age(naddr bad_gate) +{ + struct interface *ifp; + + + age_timer.tv_sec = now.tv_sec + (rip_sock < 0 + ? NEVER + : SUPPLY_INTERVAL); + + for (ifp = ifnet; ifp; ifp = ifp->int_next) { + /* Check for dead IS_REMOTE interfaces by timing their + * transmissions. + */ + if ((ifp->int_state & IS_REMOTE) + && !(ifp->int_state & IS_PASSIVE) + && (ifp->int_state & IS_ACTIVE)) { + LIM_SEC(age_timer, now.tv_sec+SUPPLY_INTERVAL); + + if (now.tv_sec - ifp->int_act_time > EXPIRE_TIME + && !(ifp->int_state & IS_BROKE)) { + msglog("remote interface %s to %s timed out" + "--turned off", + ifp->int_name, + naddr_ntoa(ifp->int_addr)); + if_bad(ifp); + } + } + } + + /* Age routes. */ + age_bad_gate = bad_gate; + (void)rn_walktree(rhead, walk_age, 0); + + /* Update the kernel routing table. */ + fix_kern(); +} |