/* This file contains code to implement the Routing Information Protocol (RIP) * and is derived from 4.2BSD code. Mike Karels of Berkeley has stated on * TCP-IP that the code may be freely used as long as UC Berkeley is * credited. (Well, here's some credit :-). AGB 4-28-88 * Further documentation on the RIP protocol is now available in Charles * Hedrick's draft RFC, as yet unnumbered. AGB 5-6-88 * * The RIP RFC has now been issued as RFC1058. AGB 7-23-88 * * Code gutted and substantially rewritten. KA9Q 9/89 */ #include "global.h" #include "mbuf.h" #include "netuser.h" #include "udp.h" #include "timer.h" #include "iface.h" #include "ip.h" #include "internet.h" #include "rip.h" #include "arp.h" struct rip_stat Rip_stat; int16 Rip_trace; int Rip_merge; struct rip_list *Rip_list; struct udp_cb *Rip_cb; struct rip_refuse *Rip_refuse; static void rip_rx __ARGS((struct iface *iface,struct udp_cb *sock,int cnt)); static void proc_rip __ARGS((struct iface *iface,int32 gateway, struct rip_route *ep,int32 ttl)); static char *putheader __ARGS((char *cp,char command,char version)); static char *putentry __ARGS((char *cp,int16 fam,int32 target,int32 metric)); static void rip_shout __ARGS((void *p)); static void send_routes __ARGS((int32 dest,int16 port,int split,int trig, int us)); /* Send RIP CMD_RESPONSE packet(s) to the specified rip_list entry */ static void rip_shout(p) void *p; { register struct rip_list *rl; rl = (struct rip_list *)p; stop_timer(&rl->rip_time); send_routes(rl->dest,RIP_PORT,rl->flags&RIP_SPLIT,0,rl->flags&RIP_US); set_timer(&rl->rip_time,rl->interval*1000L); start_timer(&rl->rip_time); } /* Send the routing table. */ static void send_routes(dest,port,split,trig,us) int32 dest; /* IP destination address to send to */ int16 port; int split; /* Do split horizon? */ int trig; /* Send only triggered updates? */ int us; /* Include our address in update */ { char *cp; int i,bits,numroutes,maxroutes; int16 pktsize; struct mbuf *bp; struct route *rp; struct socket lsock,fsock; struct iface *iface; if((rp = rt_lookup(dest)) == NULLROUTE) return; /* No route exists, can't do it */ iface = rp->iface; /* Compute maximum packet size and number of routes we can send */ pktsize = ip_mtu(dest) - IPLEN; pktsize = min(pktsize,MAXRIPPACKET); maxroutes = (pktsize - RIPHEADER) / RIPROUTE; lsock.address = INADDR_ANY; lsock.port = RIP_PORT; fsock.address = dest; fsock.port = port; /* Allocate space for a full size RIP packet and generate header */ if((bp = alloc_mbuf(pktsize)) == NULLBUF) return; numroutes = 0; cp = putheader(bp->data,RIPCMD_RESPONSE,RIPVERSION); /* Emit route to ourselves, if requested */ if(us){ cp = putentry(cp,RIP_IPFAM,iface->addr,0); numroutes++; } /* Emit default route, if appropriate */ if(R_default.iface != NULLIF && !(R_default.flags & RTPRIVATE) && (!trig || (R_default.flags & RTTRIG))){ if(!split || iface != R_default.iface){ cp = putentry(cp,RIP_IPFAM,0,R_default.metric); numroutes++; } else if(trig){ cp = putentry(cp,RIP_IPFAM,0,RIP_INFINITY); numroutes++; } } for(bits=0;bits<32;bits++){ for(i=0;inext){ if((rp->flags & RTPRIVATE) || (trig && !(rp->flags & RTTRIG))) continue; if(numroutes >= maxroutes){ /* Packet full, flush and make another */ bp->cnt = RIPHEADER + numroutes * RIPROUTE; send_udp(&lsock,&fsock,0,0,bp,bp->cnt,0,0); Rip_stat.output++; if((bp = alloc_mbuf(pktsize)) == NULLBUF) return; numroutes = 0; cp = putheader(bp->data,RIPCMD_RESPONSE,RIPVERSION); } if(!split || iface != rp->iface){ cp = putentry(cp,RIP_IPFAM,rp->target,rp->metric); numroutes++; } else if(trig){ cp = putentry(cp,RIP_IPFAM,rp->target,RIP_INFINITY); numroutes++; } } } } if(numroutes != 0){ bp->cnt = RIPHEADER + numroutes * RIPROUTE; send_udp(&lsock,&fsock,0,0,bp,bp->cnt,0,0); Rip_stat.output++; } else { free_p(bp); } } /* Add an entry to the rip broadcast list */ int rip_add(dest,interval,flags) int32 dest; int32 interval; char flags; { register struct rip_list *rl; struct route *rp; if((rp = rt_lookup(dest)) == NULLROUTE){ tprintf("%s is unreachable\n",inet_ntoa(dest)); return 1; } for(rl = Rip_list; rl != NULLRL; rl = rl->next) if(rl->dest == dest) break; if(rl == NULLRL){ /* get a chunk of memory for the rip interface descriptor */ rl = (struct rip_list *)callocw(1,sizeof(struct rip_list)); /* tack this record on as the first in the list */ rl->next = Rip_list; if(rl->next != NULLRL) rl->next->prev = rl; Rip_list = rl; rl->dest = dest; } /* and the interface ptr, tick interval and flags */ rl->iface = rp->iface; rl->interval = interval; rl->flags = flags; /* set up the timer stuff */ rl->rip_time.func = rip_shout; rl->rip_time.arg = rl; /* This will initialize the timer and do an immediate broadcast */ rip_shout(rl); return 0; } /* add a gateway to the rip_refuse list which allows us to ignore their * advertisements */ int riprefadd(gateway) int32 gateway; { register struct rip_refuse *rl; for(rl = Rip_refuse; rl != NULLREF; rl = rl->next) if(rl->target == gateway) return 0; /* Already in table */ /* get a chunk of memory for the rip interface descriptor */ rl = (struct rip_refuse *)callocw(1,sizeof(struct rip_refuse)); /* tack this record on as the first in the list */ rl->next = Rip_refuse; if(rl->next != NULLREF) rl->next->prev = rl; Rip_refuse = rl; /* fill in the gateway to ignore */ rl->target = gateway; return 0; } /* drop a RIP target */ int rip_drop(dest) int32 dest; { register struct rip_list *rl; for(rl = Rip_list; rl != NULLRL; rl = rl->next) if(rl->dest == dest) break; /* leave if we didn't find it */ if(rl == NULLRL) return 0; /* stop the timer */ stop_timer(&rl->rip_time); /* Unlink from list */ if(rl->next != NULLRL) rl->next->prev = rl->prev; if(rl->prev != NULLRL) rl->prev->next = rl->next; else Rip_list = rl->next; /* and deallocate the descriptor memory */ free((char *)rl); return 0; } /* drop a RIP-refuse target from the rip_refuse list */ int riprefdrop(gateway) int32 gateway; { register struct rip_refuse *rl; for(rl = Rip_refuse; rl != NULLREF; rl = rl->next) if(rl->target == gateway) break; /* leave if we didn't find it */ if(rl == NULLREF) return 0; /* Unlink from list */ if(rl->next != NULLREF) rl->next->prev = rl->prev; if(rl->prev != NULLREF) rl->prev->next = rl->next; else Rip_refuse = rl->next; /* and deallocate the structure memory */ free((char *)rl); return 0; } /* function to output a RIP CMD_RESPONSE packet for the rip_trigger list */ void rip_trigger() { register struct rip_list *rl; int bits,i; struct route *rp; for(rl=Rip_list;rl != NULLRL;rl = rl->next){ send_routes(rl->dest,RIP_PORT,(rl->flags & RIP_SPLIT),1,0); } /* Clear the trigger list */ R_default.flags &= ~RTTRIG; for(bits=0;bits<32;bits++){ for(i=0;inext){ rp->flags &= ~RTTRIG; } } } } /* Start RIP agent listening at local RIP UDP port */ int rip_init() { struct socket lsock; lsock.address = INADDR_ANY; lsock.port = RIP_PORT; if(Rip_cb == NULLUDP) Rip_cb = open_udp(&lsock,rip_rx); return 0; } /* Process RIP input received from 'interface'. */ static void rip_rx(iface,sock,cnt) struct iface *iface; struct udp_cb *sock; int cnt; { struct mbuf *bp; struct socket fsock; int cmd; register struct rip_refuse *rfl; struct rip_route entry; struct route *rp; struct rip_list *rl; int32 ttl; /* receive the RIP packet */ recv_udp(sock,&fsock,&bp); /* increment the rcvd cnt */ Rip_stat.rcvd++; /* check the gateway of this packet against the rip_refuse list and * discard it if a match is found */ for(rfl=Rip_refuse;rfl != NULLREF;rfl = rfl->next){ if(fsock.address == rfl->target){ Rip_stat.refusals++; if(Rip_trace > 1) printf("RIP refused from %s\n", inet_ntoa(fsock.address)); free_p(bp); return; } } cmd = PULLCHAR(&bp); /* Check the version of the frame */ if(PULLCHAR(&bp) != RIPVERSION){ free_p(bp); Rip_stat.version++; return; } switch(cmd){ case RIPCMD_RESPONSE: if(Rip_trace > 1) printf("RIPCMD_RESPONSE from %s \n",inet_ntoa(fsock.address)); Rip_stat.response++; /* See if this interface is on our broadcast list; if so, * use its interval to calculate entry lifetimes. Otherwise, * use default */ ttl = RIP_TTL; for(rl=Rip_list; rl != NULLRL; rl = rl->next){ if(rl->iface == iface){ ttl = rl->interval * 4; break; } } (void)pull16(&bp); /* remove one word of padding */ while(len_p(bp) >= RIPROUTE){ pullentry(&entry,&bp); proc_rip(iface,fsock.address,&entry,ttl); } /* If we can't reach the sender of this update, or if * our existing route is not through the interface we * got this update on, add him as a host specific entry */ if((rp = rt_blookup(fsock.address,32)) != NULLROUTE){ /* Host-specific route already exists, refresh it */ start_timer(&rp->timer); } else if((rp = rt_lookup(fsock.address)) == NULLROUTE || rp->iface != iface){ entry.addr_fam = RIP_IPFAM; entry.target = fsock.address; entry.metric = 0; /* will get incremented to 1 */ proc_rip(iface,fsock.address,&entry,ttl); } if(Rip_merge) rt_merge(Rip_trace); rip_trigger(); break; case RIPCMD_REQUEST: if(Rip_trace > 1) printf("RIPCMD_REQUEST\n"); Rip_stat.request++; /* For now, just send the whole table with split horizon * enabled when the source port is RIP_PORT, and send * the whole table with split horizon disable when another * source port is used. This should be replaced with a more * complete implementation that checks for non-global requests */ if(fsock.port == RIP_PORT) send_routes(fsock.address,fsock.port,1,0,1); else send_routes(fsock.address,fsock.port,0,0,1); break; default: if(Rip_trace > 1) printf("RIPCMD: Unknown Type\n"); Rip_stat.unknown++; break; } /* switch */ free_p(bp); } /* Apply a set of heuristics for determining the number of significant bits * (i.e., the address mask) in the target address. Needed since RIP doesn't * include the address mask for each entry. */ int nbits(target) int32 target; { int bits; if(target == 0) return 0; /* Special case: 0.0.0.0 is the default route */ /* Check the host-part bytes of * the address to check for byte-wide zeros * which we'll consider to be subnet routes. * e.g. 44.80.0.0 will be considered to be equal to 44.80/16 * whereas 44.80.1.0 will be considered to be 44.80.1/24 */ switch (hibyte(hiword(target)) >> 6) { case 3: /* Class C address */ /*is it a host address ? i.e. are there any 1's in the * host part ? */ if(target & 0xff) bits = 32; else bits = 24; break; case 2: /* Class B address */ if(target & 0xff) bits = 32; else if(target & 0xff00) bits = 24; else bits = 16; break; case 0: /* Class A address */ case 1: if(target & 0xff) bits = 32; else if(target & 0xff00) bits = 24; else if(target & 0xff0000) bits = 16; else bits = 8; } return bits; } /* Remove and process a RIP response entry from a packet */ static void proc_rip(iface,gateway,ep,ttl) struct iface *iface; int32 gateway; register struct rip_route *ep; int32 ttl; { unsigned int bits; register struct route *rp; int add = 0; /* action flags */ int drop = 0; int trigger = 0; if(ep->addr_fam != RIP_IPFAM) { /* Skip non-IP addresses */ if(Rip_trace > 1) printf("RIP_rx: Not an IP RIP packet !\n"); Rip_stat.addr_family++; return; } /* Guess at the mask, since it's not explicit */ bits = nbits(ep->target); /* Don't ever add a route to myself through somebody! */ if(bits == 32 && ismyaddr(ep->target) != NULLIF){ if(Rip_trace > 1){ printf("route to self: %s %ld\n", inet_ntoa(ep->target),ep->metric); } return; } /* Update metric to reflect link cost */ ep->metric++; ep->metric = min(ep->metric,RIP_INFINITY); /* Find existing entry, if any */ rp = rt_blookup(ep->target,bits); /* Don't touch private routes */ if(rp != NULLROUTE && (rp->flags & RTPRIVATE)) return; if(rp == NULLROUTE){ if(ep->metric < RIP_INFINITY){ /* New route; add it and trigger an update */ add++; trigger++; } } else if(rp->metric == RIP_INFINITY){ /* Route is in hold-down; ignore this guy */ if(Rip_trace > 0){ printf("ignored (hold-down): %s %lu\n", inet_ntoa(ep->target),ep->metric); } } else if(rp->gateway == gateway && rp->iface == iface){ /* This is the gateway for the entry we already have; * restart the timer */ set_timer(&rp->timer,ttl*1000L); start_timer(&rp->timer); if(rp->metric != ep->metric){ /* Metric has changed. Update it and trigger an * update. If route has become unavailable, start * the hold-down timeout. */ if(Rip_trace){ printf("metric change: %s %lu -> %lu\n", inet_ntoa(ep->target),rp->metric,ep->metric); } if(ep->metric == RIP_INFINITY) rt_timeout(rp); /* Enter hold-down timeout */ else rp->metric = ep->metric; trigger++; } } else { /* Entry is from a different gateway than the current route */ if(ep->metric < rp->metric){ /* Switch to a new gateway */ if(Rip_trace > 0){ printf("metric better: %s %lu\n", inet_ntoa(ep->target),ep->metric); } drop++; add++; trigger++; } else { /* Metric is no better, stay with current route */ if(Rip_trace > 1){ printf("metric not better: %s %lu\n", inet_ntoa(ep->target),ep->metric); } } } if(drop){ /* Switching to a better gateway; delete old entry */ if(Rip_trace){ printf("route drop [%s]/%u", inet_ntoa(ep->target),bits); if(rp != NULLROUTE) printf(" %s %s %lu",rp->iface->name, inet_ntoa(rp->gateway),rp->metric); printf("\n"); } rt_drop(ep->target,bits); } if(add){ /* Add a new entry */ if(Rip_trace > 0){ printf("route add [%s]/%u %s",inet_ntoa(ep->target), bits,iface->name); printf(" [%s] %u\n",inet_ntoa(gateway), (int)ep->metric); } rp = rt_add(ep->target,(unsigned) bits,gateway,iface, (int) ep->metric,ttl,0); } /* If the route changed, mark it for a triggered update */ if(trigger){ rp->flags |= RTTRIG; } } /* Send a RIP request packet to the specified destination */ int ripreq(dest,replyport) int32 dest; int16 replyport; { struct mbuf *bp; struct socket lsock,fsock; char *cp; lsock.address = INADDR_ANY; lsock.port = replyport; /* if we were given a valid dest addr, ask it (the routers on that net) * for a default gateway */ if(dest == 0) return 0; fsock.address = dest; fsock.port = RIP_PORT; /* Send out one RIP Request packet as a broadcast to 'dest' */ if((bp = alloc_mbuf(RIPHEADER + RIPROUTE)) == NULLBUF) return -1; cp = putheader(bp->data,RIPCMD_REQUEST,RIPVERSION); cp = putentry(cp,0,0L,RIP_INFINITY); bp->cnt = RIPHEADER + RIPROUTE; send_udp(&lsock, &fsock,0,0,bp,bp->cnt,0,0); Rip_stat.output++; return 0; } void pullentry(ep,bpp) register struct rip_route *ep; struct mbuf **bpp; { ep->addr_fam = pull16(bpp); (void)pull16(bpp); ep->target = pull32(bpp); (void)pull32(bpp); (void)pull32(bpp); ep->metric = pull32(bpp); } /* Write the header of a RIP packet */ static char * putheader(cp,command,version) register char *cp; char command; char version; { *cp++ = command; *cp++ = version; return put16(cp,0); } /* Write a single entry into a rip packet */ static char * putentry(cp,fam,target,metric) register char *cp; int16 fam; int32 target; int32 metric; { cp = put16(cp,fam); cp = put16(cp,0); cp = put32(cp,target); cp = put32(cp,0L); cp = put32(cp,0L); return put32(cp,metric); } /* Route timeout handler. If route has already been marked for deletion * then delete it. Otherwise mark for deletion and restart timer. */ void rt_timeout(s) void *s; { register struct route *rp = (struct route *)s; stop_timer(&rp->timer); if(rp->metric < RIP_INFINITY){ rp->metric = RIP_INFINITY; if(dur_timer(&rp->timer) == 0) set_timer(&rp->timer,RIP_TTL*1000L); /* wait 2/3 of timeout before garbage collect */ set_timer(&rp->timer,dur_timer(&rp->timer)*2/3); rp->timer.func = (void *)rt_timeout; rp->timer.arg = (void *)rp; start_timer(&rp->timer); /* Route changed; mark it for triggered update */ rp->flags |= RTTRIG; rip_trigger(); } else { rt_drop(rp->target,rp->bits); } }