/* $NetBSD: rdisc.c,v 1.17 2006/05/09 20:18:09 mrg 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 acknowledgment: * 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. */ #include "defs.h" #include #include #include #ifdef __NetBSD__ __RCSID("$NetBSD: rdisc.c,v 1.17 2006/05/09 20:18:09 mrg Exp $"); #elif defined(__FreeBSD__) __RCSID("$FreeBSD$"); #else __RCSID("Revision: 2.23 "); #ident "Revision: 2.23 " #endif /* 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 16 /* at least one per interface */ struct dr { /* accumulated advertisements */ struct interface *dr_ifp; naddr dr_gate; /* gateway */ time_t dr_ts; /* when received */ time_t dr_life; /* lifetime in host byte order */ n_long dr_recv_pref; /* received but biased preference */ n_long dr_pref; /* preference adjusted by metric */ } *cur_drp, drs[MAX_ADS]; /* convert between signed, balanced around zero, * and unsigned zero-based preferences */ #define SIGN_PREF(p) ((p) ^ MIN_PreferenceLevel) #define UNSIGN_PREF(p) SIGN_PREF(p) /* adjust unsigned preference by interface metric, * without driving it to infinity */ #define PREF(p, ifp) ((n_long)(p) <= (n_long)((ifp)->int_metric \ + (ifp)->int_adj_outmetric) \ ? ((p) != 0 ? 1 : 0) \ : (p) - ((ifp)->int_metric + (ifp)->int_adj_outmetric)) static void rdisc_sort(void); /* dump an ICMP Router Discovery Advertisement Message */ static void trace_rdisc(const 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=%d", 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", act, naddr_ntoa(from), naddr_ntoa(to), ifp ? ifp->int_name : "?", (int)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_ALL_HOSTS | IS_ALL_ROUTERS); return; } #ifdef MCAST_PPP_BUG if (ifp->int_if_flags & IFF_POINTOPOINT) return; #endif memset(&m, 0, sizeof(m)); #ifdef MCAST_IFINDEX m.imr_interface.s_addr = htonl(ifp->int_index); #else m.imr_interface.s_addr = ((ifp->int_if_flags & IFF_POINTOPOINT) ? ifp->int_dstaddr : ifp->int_addr); #endif 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 supplying routes"); /* 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 advertising, 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, solicit 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", naddr_ntoa(drp->dr_gate), drp->dr_ifp->int_name); drp->dr_ts = sec; } break; } } } rdisc_sol(); rdisc_sort(); /* Delete old redirected routes to keep the kernel table small, * and to prevent black holes. Check that the kernel table * matches the daemon table (i.e. has the default route). * But only if RIP is not running and we are not dealing with * a bad gateway, since otherwise age() will be called. */ if (rip_sock < 0 && bad_gate == 0) age(0); } /* 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_ts = 0; drp->dr_life = 0; } /* make a note to re-solicit, turn RIP on or off, etc. */ rdisc_timer.tv_sec = 0; } /* 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; naddr gate; int i; del_redirects(gate = 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) { trace_act("discovered router %s via %s" " is bad--have %d remaining", naddr_ntoa(gate), ifp->int_name, i); } else if (ifp->int_rdisc_cnt >= MAX_SOLICITATIONS) { trace_act("last discovered router %s via %s" " is bad--re-solicit", naddr_ntoa(gate), ifp->int_name); ifp->int_rdisc_cnt = 0; ifp->int_rdisc_timer.tv_sec = 0; rdisc_sol(); } else { trace_act("last discovered router %s via %s" " is bad--wait to solicit", naddr_ntoa(gate), ifp->int_name); } } /* Find the best discovered route, * and discard stale routers. */ static void rdisc_sort(void) { struct dr *drp, *new_drp; struct rt_entry *rt; struct rt_spare new; struct interface *ifp; u_int new_st = 0; n_long new_pref = 0; /* 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"); rdisc_ok = 0; if (rt != 0 && (rt->rt_state & RS_RDISC)) { new = rt->rt_spares[0]; new.rts_metric = HOPCNT_INFINITY; new.rts_time = now.tv_sec - GARBAGE_TIME; rtchange(rt, rt->rt_state & ~RS_RDISC, &new, 0); rtswitch(rt, 0); } } else { if (cur_drp == 0) { trace_act("turn on Router Discovery client" " using %s via %s", 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", naddr_ntoa(cur_drp->dr_gate), cur_drp->dr_ifp->int_name, naddr_ntoa(new_drp->dr_gate), new_drp->dr_ifp->int_name); } memset(&new, 0, sizeof(new)); new.rts_ifp = new_drp->dr_ifp; new.rts_gate = new_drp->dr_gate; new.rts_router = new_drp->dr_gate; new.rts_metric = HOPCNT_INFINITY-1; new.rts_time = now.tv_sec; if (rt != 0) { rtchange(rt, rt->rt_state | RS_RDISC, &new, 0); } else { rtadd(RIP_DEFAULT, 0, RS_RDISC, &new); } } cur_drp = new_drp; } /* turn RIP on or off */ if (!rdisc_ok || rip_interfaces > 1) { rip_on(0); } else { rip_off(); } } /* handle a single address in an advertisement */ static void parse_ad(naddr from, naddr gate, n_long pref, /* signed and in network order */ u_short life, /* in host byte order */ struct interface *ifp) { static struct msg_limit bad_gate; struct dr *drp, *new_drp; if (gate == RIP_DEFAULT || !check_dst(gate)) { msglim(&bad_gate, from,"router %s advertising bad gateway %s", naddr_ntoa(from), naddr_ntoa(gate)); return; } /* ignore pointers to ourself and routes via unreachable networks */ if (ifwithaddr(gate, 1, 0) != 0) { trace_pkt(" discard Router Discovery Ad pointing at us"); return; } if (!on_net(gate, ifp->int_net, ifp->int_mask)) { trace_pkt(" discard Router Discovery Ad" " toward unreachable net"); return; } /* Convert preference to an unsigned value * and later bias it by the metric of the interface. */ pref = UNSIGN_PREF(ntohl(pref)); if (pref == 0 || life < MinMaxAdvertiseInterval) { 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 valuable 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 = 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 rsin; int flags; const char *msg; naddr tgt_mcast; memset(&rsin, 0, sizeof(rsin)); rsin.sin_addr.s_addr = dst; rsin.sin_family = AF_INET; #ifdef _HAVE_SIN_LEN rsin.sin_len = sizeof(rsin); #endif flags = MSG_DONTROUTE; switch (type) { case 0: /* unicast */ default: msg = "Send"; break; case 1: /* broadcast */ if (ifp->int_if_flags & IFF_POINTOPOINT) { msg = "Send pt-to-pt"; rsin.sin_addr.s_addr = ifp->int_dstaddr; } else { msg = "Send broadcast"; rsin.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", ifp->int_name); return; } if (rdisc_sock_mcast != ifp) { /* select the right interface. */ #ifdef MCAST_IFINDEX /* specify ifindex */ tgt_mcast = htonl(ifp->int_index); #else #ifdef MCAST_PPP_BUG /* Do not specify 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; #endif 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 ? ifp->int_addr : 0, rsin.sin_addr.s_addr, ifp, p, p_size); if (0 > sendto(rdisc_sock, p, p_size, flags, (struct sockaddr *)&rsin, sizeof(rsin))) { if (ifp == 0 || !(ifp->int_state & IS_BROKE)) msglog("sendto(%s%s%s): %s", ifp != 0 ? ifp->int_name : "", ifp != 0 ? ", " : "", inet_ntoa(rsin.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; memset(&u, 0, 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); /* Convert the configured preference to an unsigned value, * bias it by the interface metric, and then send it as a * signed, network byte order value. */ pref = UNSIGN_PREF(ifp->int_rdisc_pref); u.ad.icmp_ad_info[0].icmp_ad_pref = htonl(SIGN_PREF(PREF(pref, ifp))); 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; if (!supplier) return; 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_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; if (supplier) return; 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_BROKE)) || ifp->int_rdisc_cnt >= MAX_SOLICITATIONS) continue; if (!timercmp(&ifp->int_rdisc_timer, &now, >)) { memset(&u, 0, 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(const char *act, naddr from, struct interface *ifp, naddr to, union ad_u *p, u_int len) { const char *type; 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", 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 struct msg_limit bad_asize, bad_len; #ifdef USE_PASSIFNAME static struct msg_limit bad_name; #endif struct sockaddr_in from; socklen_t fromlen; int n, cc, hlen; struct { #ifdef USE_PASSIFNAME char ifname[IFNAMSIZ]; #endif union { struct ip ip; u_short s[512/2]; u_char b[512]; } pkt; } buf; union ad_u *p; n_long *wp; struct interface *ifp; for (;;) { fromlen = sizeof(from); cc = recvfrom(rdisc_sock, &buf, sizeof(buf), 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); #ifdef USE_PASSIFNAME if ((cc -= sizeof(buf.ifname)) < 0) logbad(0,"missing USE_PASSIFNAME; only %d bytes", cc+sizeof(buf.ifname)); #endif hlen = buf.pkt.ip.ip_hl << 2; if (cc < hlen + ICMP_MINLEN) continue; p = (union ad_u *)&buf.pkt.b[hlen]; cc -= hlen; #ifdef USE_PASSIFNAME ifp = ifwithname(buf.ifname, 0); if (ifp == 0) msglim(&bad_name, from.sin_addr.s_addr, "impossible rdisc if_ name %.*s", IFNAMSIZ, buf.ifname); #else /* If we could tell the interface on which a packet from * address 0 arrived, we could deal with such solicitations. */ ifp = ((from.sin_addr.s_addr == 0) ? 0 : iflookup(from.sin_addr.s_addr)); #endif ifp = ck_icmp("Recv", from.sin_addr.s_addr, ifp, buf.pkt.ip.ip_dst.s_addr, p, cc); if (ifp == 0) continue; if (ifwithaddr(from.sin_addr.s_addr, 0, 0)) { trace_pkt(" " "discard our own Router Discovery message"); continue; } switch (p->icmp.icmp_type) { case ICMP_ROUTERADVERT: if (p->ad.icmp_ad_asize*4 < (int)sizeof(p->ad.icmp_ad_info[0])) { msglim(&bad_asize, from.sin_addr.s_addr, "intolerable rdisc address size=%d", p->ad.icmp_ad_asize); continue; } if (p->ad.icmp_ad_num == 0) { trace_pkt(" empty?"); continue; } if (cc != (int)(sizeof(p->ad) - sizeof(p->ad.icmp_ad_info) + (p->ad.icmp_ad_num * sizeof(p->ad.icmp_ad_info[0])))) { msglim(&bad_len, from.sin_addr.s_addr, "rdisc length %d does not match ad_num" " %d", cc, p->ad.icmp_ad_num); 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; if (stopint) 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(); }