/* net/rom level 3 low level processing
 * Copyright 1989 by Daniel M. Frank, W9NK.  Permission granted for
 * non-commercial distribution only.
 */
/* Mods by PA0GRI and WG7J */
  
#include <ctype.h>
#include "global.h"
#ifdef NETROM
#include "mbuf.h"
#include "pktdrvr.h"
#include "iface.h"
#include "netuser.h"
#include "timer.h"
#include "arp.h"
#include "slip.h"
#include "ax25.h"
#include "netrom.h"
#include "nr4.h"
#include "lapb.h"
#include "socket.h"
#include "trace.h"
#include "ip.h"
#include "commands.h"
  
/* IF the following is defined,
 * when we receive a nodes broadcast of a new neighbour,
 * we will immediately respond with a nodes broadcast on that interface
 * This speeds up route discovery at bootup etc..
 * 920422 - WG7J
 */
#define NR_BC_RESPOND 1
  
static int accept_bc __ARGS((char *addr,struct iface *ifp));
struct nr_bind *find_best __ARGS((struct nr_bind *list,unsigned obso));
static struct nr_bind *find_binding __ARGS((struct nr_bind *list,struct nrnbr_tab *neighbor));
void nrresetlinks __ARGS((struct nrroute_tab *rp)); /* s/b in a header file */
static struct nrnf_tab *find_nrnf __ARGS((char *, struct iface *));
static struct nr_bind *find_worst __ARGS((struct nr_bind *list));
#ifdef  notdef
static char *nr_getroute __ARGS((char *));
#endif
#ifdef NRRAW
static struct raw_nr *Raw_nr;
#endif
static int nr_aliasck __ARGS(( char *alias));
  
struct nrnbr_tab *Nrnbr_tab[NRNUMCHAINS];
struct nrroute_tab *Nrroute_tab[NRNUMCHAINS];
struct nrnf_tab *Nrnf_tab[NRNUMCHAINS];
unsigned Nr_nfmode = NRNF_NOFILTER;
unsigned short Nr_ttl = 10;
static unsigned Obso_init = 6;
static unsigned Obso_minbc = 5;
static unsigned Nr_maxroutes = 5;
unsigned Nr_autofloor = 10;
int Nr_derate = 1;          /* Allow automatic derating of routes */
int Nr_promisc = 0;         /* behave promisuously with nr bcasts or not? */
struct iface *Nr_iface;
  
extern char Nralias[ALEN+1];
  
/* send a NET/ROM layer 3 datagram */
void
nr3output(dest, data)
char *dest;
struct mbuf *data;
{
    struct nr3hdr n3hdr;
    struct mbuf *n3b;
  
    memcpy(n3hdr.dest,dest,AXALEN); /* copy destination field */
    n3hdr.ttl = Nr_ttl; /* time to live from initializer parm */
  
    if((n3b = htonnr3(&n3hdr)) == NULLBUF){
        free_p(data);
        return;
    }
    append(&n3b, data);
    /* The null interface indicates that the packet needs to have */
    /* an appropriate source address inserted by nr_route */
    nr_route(n3b,NULLAX25);
}
  
/* send IP datagrams across a net/rom network connection */
int
nr_send(bp,iface,gateway,prec,del,tput,rel)
struct mbuf *bp;
struct iface *iface;
int32 gateway;
int prec;
int del;
int tput;
int rel;
{
    struct arp_tab *arp;
  
    dump(iface,IF_TRACE_OUT,CL_NETROM,bp);
  
    if((arp = arp_lookup(ARP_NETROM,gateway,iface)) == NULLARP){
        free_p(bp); /* drop the packet if no route */
        return -1;
    }
  
    /*
       these are already taken care of when the packet reaches
       nr_route() to be routed - WG7J
  
    iface->rawsndcnt++;
    iface->lastsent = Nr_iface->lastsent = secclock();
    */
  
    Nr_iface->ipsndcnt++;
    nr_sendraw(arp->hw_addr, NRPROTO_IP, NRPROTO_IP, bp);
    return 0;
}
  
/* Send arbitrary protocol data on top of a NET/ROM connection */
void
nr_sendraw(dest,family,proto,data)
char *dest;
unsigned family;
unsigned proto;
struct mbuf *data;
{
    struct mbuf *pbp;
    struct nr4hdr n4hdr;
  
    /* Create a "network extension" transport header */
    n4hdr.opcode = NR4OPPID;
    n4hdr.u.pid.family = family;
    n4hdr.u.pid.proto = proto;
  
    if((pbp = htonnr4(&n4hdr)) == NULLBUF){
        free_p(data);
        return;
    }
    append(&pbp,data);      /* Append the data to that */
    nr3output(dest, pbp); /* and pass off to level 3 code */
}
  
#ifdef NRRAW
/* Arrange for receipt of raw NET/ROM datagrams */
struct raw_nr *
raw_nr(protocol)
char protocol;
{
    register struct raw_nr *rp;
  
    rp = (struct raw_nr *)callocw(1,sizeof(struct raw_nr));
    rp->protocol = protocol;
    rp->next = Raw_nr;
    if(rp->next != NULLRNR)
        rp->next->prev = rp;
    Raw_nr = rp;
    return rp;
}
  
/* Free a raw NET/ROM descriptor */
void
del_rnr(rpp)
struct raw_nr *rpp;
{
    register struct raw_nr *rp;
  
    /* Do sanity check on arg */
    for(rp = Raw_nr;rp != NULLRNR;rp = rp->next)
        if(rp == rpp)
            break;
    if(rp == NULLRNR)
        return; /* Doesn't exist */
  
    /* Unlink */
    if(rp->prev != NULLRNR)
        rp->prev->next = rp->next;
    else
        Raw_nr = rp->next;
    if(rp->next != NULLRNR)
        rp->next->prev = rp->prev;
    /* Free resources */
    free_q(&rp->rcvq);
    free((char *)rp);
}
#endif /* NRRAW */
  
/* Route net/rom network layer packets.
 */
void
nr_route(bp, iaxp)
struct mbuf *bp;            /* network packet */
struct ax25_cb *iaxp;           /* incoming ax25 control block */
{
    struct nr3hdr n3hdr;
    struct nr4hdr n4hdr;
    struct ax25_cb *axp;
    struct mbuf *hbp, *pbp;
#ifdef NRRAW
    struct raw_nr *rnr;
#endif
    struct nrnbr_tab *np;
    struct nrroute_tab *rp;
    struct nr_bind *bindp;
    struct iface *iface;
  
    if(ntohnr3(&n3hdr,&bp) == -1){
        free_p(bp);
        return;
    }
    /* If this isn't an internally generated network packet,
     * give the router a chance to record a route back to the
     * sender, in case they aren't in the local node's routing
     * table yet.
     */
    /* Add some statictics gathering - WG7J */
    if(iaxp != NULLAX25){
        /* incoming packet, find the interface number */
        if(!(iaxp->iface->flags & IS_NR_IFACE)){   /* Not a net/rom interface! */
            free_p(bp);
            return;
        }
  
        /* Add (possibly) a zero-quality recorded route via */
        /* the neighbor from which this packet was received */
        /* Note that this doesn't work with digipeated neighbors. */
        (void) nr_routeadd("##temp",n3hdr.source,iaxp->iface,0,iaxp->remote,0,1);
  
        Nr_iface->rawrecvcnt++;
        Nr_iface->lastrecv = secclock();
  
    } else {
        Nr_iface->rawsndcnt++;
        Nr_iface->lastsent = secclock();
    }
  
    /* A packet from me, to me, can only be one thing:
     * a horrible routing loop.  This will probably result
     * from a bad manual ARP entry, but we should fix these
     * obscure errors as we find them.
     */
    if(addreq(Nr_iface->hwaddr,n3hdr.dest)){
        /* Toss if from me */
        if(iaxp == NULLAX25 || ntohnr4(&n4hdr,&bp) == -1){
            free_p(bp);
            return;
        }
#ifdef G8BPQ
        /* Add the G8BPQ stuff to our route info - WG7J */
        if(n4hdr.flags & NR4_G8BPQMASK)
            if((rp = find_nrroute(n3hdr.source)) != NULLNRRTAB)
                if(n4hdr.flags & NR4_G8BPQTTL) {
                    rp->hops = n4hdr.u.conack.ttl - n3hdr.ttl + 1;
                    rp->flags |= G8BPQ_NODETTL;
                } else {
                    rp->irtt = n4hdr.u.conreq.t4init;
                    rp->flags |= G8BPQ_NODERTT;
                }
#endif
  
        if((n4hdr.opcode & NR4OPCODE) == NR4OPPID){
#ifdef NRRAW
            for(rnr = Raw_nr;rnr!=NULLRNR;rnr = rnr->next){
                if(rnr->protocol!=n4hdr.u.pid.family ||
                    rnr->protocol != n4hdr.u.pid.proto)
                    continue;
                /* Duplicate the data portion, and put the
                 * level 3 header back on
                 */
                dup_p(&pbp,bp,0,len_p(bp));
                if(pbp != NULLBUF &&
                (hbp = htonnr3(&n3hdr)) != NULLBUF){
                    append(&hbp,pbp);
                    enqueue(&rnr->rcvq,hbp);
                } else {
                    free_p(pbp);
                    free_p(hbp);
                }
            }
#endif
            /* IP does not use a NET/ROM level 3 socket */
            if(n4hdr.u.pid.family == NRPROTO_IP
            && n4hdr.u.pid.proto == NRPROTO_IP) {
                if(Nr_iface->flags & LOG_IPHEARD) {
                    struct mbuf *nbp;
                    int pid;
                    struct ip ip;
                    int len;
  
                    len = len_p(bp);
                    if(dup_p(&nbp, bp, 0, len) == len) {
                        ntohip(&ip,&nbp);
                        if(ip.version == IPVERSION)
                            log_ipheard(ip.source,Nr_iface);
                    }
                    free_p(nbp);
                }
                Nr_iface->iprecvcnt++;
                ip_route(iaxp->iface,bp,0);
            } else        /* we don't do this proto */
                free_p(bp);
        } else {
            /* Must be net/rom transport: */
            nr4input(&n4hdr,bp);
        }
        return;
    }
  
#ifdef G8BPQ
    {
        struct mbuf *nbp;
  
    /* If this is not a locally generated packet,
     * Add the G8BPQ stuff to our route info - WG7J
     */
        if(iaxp != NULLAX25) {
            dup_p(&nbp,bp,0,len_p(bp));
            if(ntohnr4(&n4hdr,&nbp) != -1)
                if(n4hdr.flags & NR4_G8BPQMASK)
                    if((rp = find_nrroute(n3hdr.source)) != NULLNRRTAB) {
                        if(n4hdr.flags & NR4_G8BPQTTL) {
                            rp->hops = n4hdr.u.conack.ttl - n3hdr.ttl + 1;
                            rp->flags |= G8BPQ_NODETTL;
                        } else {
                            rp->irtt = n4hdr.u.conreq.t4init;
                            rp->flags |= G8BPQ_NODERTT;
                        }
                    }
            free_p(nbp);
        }
    }
#endif
  
    /* Now finally go route it */
    if((rp = find_nrroute(n3hdr.dest)) == NULLNRRTAB) {
        /* no route, drop the packet */
        free_p(bp);
        return;
    }
  
    if((bindp = find_best(rp->routes,1)) == NULLNRBIND){
        /* This shouldn't happen yet, but might if we add */
        /* dead route detection */
        free_p(bp);
        return;
    }
  
    np = bindp->via;
    iface = np->iface;
  
    /* Now check to see if iaxp is null.  That is */
    /* a signal that the packet originates here, */
    /* so we need to insert the callsign of the appropriate  */
    /* interface */
    if(iaxp == NULLAX25)
        memcpy(n3hdr.source,Nr_iface->hwaddr,AXALEN);
  
    /* Make sure there is a connection to the neighbor */
    /* Make sure we use the netrom-interface call for this connection!
     * 11/20/91 WG7J/PA3DIS
     */
    if((axp = find_ax25(Nr_iface->hwaddr,np->call,iface)) == NULLAX25 ||
    (axp->state != LAPB_CONNECTED && axp->state != LAPB_RECOVERY)){
        /* Open a new connection or reinitialize old one */
        /* hwaddr has been advanced to point to neighbor + digis */
        axp = open_ax25(iface,Nr_iface->hwaddr,np->call,AX_ACTIVE,
        0,s_arcall,s_atcall, s_ascall,-1);
        if(axp == NULLAX25){
            free_p(bp);
            return;
        }
    }
  
    if(--n3hdr.ttl == 0){   /* the packet's time to live is over! */
        free_p(bp);
        return;
    }
    /* now format network header */
    if((pbp = htonnr3(&n3hdr)) == NULLBUF){
        free_p(bp);
        return;
    }
    append(&pbp,bp);        /* append data to header */
  
    /* put AX.25 PID on front */
    if((bp = pushdown(pbp,1)) == NULLBUF){
        free_p(pbp);
        return;
    }
    bp->data[0] = PID_NETROM;
  
    if((pbp = segmenter(bp,axp->paclen)) == NULLBUF){
        free_p(bp);
        return;
    }
    /*Update the last-used timestamp - WG7J */
    np->lastsent = secclock();
  
    /* Add one to the route usage count */
    bindp->usage++;
  
    /* Add one to the number of tries for this neighbour */
    np->tries++;
  
    send_ax25(axp,pbp,-1);  /* pass it off to ax25 code */
}
  
  
/* Perform a nodes broadcast on interface # ifno in the net/rom
 * interface table.
 */
/* This uses Nralias as the alias instead of Nriface.alias as before - WG7J */
void
nr_bcnodes(ifp)
struct iface *ifp;
{
    struct mbuf *hbp, *dbp, *savehdr;
    struct nrroute_tab *rp;
    struct nrnbr_tab *np;
    struct nr_bind * bp;
    struct nr3dest nrdest;
    int i, didsend = 0, numdest = 0;
  
    /* prepare the header */
    if((hbp = alloc_mbuf(NR3NODEHL)) == NULLBUF)
        return;
  
    hbp->cnt = NR3NODEHL;
  
    *hbp->data = NR3NODESIG;
    memcpy(hbp->data+1,Nralias,ALEN);
  
    /* Some people don't want to advertise any routes; they
     * just want to be a terminal node.  In that case we just
     * want to send our call and alias and be done with it.
     */
  
    if(ifp->nr_autofloor == 0) {
        /*changed to use the Netrom interface call on all broadcasts,
         *INDEPENDENT from the actual interface call!
         *11/20/91 WG7J/PA3DIS
         */
        (*ifp->output)(ifp, Ax25multi[NODESCALL], Nr_iface->hwaddr,
        PID_NETROM, hbp);   /* send it */
        return;
    }
  
    /* make a copy of the header in case we need to send more than */
    /* one packet */
    savehdr = copy_p(hbp,NR3NODEHL);
  
    /* now scan through the routing table, finding the best routes */
    /* and their neighbors.  create destination subpackets and append */
    /* them to the header */
    for(i = 0; i < NRNUMCHAINS; i++){
        for(rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rp->next){
            /* look for best, non-obsolescent route */
            if((bp = find_best(rp->routes,0)) == NULLNRBIND)
                continue;   /* no non-obsolescent routes found */
  
            if(bp->quality == 0)    /* this is a loopback route */
                continue;           /* we never broadcast these */
  
            if (bp->quality < ifp->nr_autofloor) /* below threshhold route */
                continue ;      /* so don't broadcast it */
  
            if (nr_aliasck(rp->alias))  /* corrupted alias entry? */
                continue ;      /* don't rebroadcast it */
                            /* safety measure! */
            np = bp->via;
            /* insert best neighbor */
            memcpy(nrdest.neighbor,np->call,AXALEN);
            /* insert destination from route table */
            memcpy(nrdest.dest,rp->call,AXALEN);
            /* insert alias from route table */
            strcpy(nrdest.alias,rp->alias);
            /* insert quality from binding */
            nrdest.quality = bp->quality;
            /* create a network format destination subpacket */
            if((dbp = htonnrdest(&nrdest)) == NULLBUF){
                free_p(hbp);    /* drop the whole idea ... */
                free_p(savehdr);
                return;
            }
            /* we now have a partially filled packet */
            didsend = 0;
            append(&hbp,dbp);   /* append to header and others */
            /* see if we have appended as many destinations
             * as we can fit into a single broadcast.  If we
             * have, go ahead and send them out.
             */
            if(++numdest == NRDESTPERPACK){ /* filled it up */
                /* indicate that we did broadcast */
                didsend = 1;
                /* reset the destination counter */
                numdest = 0;
                (*ifp->output)(ifp, Ax25multi[NODESCALL], Nr_iface->hwaddr,
                PID_NETROM,hbp);   /* send it */
                /* new header */
                hbp = copy_p(savehdr,NR3NODEHL);
            }
        }
    }
  
    /* If we have a partly filled packet left over, or we never */
    /* sent one at all, we broadcast: */
  
    if(!didsend || numdest > 0) {
        (*ifp->output)(ifp, Ax25multi[NODESCALL], Nr_iface->hwaddr,PID_NETROM, hbp);
    } else {
        if(numdest == 0)    /* free the header copies */
            free_p(hbp);
    }
  
    free_p(savehdr);
}
  
/* Perform a nodes broadcast poll on interface ifp
 * in the net/rom interface table. - WG7J
 */
void
nr_bcpoll(ifp)
struct iface *ifp;
{
    struct mbuf *hbp;
  
    /* prepare the header */
    if((hbp = alloc_mbuf(NR3NODEHL)) == NULLBUF)
        return;
    hbp->cnt = NR3NODEHL;
    *hbp->data = NR3POLLSIG;
    memcpy(hbp->data+1,Nralias,ALEN);
  
    /* send it out */
    (*ifp->output)(ifp, Ax25multi[1],Nr_iface->hwaddr,PID_NETROM, hbp);
    return;
}
  
extern struct timer Nodetimer,Obsotimer;
extern void donodetick __ARGS((void)),doobsotick __ARGS((void));
int nr_stop(struct iface *);
  
int nr_stop(struct iface *iftmp) {
  
    /*make sure that the netrom interface is properly detached
     *fixed 11/15/91, Johan. K. Reinalda, WG7J/PA3DIS
     */
    for(iftmp=Ifaces;iftmp;iftmp=iftmp->next)
        iftmp->flags &= ~IS_NR_IFACE; /* this 'resets' active interfaces */
    Nr_iface = NULLIF;
    return 0;
}
  
/* attach the net/rom interface.  no parms for now. */
int
nr_attach(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    if(Nr_iface != (struct iface *)0){
        tputs("netrom interface already attached\n");
        return -1;
    }
  
    Nr_iface = (struct iface *)callocw(1,sizeof(struct iface));
    Nr_iface->addr = Ip_addr;
  
    /* The strdup is needed to keep the detach routine happy (it'll
     * free the allocated memory)
     */
    Nr_iface->name = strdup("netrom");
    if(Nr_iface->hwaddr == NULLCHAR){
        Nr_iface->hwaddr = mallocw(AXALEN);
        memcpy(Nr_iface->hwaddr,Mycall,AXALEN);
    }
    Nr_iface->stop = nr_stop;
    Nr_iface->mtu = NR4MAXINFO;
    setencap(Nr_iface,"NETROM");
    Nr_iface->next = Ifaces;
    Ifaces = Nr_iface;
    memcpy(Nr4user,Mycall,AXALEN);
  
    /*Added some default settings for node-broadcast interval and
     *obsolescence timers.
     *11/21/91 WG7J/PA3DIS
     */
    stop_timer(&Nodetimer);
    Nodetimer.func = (void (*)__ARGS((void*)))donodetick;/* what to call on timeout */
    Nodetimer.arg = NULLCHAR;       /* dummy value */
    set_timer(&Nodetimer,1800000L);  /* 'standard netrom' 30 minutes*/
    start_timer(&Nodetimer);        /* and fire it up */
  
    stop_timer(&Obsotimer);
    Obsotimer.func = (void (*)__ARGS((void*)))doobsotick;/* what to call on timeout */
    Obsotimer.arg = NULLCHAR;       /* dummy value */
    set_timer(&Obsotimer,2100000L);  /* 35 minutes */
    start_timer(&Obsotimer);        /* and fire it up */
  
    return 0;
}
  
/* This function checks an ax.25 address and interface number against
 * the filter table and mode, and returns -1 if the address is to be accepted
 * verbatim, the quality if filtered in or 0 if it is to be filtered out.
 */
static int
accept_bc(addr,ifp)
char *addr;
struct iface *ifp;
{
    struct nrnf_tab *fp;
  
    if(Nr_nfmode == NRNF_NOFILTER)      /* no filtering in effect */
        return -1;
  
    fp = find_nrnf(addr,ifp);     /* look it up */
  
    if (fp != NULLNRNFTAB && Nr_nfmode == NRNF_ACCEPT)
        return fp->quality ;
  
    if (fp == NULLNRNFTAB && Nr_nfmode == NRNF_REJECT)
        return -1;
  
    if (Nr_promisc)
        return -1;      /* Come up and see me sometime..... */
    else
        return 0 ;      /* My mummy said not to listen to strangers! */
}
  
struct targ {
    struct iface *iface;
    struct timer *t;
};
  
void nr_poll_resp(struct targ *targ) {
  
    nr_bcnodes(targ->iface);
    free(targ->t);
    free(targ);
}
  
void nr_poll_respond(struct iface *iface) {
  
    struct timer *t;
    struct targ *targ;
  
    /* Set the random delay timer */
    t = mallocw(sizeof(struct timer));
    targ = mallocw(sizeof(struct targ));
  
    /* Set the arguments */
    targ->iface = iface;
    targ->t = t;
    t->func = (void (*)())nr_poll_resp;
    t->arg = targ;
  
    set_timer(t,random(30000) + 1);
    start_timer(t);
  
}
  
/* receive and process node broadcasts. */
void
nr_nodercv(iface,source,bp)
struct iface *iface;
char *source;
struct mbuf *bp;
{
    char bcalias[AXALEN];
    struct nr3dest ds;
    int qual,poll;
    unsigned char c;
    struct timer t;
  
    /* First, see if this is even a net/rom interface: */
    if(!(iface->flags & IS_NR_IFACE)){
        free_p(bp);
        return;
    }
  
    if ((qual = accept_bc(source,iface)) == 0) {    /* check against filter */
        free_p(bp) ;                /* and get quality */
        return ;
    }
  
    c = PULLCHAR(&bp);
    /* is this a route update poll from a neigbour ? - WG7J */
    poll = 0;
    if(c == NR3POLLSIG) {
        poll = 1;
        nr_poll_respond(iface);
    } else if(c != NR3NODESIG) {
        free_p(bp);
        return;
    }
  
    /* now try to get the alias */
    if(pullup(&bp,bcalias,ALEN) < ALEN){
        free_p(bp);
        return;
    }
  
    /* now check that the alias field is not corrupted - saftey measure! */
    if (nr_aliasck(bcalias)) {
        free_p(bp);
        return;
    }
  
    bcalias[ALEN] = '\0';       /* null terminate */
  
#ifdef NR_BC_RESPOND
    /* If we were polled, we've already sent the routes list - WG7J */
    if(!poll && (find_nrnbr(source,iface) == NULLNTAB)) /* a new node ! */
        nr_poll_respond(iface);
#endif
  
    /* enter the neighbor into our routing table */
    if(qual == -1)
        qual = iface->quality; /* use default quality */
  
    if(nr_routeadd(bcalias, source, iface, qual, source, 0, 0) == -1){
        free_p(bp);
        return;
    }
  
    /* we've digested the header; now digest the actual */
    /* routing information */
    while(ntohnrdest(&ds,&bp) != -1){
  
        /* ignore routes to me! */
        if(addreq(Nr_iface->hwaddr,ds.dest))
            continue;
  
        /* ignore routes with corrupted aliases - safety measure */
        if (nr_aliasck (ds.alias))
            continue;
  
        /* ignore loopback paths to ourselves */
        if(addreq(Nr_iface->hwaddr,ds.neighbor))
            continue;
        else
            ds.quality = ((ds.quality * qual + 128) / 256) & 0xff;
  
        /* ignore routes below the minimum quality threshhold */
        if(ds.quality < Nr_autofloor)
            continue;
  
        if(nr_routeadd(ds.alias,ds.dest,iface,ds.quality,source,0,0)
            == -1)
            break;
    }
    free_p(bp); /* This will free the mbuf if anything fails above */
}
  
  
/* The following are utilities for manipulating the routing table */
  
/* hash function for callsigns.  Look familiar? */
int16
nrhash(s)
char *s;
{
    register char x;
    register int i;
  
    x = 0;
    for(i = ALEN; i !=0; i--)
        x ^= *s++ & 0xfe;
    x ^= *s & SSID;
    return (int16)(uchar(x) % NRNUMCHAINS);
}
  
/* Find a neighbor table entry.  Neighbors are determined by
 * their callsign and the interface number.  This takes care
 * of the case where the same switch or hosts uses the same
 * callsign on two different channels.  This isn't done by
 * net/rom, but it might be done by stations running *our*
 * software.
 */
struct nrnbr_tab *
find_nrnbr(addr,ifp)
register char *addr;
struct iface *ifp;
{
    int16 hashval;
    register struct nrnbr_tab *np;
  
    /* Find appropriate hash chain */
    hashval = nrhash(addr);
  
    /* search hash chain */
    for(np = Nrnbr_tab[hashval]; np != NULLNTAB; np = np->next){
        /* convert first in  list to ax25 address format */
        if(addreq(np->call,addr) && np->iface == ifp){
            return np;
        }
    }
    return NULLNTAB;
}
  
/* Try to find the AX.25 address of a node with the given call or alias.
 * Return a pointer to the route if found, otherwize NULLNRRTAB.
 * alias should be a six character, blank-padded, upper-case string.
 * call should be a upper-case string.
 * 12-21-91, WG7J
 */
  
struct nrroute_tab *
find_nrboth(alias,call)
char *alias;
char *call;
{
    int i;
    register struct nrroute_tab *rp;
    char tmp[AXBUF];
  
    /* Since the route entries are hashed by ax.25 address, we'll */
    /* have to search all the chains */
  
    for(i = 0; i < NRNUMCHAINS; i++)
        for(rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rp->next)
            if( (strncmp(alias, rp->alias, 6) == 0) ||
                (strcmp(call,pax25(tmp,rp->call)) == 0) )
                return rp;
  
    /* If we get to here, we're out of luck */
    return NULLNRRTAB;
}
  
/* Find a route table entry */
struct nrroute_tab *
find_nrroute(addr)
register char *addr;
{
    int16 hashval;
    register struct nrroute_tab *rp;
  
    /* Find appropriate hash chain */
    hashval = nrhash(addr);
  
    /* search hash chain */
    for(rp = Nrroute_tab[hashval]; rp != NULLNRRTAB; rp = rp->next){
        if(addreq(rp->call,addr)){
            return rp;
        }
    }
    return NULLNRRTAB;
}
  
/* Try to find the AX.25 address of a node with the given alias.  Return */
/* a pointer to the AX.25 address if found, otherwise NULLCHAR.  The alias */
/* should be a six character, blank-padded, upper-case string. */
  
char *
find_nralias(alias)
char *alias;
{
    int i;
    register struct nrroute_tab *rp;
  
    /* Since the route entries are hashed by ax.25 address, we'll */
    /* have to search all the chains */
  
    for(i = 0; i < NRNUMCHAINS; i++)
        for(rp = Nrroute_tab[i]; rp != NULLNRRTAB; rp = rp->next)
            if(strncmp(alias, rp->alias, 6) == 0)
                return rp->call;
  
    /* If we get to here, we're out of luck */
  
    return NULLCHAR;
}
  
  
/* Find a binding in a list by its neighbor structure's address */
static struct nr_bind *
find_binding(list,neighbor)
struct nr_bind *list;
register struct nrnbr_tab *neighbor;
{
    register struct nr_bind *bp;
  
    for(bp = list; bp != NULLNRBIND; bp = bp->next)
        if(bp->via == neighbor)
            return bp;
  
    return NULLNRBIND;
}
  
/* Find the worst quality non-permanent binding in a list */
static
struct nr_bind *
find_worst(list)
struct nr_bind *list;
{
    register struct nr_bind *bp;
    struct nr_bind *worst = NULLNRBIND;
    unsigned minqual = 1000;    /* infinity */
  
    for(bp = list; bp != NULLNRBIND; bp = bp->next)
        if(!(bp->flags & NRB_PERMANENT) && bp->quality < minqual){
            worst = bp;
            minqual = bp->quality;
        }
  
    return worst;
}
  
/* Find the binding for a given neighbour in a list of routes */
struct nr_bind *
find_bind(list,np)
struct nr_bind *list;
struct nrnbr_tab *np;
{
    struct nr_bind *bp;
  
    for(bp = list; bp != NULLNRBIND; bp = bp->next)
        if(bp->via == np)
            break;
    return bp;
}
  
/* Find the best binding of any sort in a list.  If obso is 1,
 * include entries below the obsolescence threshhold in the
 * search (used when this is called for routing broadcasts).
 * If it is 0, routes below the threshhold are treated as
 * though they don't exist.
 */
struct nr_bind *
find_best(list,obso)
struct nr_bind *list;
unsigned obso;
{
    register struct nr_bind *bp;
    struct nr_bind *best = NULLNRBIND;
    int maxqual = -1;   /* negative infinity */
  
    for(bp = list; bp != NULLNRBIND; bp = bp->next)
        if((int)bp->quality > maxqual)
            if(obso || bp->obsocnt >= Obso_minbc){
                best = bp;
                maxqual = bp->quality;
            }
  
    return best;
}
  
/* Add a route to the net/rom routing table */
int
nr_routeadd(alias,dest,ifp,quality,neighbor,permanent,record)
char *alias;        /* net/rom node alias, blank-padded and */
            /* null-terminated */
char *dest;     /* destination node callsign */
struct iface *ifp;
unsigned quality;   /* route quality */
char *neighbor; /* neighbor node + 2 digis (max) in arp format */
unsigned permanent; /* 1 if route is permanent (hand-entered) */
unsigned record;    /* 1 if route is a "record route" */
{
    struct nrroute_tab *rp;
    struct nr_bind *bp;
    struct nrnbr_tab *np;
    int16 rhash, nhash;
  
    /* See if a routing table entry exists for this destination */
    if((rp = find_nrroute(dest)) == NULLNRRTAB){
        rp = (struct nrroute_tab *)callocw(1,sizeof(struct nrroute_tab));
        /* create a new route table entry */
        strncpy(rp->alias,alias,6);
        memcpy(rp->call,dest,AXALEN);
        rhash = nrhash(dest);
        rp->next = Nrroute_tab[rhash];
        if(rp->next != NULLNRRTAB)
            rp->next->prev = rp;
        Nrroute_tab[rhash] = rp;    /* link at head of hash chain */
    } else if(permanent || !strncmp(rp->alias,"##temp",6)) {
        strncpy(rp->alias,alias,6); /* update the alias */
    }
  
    /* See if an entry exists for this neighbor */
    if((np = find_nrnbr(neighbor,ifp)) == NULLNTAB){
        np = (struct nrnbr_tab *)callocw(1,sizeof(struct nrnbr_tab));
        /* create a new neighbor entry */
        memcpy(np->call,neighbor,AXALEN);
        np->iface = ifp;
        nhash = nrhash(neighbor);
        np->next = Nrnbr_tab[nhash];
        if(np->next != NULLNTAB)
            np->next->prev = np;
        Nrnbr_tab[nhash] = np;
    } else if(permanent){       /* force this path to the neighbor */
        memcpy(np->call,neighbor,AXALEN);
    }
  
    /* See if there is a binding between the dest and neighbor */
    if((bp = find_binding(rp->routes,np)) == NULLNRBIND){
        bp = (struct nr_bind *)callocw(1,sizeof(struct nr_bind));
        /* create a new binding and link it in */
        bp->via = np;   /* goes via this neighbor */
        bp->next = rp->routes;  /* link into binding chain */
        if(bp->next != NULLNRBIND)
            bp->next->prev = bp;
        rp->routes = bp;
        rp->num_routes++;   /* bump route count */
        np->refcnt++;       /* bump neighbor ref count */
        bp->quality = quality;
        bp->obsocnt = Obso_init;    /* use initial value */
        if(permanent)
            bp->flags |= NRB_PERMANENT;
        else if(record) /* notice permanent overrides record! */
            bp->flags |= NRB_RECORDED;
    } else {
        if(permanent){  /* permanent request trumps all */
            bp->quality = quality;
            bp->obsocnt = Obso_init;
            bp->flags |= NRB_PERMANENT;
            bp->flags &= ~NRB_RECORDED; /* perm is not recorded */
        } else if(!(bp->flags & NRB_PERMANENT)){    /* not permanent */
            if(record){ /* came from nr_route */
                if(bp->flags & NRB_RECORDED){ /* no mod non-rec bindings */
                    bp->quality = quality;
                    bp->obsocnt = Obso_init; /* freshen recorded routes */
                }
            } else {        /* came from a routing broadcast */
                bp->quality = quality;
                bp->obsocnt = Obso_init;
                bp->flags &= ~NRB_RECORDED; /* no longer a recorded route */
            }
        }
    }
  
    /* Now, check to see if we have too many bindings, and drop */
    /* the worst if we do */
    if(rp->num_routes > Nr_maxroutes){
        /* since find_worst never returns permanent entries, the */
        /* limitation on number of routes is circumvented for    */
        /* permanent routes */
        if((bp = find_worst(rp->routes)) != NULLNRBIND){
            nr_routedrop(dest,bp->via->call,bp->via->iface);
        }
    }
  
    return 0;
}
  
/* Reset the netrom links to this neighbour, since the
 * route to it just expired - WG7J
 */
void
nrresetlinks(struct nrroute_tab *rp) {
    int i ;
    struct nr4cb *cb ;
  
    for(i = 0 ; i < NR4MAXCIRC ; i++)
        if((cb = Nr4circuits[i].ccb) != NULLNR4CB)
            if(!memcmp(cb->remote.node,rp->call,AXALEN))
                reset_nr4(cb);
}
  
/* Drop a route to dest via neighbor */
int
nr_routedrop(dest,neighbor,ifp)
char *dest, *neighbor;
struct iface *ifp;
{
    register struct nrroute_tab *rp;
    register struct nrnbr_tab *np;
    register struct nr_bind *bp;
  
    if((rp = find_nrroute(dest)) == NULLNRRTAB)
        return -1;
  
    if((np = find_nrnbr(neighbor,ifp)) == NULLNTAB)
        return -1;
  
    if((bp = find_binding(rp->routes,np)) == NULLNRBIND)
        return -1;
  
    /* drop the binding first */
    if(bp->next != NULLNRBIND)
        bp->next->prev = bp->prev;
    if(bp->prev != NULLNRBIND)
        bp->prev->next = bp->next;
    else
        rp->routes = bp->next;
  
    free((char *)bp);
    rp->num_routes--;       /* decrement the number of bindings */
    np->refcnt--;           /* and the number of neighbor references */
  
    /* now see if we should drop the route table entry */
    if(rp->num_routes == 0){
        if(rp->next != NULLNRRTAB)
            rp->next->prev = rp->prev;
        if(rp->prev != NULLNRRTAB)
            rp->prev->next = rp->next;
        else
            Nrroute_tab[nrhash(dest)] = rp->next;
        /* No more routes left !
         * We should close/reset any netrom connections
         * still idling for this route ! - WG7J
         */
        nrresetlinks(rp);
  
        free((char *)rp);
    }
  
    /* and check to see if this neighbor can be dropped */
    if(np->refcnt == 0){
        if(np->next != NULLNTAB)
            np->next->prev = np->prev;
        if(np->prev != NULLNTAB)
            np->prev->next = np->next;
        else
            Nrnbr_tab[nrhash(neighbor)] = np->next;
  
        free((char *)np);
    }
  
    return 0;
}
  
/* Find an entry in the filter table */
static struct nrnf_tab *
find_nrnf(addr,ifp)
register char *addr;
struct iface *ifp;
{
    int16 hashval;
    register struct nrnf_tab *fp;
  
    /* Find appropriate hash chain */
    hashval = nrhash(addr);
  
    /* search hash chain */
    for(fp = Nrnf_tab[hashval]; fp != NULLNRNFTAB; fp = fp->next){
        if(addreq(fp->neighbor,addr) && (fp->iface == ifp)){
            return fp;
        }
    }
  
    return NULLNRNFTAB;
}
  
/* Add an entry to the filter table.  Return 0 on success,
 * -1 on failure
 */
int
nr_nfadd(addr,ifp,qual)
char *addr;
struct iface *ifp;
unsigned qual;
{
    struct nrnf_tab *fp;
    int16 hashval;
  
    if(find_nrnf(addr,ifp) != NULLNRNFTAB)
        return 0;   /* already there; it's a no-op */
  
    fp = (struct nrnf_tab *)callocw(1,sizeof(struct nrnf_tab));
  
    hashval = nrhash(addr);
    memcpy(fp->neighbor,addr,AXALEN);
    fp->iface = ifp;
    fp->next = Nrnf_tab[hashval];
    fp->quality = qual;
    if(fp->next != NULLNRNFTAB)
        fp->next->prev = fp;
    Nrnf_tab[hashval] = fp;
  
    return 0;
}
  
/* Drop a neighbor from the filter table.  Returns 0 on success, -1
 * on failure.
 */
int
nr_nfdrop(addr,ifp)
char *addr;
struct iface *ifp;
{
    struct nrnf_tab *fp;
  
    if((fp = find_nrnf(addr,ifp)) == NULLNRNFTAB)
        return -1;  /* not in the table */
  
    if(fp->next != NULLNRNFTAB)
        fp->next->prev = fp->prev;
    if(fp->prev != NULLNRNFTAB)
        fp->prev->next = fp->next;
    else
        Nrnf_tab[nrhash(addr)] = fp->next;
  
    free((char *)fp);
  
    return 0;
}
  
/*
 * Validate the alias field is good quality ascii to prevent network corruption
 */
  
static int
nr_aliasck(alias)
char *alias;
{
    int x = ALEN;
    int c;
    while (x--) {
        c = *alias++;
        if (!isprint( (int) c) )
            return 1;
    }
    return 0;
}
  
/* called from lapb whenever a link failure implies that a particular ax25
 * path may not be able to carry netrom traffic too well. Experimental!!!!
 */
void nr_derate(axp)
struct ax25_cb *axp;
{
    register struct nrnbr_tab *np ;
    register struct nrroute_tab *rp;
    register struct nr_bind *bp;
    struct mbuf *buf;
    int i;
    int nr_traffic = 0; /* assume no netrom traffic on connection */
  
    if(!Nr_derate)
        return;     /* derating function is disabled */
  
    /* is this an active netrom interface ? */
    if (!(axp->iface->flags & IS_NR_IFACE))
        return ;
  
    if (axp == NULLAX25)
        return;         /* abandon ship! */
  
    /* If it is valid for netrom traffic, lets see if there is */
    /* really netrom traffic on the connection to be derated.  */
    for (buf = axp->txq; buf != NULLBUF; buf = buf->anext)
        if ((buf->data[0] & 0xff) == PID_NETROM)
            nr_traffic = 1;     /* aha - netrom traffic! */
  
    if (!nr_traffic)
        return;     /* no sign of being used by netrom just now */
  
    /* we now have the appropriate interface entry */
    for (i = 0 ; i < NRNUMCHAINS ; i++) {
        for (rp = Nrroute_tab[i] ; rp != NULLNRRTAB ; rp = rp->next) {
            for (bp = rp->routes ; bp != NULLNRBIND ; bp = bp->next) {
                np = bp->via;
                if(bp->quality >= 1 && np->iface == axp->iface &&
                    !(bp->flags & NRB_PERMANENT) &&
                    !memcmp(np->call,axp->remote,ALEN) &&
                (np->call[6] & SSID) == (axp->remote[6] & SSID)) {
                    bp->quality = ((bp->quality * 2) / 3);
                }
            }
        }
    }
}
  
#endif /* NETROM */
  
