Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.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
{
        uchar   family[2];
        uchar   port[2];
        uchar   addr[Pasize];
        uchar   pad[8];
        uchar   metric[4];
};
typedef struct Ripmsg   Ripmsg;
struct Ripmsg
{
        uchar   type;
        uchar   vers;
        uchar   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;

        uchar   dest[Pasize];
        uchar   mask[Pasize];
        uchar   gate[Pasize];
        int     metric;
        int     inuse;
        long    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;
        uchar   addr[Pasize];   /* my address */
        uchar   mask[Pasize];   /* subnet mask */
        uchar   net[Pasize];    /* subnet */
        uchar   *cmask;         /* class mask */
        uchar   cnet[Pasize];   /* class net */
};
struct {
        Ifc     ifc[Nifc];
        int     nifc;
} ialloc;

/*
 *  specific networks to broadcast on
 */
typedef struct Bnet Bnet;
struct Bnet
{
        Bnet    *next;
        uchar   addr[Pasize];
};
Bnet    *bnets;

int     ripfd;
long    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*);
uchar   *getmask(uchar*);
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) {
                errstr(sysbuf, sizeof sysbuf);
                fprint(2, "routed: %s: %s\n", buf, sysbuf);
        }
        else
                fprint(2, "routed: %s\n", buf);
        exits(buf);
}

ulong
v4parseipmask(uchar *ip, char *p)
{
        ulong x;
        uchar v6ip[IPaddrlen];

        x = parseipmask(v6ip, p);
        memmove(ip, v6ip+IPv4off, 4);
        return x;
}

uchar*
v4defmask(uchar *ip)
{
        uchar v6ip[IPaddrlen];

        v4tov6(v6ip, ip);
        ip = defmask(v6ip);
        return ip+IPv4off;
}

void
v4maskip(uchar *from, uchar *mask, uchar *to)
{
        int i;

        for(i = 0; i < Pasize; i++)
                *to++ = *from++ & *mask++;
}

void
v6tov4mask(uchar *v4, uchar *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"))
                noted(NCONT);
        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];
        uchar 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(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();

        notify(ding);

        ripfd = openport();
        for(;;) {
                diff = btime - time(0);
                if(diff <= 0){
                        if(dobroadcast)
                                broadcast();
                        timeoutroutes();

                        btime = time(0) + 2*60;
                        diff = 2*60;
                }
                alarm(diff*1000);
                n = read(ripfd, buf, sizeof(buf));
                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 = 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
 */
ulong
rhash(uchar *d)
{
        ulong h;
        uchar 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)
{
        ulong 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 = 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);
        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;
        ulong h;
        Route *hp;
        uchar net[Pasize];

        /*
         *  don't install routes whose gateway is 00000000
         */
        if(equivip(r->gate, ralloc.def.dest))
                return;

        fd = 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);
                        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);
        close(fd);
}

/*
 *  return true of dest is on net
 */
int
onnet(uchar *dest, uchar *net, uchar *netmask)
{
        uchar 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.
 */
uchar*
getmask(uchar *dest)
{
        int i;
        Ifc *ip;
        ulong mask, nmask;
        uchar *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;
        uchar 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){
                                write(ripfd, mbuf, Udphdrsize + 4 + n*20);
                                n = 0;
                        }
                }
        }

        if(n && !readonly)
                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;
        long 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;
                }
        }
}