/* table.c -- table manipulation and IP address parsing */

/*
 *  srouted -- silent routing daemon
 *  Copyright (C) 1995 Kevin Buhr
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef lint
static char rcsid[] = "$Id: table.c,v 1.3 1995/02/17 17:43:00 buhr Exp $";
#endif /* not lint */

#include "defs.h"
#include "kernel.h"
#include "output.h"
#include "timer.h"
#include "table.h"

#include <netinet/in.h>
#include <time.h>
#include <string.h>

static int tb_route_free=0;
static int tb_iface_free=0;

static void tb_ifdest( struct tb_iface *iface, struct sockaddr *dest );
static int tb_ifroute( int ifi );


/*
 *	Get an empty interface table entry
 */

int tb_newiface(void)
{
   int count=0;
   struct tb_iface *iface;

   while( tb_iface[tb_iface_free].tbif_flags & TBIFF_USED ) {
      tb_iface_free++;
      if( ++count > TB_IFACE_SIZE )
	 return -1;
   }
   iface = &tb_iface[tb_iface_free];
   iface->tbif_flags=0;

   return tb_iface_free;
}


/*
 *	Get an empty route table entry
 */

int tb_newroute(void)
{
   int count=0;
   struct tb_route *route;

   while( tb_route[tb_route_free].tbrt_flags & TBRTF_USED ) {
      tb_route_free++;
      if( ++count > TB_ROUTE_SIZE )
	 return -1;
   }
   route = &tb_route[tb_route_free];
   memset((void *) &route->tbrt_dst,0,sizeof(route->tbrt_dst));
   memset((void *) &route->tbrt_mask,0,sizeof(route->tbrt_mask));
   memset((void *) &route->tbrt_gateway,0,sizeof(route->tbrt_gateway));
   route->tbrt_iface=-1;
   route->tbrt_supernet=-1;
   route->tbrt_metric=0;
   route->tbrt_flags=0;
   route->tbrt_timer=0;

   return tb_route_free;
}


/*
 *	Convert a sockaddr structure into a host-order IP address
 */
unsigned long int tb_satoip( struct sockaddr *sa )
{
   return ntohl( ((struct sockaddr_in *) sa)->sin_addr.s_addr );
}


/*
 *	Convert a host-order IP address to a sockaddr structure
 */
void tb_iptosa( unsigned long int a, struct sockaddr *sa )
{
   memset((void *) sa, 0, sizeof(struct sockaddr));
   sa->sa_family = AF_INET;
   ((struct sockaddr_in *) sa)->sin_addr.s_addr
      = htonl(a);
}


/*
 *	Get route table entry for given destination address
 */

int tb_findroute(struct sockaddr *dst)
{
   int i;

   for(i=0; i<TB_ROUTE_SIZE; i++) {
      if( (tb_route[i].tbrt_flags & TBRTF_USED) == 0 )
	 continue;
      if( tb_samehost( &tb_route[i].tbrt_dst, dst ) )
	 return(i);
   }

   return -1;
}


/*
 *	Check if two sockaddrs refer to same network/host entity
 *	(currently returns "false" for address families other than INET)
 */
int tb_samehost( struct sockaddr *a1, struct sockaddr *a2 )
{
   if( a1->sa_family == a2->sa_family
      && a1->sa_family == AF_INET 
      && tb_satoip(a1) == tb_satoip(a2) )
      return 1;
   else
      return 0;
}


/*
 *	Get destination address of an interface
 */

static void tb_ifdest( struct tb_iface *iface, struct sockaddr *dest )
{
   char *p, *q;
   int i;

   if( iface->tbif_flags & TBIFF_POINTOPOINT ) {
      *dest = iface->tbif_dstaddr;
   } else {
      *dest = iface->tbif_myaddr;
      p = (char *) &dest->sa_data;
      q = (char *) &iface->tbif_netmask.sa_data;
      for( i = 0; i < sizeof( iface->tbif_myaddr.sa_data ); i++ )
	 *p++ &= *q++; 
   }
}


/*
 *	Create routing supernet entry, if necessary
 */

void tb_makesuper( int route, struct tb_address *dest )
{
   int super;

   if( dest->tba_flags & TBAF_SUBNET ) {
      super = tb_findroute( &dest->tba_netsa );
      if( super == -1 ) {
	 super = tb_newroute();
	 if(super==-1) {
	    warn0(ERCTB_RTFULL);
	    return;
	 }
	 tb_route[super].tbrt_dst = dest->tba_netsa;
	 tb_route[super].tbrt_mask = dest->tba_supmasksa;
	 tb_route[super].tbrt_metric = TBM_INFINITY;
	 tb_route[super].tbrt_flags = TBRTF_USED | TBRTF_KEEP
	    | TBRTF_KILLED | TBRTF_SUPERNET;
	 note1( ERCTB_ADDEDSUPER, super );
      } else {
	 if( (tb_route[super].tbrt_flags & TBRTF_SUPERNET)==0 ) {
	    warn1( ERCTB_BADSUPER, &dest );
	 }
      }
      tb_route[route].tbrt_supernet = super;
      tb_route[route].tbrt_flags |= TBRTF_SUBNET;
   }

}
/*
 *	Construct default route table entry for an interface
 */

static int tb_ifroute(int ifi)
{
   int rti;
   struct tb_route *route;
   struct tb_iface *iface;
   struct tb_address dest;  /* route destination address */

   iface = &tb_iface[ifi];
   if( iface->tbif_flags & TBIFF_LOOPBACK ) {
      return 0;
   }
   tb_ifdest( iface, &dest.tba_addr );
   tb_chkaddr(&dest);
   if( (dest.tba_flags & TBAF_VALID) == 0 ) {
      weakwarn2(ERCTB_INVDEST,iface,&dest);
      return ERCTB_INVDEST;  /* bad destination */
   }
   rti = tb_findroute(&dest.tba_addr);
   if( rti != -1 ) {
      warn2(ERCTB_RTDUP,iface,&tb_route[rti]);
      return ERCTB_RTDUP;
   }
   rti = tb_newroute();
   if(rti==-1) {
      warn0(ERCTB_RTFULL);
      return ERCTB_RTFULL;
   }
   route=&tb_route[rti];
   route->tbrt_dst=dest.tba_addr;
   tb_iptosa( dest.tba_netmask, &route->tbrt_mask );
   tb_iptosa( 0, &route->tbrt_gateway );  /* no gateway---direct route */
   route->tbrt_iface=ifi;
   route->tbrt_metric=tb_iface[ifi].tbif_metric;
   /* may want to add TBRTF_TENTATIVE based on command line option */
   route->tbrt_flags |= TBRTF_USED|TBRTF_DIRECT|TBRTF_KEEP;
   if( dest.tba_flags & TBAF_HOST )
      route->tbrt_flags |= TBRTF_HOST;
   note2( ERCTB_IFDEFAULT, &tb_iface[ifi], route );
   tb_makesuper( rti, &dest );

   return 0;
}


/*
 *	Construct initial route table entries for interfaces
 */

void tb_initroute(void)
{
   int ifi;

   for( ifi=0; ifi<TB_IFACE_SIZE; ifi++ ) {
      if( tb_iface[ifi].tbif_flags & TBIFF_USED
	 && tb_iface[ifi].tbif_flags & TBIFF_UP ) {
	 tb_ifroute(ifi);
      }
   }
}


/*
 *	Get our metric for this destination (infinity if no entry)
 */

short tb_rtmetric(struct sockaddr dst)
{
   int rti;

   rti = tb_findroute(&dst);
   if(rti==-1)
      return TBM_INFINITY;
   return tb_route[rti].tbrt_metric;
}


/*
 *	Check/analyze an IP address
 */

void tb_chkaddr(struct tb_address *a)
{
   int ifi;
   unsigned long ah, myaddr, netmask;
   int ifscore=0;
   int ifguess=-1;

   a->tba_flags=0;
   a->tba_iface=-1;
   a->tba_af = a->tba_addr.sa_family;

   /* check it's an IP address */
   if(a->tba_af != AF_INET) {
      return;
   }
   a->tba_port = ntohs( ((struct sockaddr_in *) &a->tba_addr)->sin_port );
   ah = ntohl( ((struct sockaddr_in *) &a->tba_addr)->sin_addr.s_addr );
   if(ah==0x00000000) {
      a->tba_flags|=TBAF_DEFAULT|TBAF_VALID;
      a->tba_network=0;
      a->tba_host=0;
      a->tba_hostsa=a->tba_addr;
      a->tba_subnet=0;
      a->tba_subnetsa=a->tba_addr;
      a->tba_netmask=0;
      return;
   }
   if(IN_CLASSA(ah)) {
      a->tba_flags|=TBAF_CLASSA;
      a->tba_netmask=IN_CLASSA_NET;
   } else if(IN_CLASSB(ah)) {
      a->tba_flags|=TBAF_CLASSB;
      a->tba_netmask=IN_CLASSB_NET;
   } else if(IN_CLASSC(ah)) {
      a->tba_flags|=TBAF_CLASSC;
      a->tba_netmask=IN_CLASSC_NET;
   } else {
      return;  /* invalid address */
   }
   a->tba_flags|=TBAF_VALID;
   a->tba_network = ah & a->tba_netmask;
   tb_iptosa( a->tba_network, &a->tba_netsa );
   tb_iptosa( a->tba_netmask, &a->tba_supmasksa );
   /* find correct subnet */
   for(ifi=0; ifi<TB_IFACE_SIZE; ifi++) {
      if( (tb_iface[ifi].tbif_flags & TBIFF_USED)==0 ) {
	 continue;
      }
      if( tb_iface[ifi].tbif_myaddr.sa_family == AF_INET ) {
	 myaddr = tb_satoip( &tb_iface[ifi].tbif_myaddr );
	 /* is our network subnetted? */
	 netmask = tb_satoip( &tb_iface[ifi].tbif_netmask );
	 if( (myaddr & a->tba_netmask) == a->tba_network
	    && a->tba_netmask != netmask ) {
	    a->tba_netmask = netmask;
	    a->tba_flags |= TBAF_SUBNET;
	    break;
	 }
      }
   }
   a->tba_subnet = ah & a->tba_netmask;
   tb_iptosa( a->tba_subnet, &a->tba_subnetsa );
   a->tba_host = ah & ~a->tba_netmask;
   tb_iptosa( a->tba_subnet | a->tba_host, &a->tba_hostsa );
   if(a->tba_host == ~a->tba_netmask) {
      a->tba_flags|=TBAF_BROADCAST;
   } else if(a->tba_host) {
      a->tba_flags|=TBAF_HOST;
   }

   /*
    * The following block finds the interface most likely associated
    * with the address giving preference to (in descending order):
    *		- match to own address with interface to a network
    *		- match to own address with point-to-point interface
    *		- match to point-to-point destination address
    * 		- match to destination network
    */
   ifscore=0;
   for(ifi=0; ifi<TB_IFACE_SIZE; ifi++) {
      if( (tb_iface[ifi].tbif_flags & TBIFF_USED)==0 ) {
	 continue;
      }
      if( tb_iface[ifi].tbif_myaddr.sa_family == AF_INET ) {
	 myaddr = tb_satoip( &tb_iface[ifi].tbif_myaddr );
	 /* if on same subnet, these interface scores apply... */
	 if( (myaddr & a->tba_netmask) == a->tba_subnet ) {
	    /* if it's our own address... */
	    if( (a->tba_flags & TBAF_HOST) 
	       && (myaddr & ~a->tba_netmask) == a->tba_host ) {
	       if(tb_iface[ifi].tbif_flags & TBIFF_POINTOPOINT) {
		  /* score 3 for own address on point-to-point */
		  if(ifscore<3) {
		     ifscore=3;
		     ifguess=ifi;
		  }
	       } else {
		  /* score 4 for own address on network interface */
		  if(ifscore<4) {
		     ifscore=4;
		     ifguess=ifi;
		  }
	       }
	    }
	    /* score 1 for address on a network interface */
	    else {
	       if(ifscore<1) {
		  ifscore=1;
		  ifguess=ifi;
	       }
	    }
	 } else {
	    /* check point-to-point destination */
	    if( (a->tba_flags & TBAF_HOST)
	       && (tb_iface[ifi].tbif_flags & TBIFF_POINTOPOINT)
	       && tb_satoip(&tb_iface[ifi].tbif_dstaddr)
	                    == tb_satoip(&a->tba_addr) ) {
	       /* score 2 for point-to-point destination */
	       if(ifscore<2) {
		  ifscore=2;
		  ifguess=ifi;
	       }
	    }
	 }
      }
   }

   if(ifscore>0)
      a->tba_iface=ifguess;

}


/*
 *	Add a route to the table
 */

void tb_addroute( int route, struct tb_address *dest, 
		 struct tb_address *gw, short cost )
{
   tb_route[route].tbrt_flags |= TBRTF_CHANGED | TBRTF_USED;
   tb_route[route].tbrt_flags &= 
      ~(TBRTF_TENTATIVE|TBRTF_KILLED|TBRTF_DELETED);
   tb_route[route].tbrt_dst = dest->tba_hostsa;
   if( dest->tba_flags & TBAF_HOST )
      tb_route[route].tbrt_flags |= TBRTF_HOST;
   tb_makesuper( route, dest );
   tb_iptosa( dest->tba_netmask, &tb_route[route].tbrt_mask );
   tb_route[route].tbrt_gateway = gw->tba_hostsa;
   tb_route[route].tbrt_metric = cost;
   /* default routes should be hard to lose */
   if( dest->tba_flags & TBAF_DEFAULT )
      tb_route[route].tbrt_flags |= TBRTF_KEEP | TBRTF_DEFAULT;
   note1( ERCTB_ADDRT, route );
   tm_settimeout( route );
   kr_addroute( route );
   out_update( route );
}


/*
 *	Delete a route from the table
 */

void tb_delroute( int route, struct tb_address *gw )
{
   tb_route[route].tbrt_flags &= ~(TBRTF_TENTATIVE|TBRTF_KILLED);
   tb_route[route].tbrt_flags |= (TBRTF_CHANGED|TBRTF_DELETED);
   if(gw) tb_route[route].tbrt_gateway = gw->tba_hostsa;
   tb_route[route].tbrt_metric = TBM_INFINITY;
   note1( ERCTB_DELRT, route );
   tm_setgarbcoll( route );
   if( (tb_route[route].tbrt_flags & TBRTF_KEEP)==0 )
      kr_delroute( route );
   out_update( route );
}


/*
 *	Kill a route from the table
 */

void tb_killroute( int route )
{
   if( tb_route[route].tbrt_flags & TBRTF_KEEP ) {
      note1( ERCTB_KEPTRT, route );
      tb_route[route].tbrt_flags &= 
	 ~(TBRTF_CHANGED|TBRTF_DELETED);
      tb_route[route].tbrt_flags |= TBRTF_KILLED;
      tm_killtimer( route );
   } else {
      note1( ERCTB_KILLRT, route );
      tb_route[route].tbrt_flags = 0;
   }
}
