jehanne/sys/src/cmd/ip/rip.c

719 lines
13 KiB
C

/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#include <u.h>
#include <lib9.h>
#include <bio.h>
#include <ip.h>
enum
{
Version= 1,
Pasize= 4,
/*
* definitions that are innately tied to BSD
*/
AF_INET= 2,
AF_UNSPEC= 0,
/*
* Packet types.
*/
Request= 1,
Response= 2,
Traceon= 3,
Traceoff= 4,
Infinity= 16, /* infinite hop count */
Maxpacket= 488, /* largest packet body */
};
/*
* network info
*/
typedef struct Rip Rip;
struct Rip
{
uint8_t family[2];
uint8_t port[2];
uint8_t addr[Pasize];
uint8_t pad[8];
uint8_t metric[4];
};
typedef struct Ripmsg Ripmsg;
struct Ripmsg
{
uint8_t type;
uint8_t vers;
uint8_t pad[2];
Rip rip[1]; /* the rest of the packet consists of routes */
};
enum
{
Maxroutes= (Maxpacket-4)/sizeof(Ripmsg),
};
/*
* internal route info
*/
enum
{
Nroute= 2048, /* this has to be smaller than what /ip has */
Nhash= 256, /* routing hash buckets */
Nifc= 16,
};
typedef struct Route Route;
struct Route
{
Route *next;
uint8_t dest[Pasize];
uint8_t mask[Pasize];
uint8_t gate[Pasize];
int metric;
int inuse;
int32_t time;
};
struct {
Route route[Nroute];
Route *hash[Nhash];
int nroute;
Route def; /* default route (immutable by us) */
} ralloc;
typedef struct Ifc Ifc;
struct Ifc
{
int bcast;
uint8_t addr[Pasize]; /* my address */
uint8_t mask[Pasize]; /* subnet mask */
uint8_t net[Pasize]; /* subnet */
uint8_t *cmask; /* class mask */
uint8_t cnet[Pasize]; /* class net */
};
struct {
Ifc ifc[Nifc];
int nifc;
} ialloc;
/*
* specific networks to broadcast on
*/
typedef struct Bnet Bnet;
struct Bnet
{
Bnet *next;
uint8_t addr[Pasize];
};
Bnet *bnets;
int ripfd;
int32_t now;
int debug;
int readonly;
char routefile[256];
char netdir[256];
int openport(void);
void readroutes(void);
void readifcs(void);
void considerroute(Route*);
void installroute(Route*);
void removeroute(Route*);
uint8_t *getmask(uint8_t*);
void broadcast(void);
void timeoutroutes(void);
void
fatal(int syserr, char *fmt, ...)
{
char buf[ERRMAX], sysbuf[ERRMAX];
va_list arg;
va_start(arg, fmt);
vseprint(buf, buf+sizeof(buf), fmt, arg);
va_end(arg);
if(syserr) {
sys_errstr(sysbuf, sizeof sysbuf);
fprint(2, "routed: %s: %s\n", buf, sysbuf);
}
else
fprint(2, "routed: %s\n", buf);
exits(buf);
}
uint32_t
v4parseipmask(uint8_t *ip, char *p)
{
uint32_t x;
uint8_t v6ip[IPaddrlen];
x = parseipmask(v6ip, p);
memmove(ip, v6ip+IPv4off, 4);
return x;
}
uint8_t*
v4defmask(uint8_t *ip)
{
uint8_t v6ip[IPaddrlen];
v4tov6(v6ip, ip);
ip = defmask(v6ip);
return ip+IPv4off;
}
void
v4maskip(uint8_t *from, uint8_t *mask, uint8_t *to)
{
int i;
for(i = 0; i < Pasize; i++)
*to++ = *from++ & *mask++;
}
void
v6tov4mask(uint8_t *v4, uint8_t *v6)
{
memmove(v4, v6+IPv4off, 4);
}
#define equivip(a, b) (memcmp((a), (b), Pasize) == 0)
void
ding(void *u, char *msg)
{
USED(u);
if(strstr(msg, "alarm"))
sys_noted(NCONT);
sys_noted(NDFLT);
}
void
usage(void)
{
fprint(2, "usage: %s [-bnd] [-x netmtpt]\n", argv0);
exits("usage");
}
void
main(int argc, char *argv[])
{
int dobroadcast, i, n;
long diff;
char *p;
char buf[2*1024];
uint8_t raddr[Pasize];
Bnet *bn, **l;
Udphdr *up;
Rip *r;
Ripmsg *m;
Route route;
static long btime;
setnetmtpt(netdir, sizeof(netdir), nil);
dobroadcast = 0;
ARGBEGIN{
case 'b':
dobroadcast++;
break;
case 'd':
debug++;
break;
case 'n':
readonly++;
break;
case 'x':
p = ARGF();
if(p == nil)
usage();
setnetmtpt(netdir, sizeof(netdir), p);
break;
default:
usage();
}ARGEND
/* specific broadcast nets */
l = &bnets;
while(argc > 0){
bn = (Bnet*)malloc(sizeof(Bnet));
if(bn == 0)
fatal(1, "out of mem");
v4parseip(bn->addr, *argv);
*l = bn;
l = &bn->next;
argc--;
argv++;
dobroadcast++;
}
/* command returns */
if(!debug)
switch(sys_rfork(RFNOTEG|RFPROC|RFFDG|RFNOWAIT)) {
case -1:
fatal(1, "fork");
case 0:
break;
default:
exits(0);
}
fmtinstall('E', eipfmt);
fmtinstall('V', eipfmt);
snprint(routefile, sizeof(routefile), "%s/iproute", netdir);
snprint(buf, sizeof(buf), "%s/iproute", netdir);
now = time(0);
readifcs();
readroutes();
sys_notify(ding);
ripfd = openport();
for(;;) {
diff = btime - time(0);
if(diff <= 0){
if(dobroadcast)
broadcast();
timeoutroutes();
btime = time(0) + 2*60;
diff = 2*60;
}
sys_alarm(diff*1000);
n = jehanne_read(ripfd, buf, sizeof(buf));
sys_alarm(0);
if(n <= 0)
continue;
n = (n - Udphdrsize - 4) / sizeof(Rip);
if(n <= 0)
continue;
up = (Udphdr*)buf;
m = (Ripmsg*)(buf+Udphdrsize);
if(m->type != Response || m->vers != Version)
continue;
v6tov4(raddr, up->raddr);
/* ignore our own messages */
for(i = 0; i < ialloc.nifc; i++)
if(equivip(ialloc.ifc[i].addr, raddr))
continue;
now = time(0);
for(r = m->rip; r < &m->rip[n]; r++){
memmove(route.gate, raddr, Pasize);
memmove(route.mask, getmask(r->addr), Pasize);
v4maskip(r->addr, route.mask, route.dest);
route.metric = nhgetl(r->metric) + 1;
if(route.metric < 1)
continue;
considerroute(&route);
}
}
/* not reached */
}
int
openport(void)
{
int ripctl, rip;
char data[128], devdir[40];
snprint(data, sizeof(data), "%s/udp!*!rip", netdir);
ripctl = announce(data, devdir);
if(ripctl < 0)
fatal(1, "can't announce");
if(fprint(ripctl, "headers") < 0)
fatal(1, "can't set header mode");
sprint(data, "%s/data", devdir);
rip = sys_open(data, ORDWR);
if(rip < 0)
fatal(1, "open udp data");
return rip;
}
Ipifc *ifcs;
void
readifcs(void)
{
Ipifc *ifc;
Iplifc *lifc;
Ifc *ip;
Bnet *bn;
Route route;
int i;
ifcs = readipifc(netdir, ifcs, -1);
i = 0;
for(ifc = ifcs; ifc != nil; ifc = ifc->next){
for(lifc = ifc->lifc; lifc != nil && i < Nifc; lifc = lifc->next){
// ignore any interfaces that aren't v4
if(memcmp(lifc->ip, v4prefix, IPaddrlen-IPv4addrlen) != 0)
continue;
ip = &ialloc.ifc[i++];
v6tov4(ip->addr, lifc->ip);
v6tov4mask(ip->mask, lifc->mask);
v6tov4(ip->net, lifc->net);
ip->cmask = v4defmask(ip->net);
v4maskip(ip->net, ip->cmask, ip->cnet);
ip->bcast = 0;
/* add as a route */
memmove(route.mask, ip->mask, Pasize);
memmove(route.dest, ip->net, Pasize);
memset(route.gate, 0, Pasize);
route.metric = 0;
considerroute(&route);
/* mark as broadcast */
if(bnets == 0)
ip->bcast = 1;
else for(bn = bnets; bn; bn = bn->next)
if(memcmp(bn->addr, ip->net, Pasize) == 0){
ip->bcast = 1;
break;
}
}
}
ialloc.nifc = i;
}
void
readroutes(void)
{
int n;
char *p;
Biobuf *b;
char *f[6];
Route route;
b = Bopen(routefile, OREAD);
if(b == 0)
return;
while(p = Brdline(b, '\n')){
p[Blinelen(b)-1] = 0;
n = getfields(p, f, 6, 1, " \t");
if(n < 5)
continue;
v4parseip(route.dest, f[0]);
v4parseipmask(route.mask, f[1]);
v4parseip(route.gate, f[2]);
route.metric = Infinity;
if(equivip(route.dest, ralloc.def.dest)
&& equivip(route.mask, ralloc.def.mask))
memmove(ralloc.def.gate, route.gate, Pasize);
else if(!equivip(route.dest, route.gate) && strchr(f[3], 'i') == 0)
considerroute(&route);
}
Bterm(b);
}
/*
* route's hashed by net, not subnet
*/
uint32_t
rhash(uint8_t *d)
{
uint32_t h;
uint8_t net[Pasize];
v4maskip(d, v4defmask(d), net);
h = net[0] + net[1] + net[2];
return h % Nhash;
}
/*
* consider installing a route. Do so only if it is better than what
* we have.
*/
void
considerroute(Route *r)
{
uint32_t h;
Route *hp;
if(debug)
fprint(2, "consider %16V & %16V -> %16V %d\n", r->dest, r->mask, r->gate, r->metric);
r->next = 0;
r->time = now;
r->inuse = 1;
/* don't allow our default route to be highjacked */
if(equivip(r->dest, ralloc.def.dest) || equivip(r->mask, ralloc.def.mask))
return;
h = rhash(r->dest);
for(hp = ralloc.hash[h]; hp; hp = hp->next){
if(equivip(hp->dest, r->dest)){
/*
* found a match, replace if better (or much newer)
*/
if(r->metric < hp->metric || now-hp->time > 5*60){
removeroute(hp);
memmove(hp->mask, r->mask, Pasize);
memmove(hp->gate, r->gate, Pasize);
hp->metric = r->metric;
installroute(hp);
}
if(equivip(hp->gate, r->gate))
hp->time = now;
return;
}
}
/*
* no match, look for space
*/
for(hp = ralloc.route; hp < &ralloc.route[Nroute]; hp++)
if(hp->inuse == 0)
break;
if(hp == 0)
fatal(0, "no more routes");
memmove(hp, r, sizeof(Route));
hp->next = ralloc.hash[h];
ralloc.hash[h] = hp;
installroute(hp);
}
void
removeroute(Route *r)
{
int fd;
fd = sys_open(routefile, ORDWR);
if(fd < 0){
fprint(2, "can't open oproute\n");
return;
}
if(!readonly)
fprint(fd, "delete %V", r->dest);
if(debug)
fprint(2, "removeroute %V\n", r->dest);
sys_close(fd);
}
/*
* pass a route to the kernel or /ip. Don't bother if it is just the default
* gateway.
*/
void
installroute(Route *r)
{
int fd;
uint32_t h;
Route *hp;
uint8_t net[Pasize];
/*
* don't install routes whose gateway is 00000000
*/
if(equivip(r->gate, ralloc.def.dest))
return;
fd = sys_open(routefile, ORDWR);
if(fd < 0){
fprint(2, "can't open oproute\n");
return;
}
h = rhash(r->dest);
/*
* if the gateway is the same as the default gateway
* we may be able to avoid a entry in the kernel
*/
if(equivip(r->gate, ralloc.def.gate)){
/*
* look for a less specific match
*/
for(hp = ralloc.hash[h]; hp; hp = hp->next){
v4maskip(hp->mask, r->dest, net);
if(equivip(net, hp->dest) && !equivip(hp->gate, ralloc.def.gate))
break;
}
/*
* if no less specific match, just use the default
*/
if(hp == 0){
if(!readonly)
fprint(fd, "delete %V", r->dest);
if(debug)
fprint(2, "delete %V\n", r->dest);
sys_close(fd);
return;
}
}
if(!readonly)
fprint(fd, "add %V %V %V", r->dest, r->mask, r->gate);
if(debug)
fprint(2, "add %V & %V -> %V\n", r->dest, r->mask, r->gate);
sys_close(fd);
}
/*
* return true of dest is on net
*/
int
onnet(uint8_t *dest, uint8_t *net, uint8_t *netmask)
{
uint8_t dnet[Pasize];
v4maskip(dest, netmask, dnet);
return equivip(dnet, net);
}
/*
* figure out what mask to use, if we have a direct connected network
* with the same class net use its subnet mask.
*/
uint8_t*
getmask(uint8_t *dest)
{
int i;
Ifc *ip;
uint32_t mask, nmask;
uint8_t *m;
m = 0;
mask = 0xffffffff;
for(i = 0; i < ialloc.nifc; i++){
ip = &ialloc.ifc[i];
if(onnet(dest, ip->cnet, ip->cmask)){
nmask = nhgetl(ip->mask);
if(nmask < mask){
mask = nmask;
m = ip->mask;
}
}
}
if(m == 0)
m = v4defmask(dest);
return m;
}
/*
* broadcast routes onto all networks
*/
void
sendto(Ifc *ip)
{
int h, n;
uint8_t raddr[Pasize], mbuf[Udphdrsize+512];
Ripmsg *m;
Route *r;
Udphdr *u;
u = (Udphdr*)mbuf;
for(n = 0; n < Pasize; n++)
raddr[n] = ip->net[n] | ~(ip->mask[n]);
v4tov6(u->raddr, raddr);
hnputs(u->rport, 520);
m = (Ripmsg*)(mbuf+Udphdrsize);
m->type = Response;
m->vers = Version;
if(debug)
fprint(2, "to %V\n", u->raddr);
n = 0;
for(h = 0; h < Nhash; h++){
for(r = ralloc.hash[h]; r; r = r->next){
/*
* don't send any route back to the net
* it came from
*/
if(onnet(r->gate, ip->net, ip->mask))
continue;
/*
* don't tell a network about itself
*/
if(equivip(r->dest, ip->net))
continue;
/*
* don't tell nets about other net's subnets
*/
if(!equivip(r->mask, v4defmask(r->dest))
&& !equivip(ip->cmask, v4defmask(r->dest)))
continue;
memset(&m->rip[n], 0, sizeof(m->rip[n]));
memmove(m->rip[n].addr, r->dest, Pasize);
if(r->metric < 1)
hnputl(m->rip[n].metric, 1);
else
hnputl(m->rip[n].metric, r->metric);
hnputs(m->rip[n].family, AF_INET);
if(debug)
fprint(2, " %16V & %16V -> %16V %2d\n",
r->dest, r->mask, r->gate, r->metric);
if(++n == Maxroutes && !readonly){
jehanne_write(ripfd, mbuf, Udphdrsize + 4 + n*20);
n = 0;
}
}
}
if(n && !readonly)
jehanne_write(ripfd, mbuf, Udphdrsize+4+n*20);
}
void
broadcast(void)
{
int i;
readifcs();
for(i = 0; i < ialloc.nifc; i++){
if(ialloc.ifc[i].bcast)
sendto(&ialloc.ifc[i]);
}
}
/*
* timeout any routes that haven't been refreshed and aren't wired
*/
void
timeoutroutes(void)
{
int h;
int32_t now;
Route *r, **l;
now = time(0);
for(h = 0; h < Nhash; h++){
l = &ralloc.hash[h];
for(r = *l; r; r = *l){
if(r->metric < Infinity && now - r->time > 10*60){
removeroute(r);
r->inuse = 0;
*l = r->next;
continue;
}
l = &r->next;
}
}
}