/* output.c -- RIP packet output */

/*
 *  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: output.c,v 1.4 1995/02/17 17:43:07 buhr Exp $";
#endif /* not lint */

#include "defs.h"
#include "error.h"
#include "input.h"
#include "output.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>


/*
 *	Send a routing information packet to a particular destination
 */

void out_rip( struct rip *rip, int length, struct tb_address *dest,
	     u_char ripcmd )
{
   int rc, i, c, maxentries, totentries, entries;

   if( (dest->tba_flags & TBAF_VALID)==0 )
      return;  /* we can't handle invalid addresses */
   /* we are silent, so ignore attempts to broadcast responses */
   if( (dest->tba_flags & TBAF_HOST)==0 && ripcmd==RIPCMD_RESPONSE )
      return;

   rip->rip_vers = RIPVERSION;
   rip->rip_cmd = ripcmd;
   rip->rip_res1[0] = rip->rip_res1[1] = 0;

   for( i=0; i < in_countentries( length ); i++ ) {
      for( c=0; c<14; c++ ) {
	 if(c==2) c=6; /* skip IP address */
	 rip->rip_nets[i].rip_dst.sa_data[c]=0;
      }
   }

   maxentries = ( OUT_MAXPACKETLEN - sizeof( rip->rip_cmd ) 
		 - sizeof( rip->rip_vers ) - sizeof( rip->rip_res1 ) ) 
      / sizeof( rip->rip_nets[0] );  /* maximum entries per packet */
   totentries = in_countentries( length );  /* entries in this packet */

   while( totentries > 0 ) {
      entries = totentries > maxentries ? maxentries : totentries;
      length = sizeof( rip->rip_cmd ) + sizeof( rip->rip_vers ) 
	 + sizeof( rip->rip_res1 ) + entries * sizeof( rip->rip_nets[0] );
      note3( ERCOUT_SENDING, rip, length, dest );
      rc = sendto( g_rs, (void *) rip, length, 0,
		  &dest->tba_addr, sizeof(dest->tba_addr) );
      if( rc < 0 ) {
	 warn1( ERCOUT_SENDTO, errno );
	 break;
      }
      totentries -= entries;
      if( totentries > 0 ) {
	 memmove( &rip->rip_nets[0], &rip->rip_nets[entries],
		 totentries * sizeof( rip->rip_nets[0] ) );
      }
   }
}


/*
 *	Trigger an update for a route
 */

void out_update( int route )
{
   /* we are silent, so we don't carry out the updates */
   tb_route[route].tbrt_flags &= ~TBRTF_CHANGED;
   note0( ERCOUT_UPDATE );
}


/*
 *	Send a copy of our routing table
 */

void out_table( struct tb_address *dest )
{
   int i, j;
   short metric;
   struct tb_route *route;
   struct netinfo *ni;

   note1( ERCOUT_SENDTABLE, dest );
   ni = ((struct rip *) page)->rip_nets;

   for( i=0; i<TB_ROUTE_SIZE; i++ ) {
      route = &tb_route[i];
      if( route->tbrt_flags & TBRTF_USED
	 && (route->tbrt_flags & TBRTF_KILLED)==0
	 && (route->tbrt_flags & TBRTF_TENTATIVE)==0 ) {

	 /* we obviously aren't equipped to act as default */
	 if( route->tbrt_flags & TBRTF_DEFAULT )
	    continue;
	 /* don't mention a subnet on another network */
	 if( route->tbrt_flags & TBRTF_SUBNET
	    && !tb_samehost( &tb_route[route->tbrt_supernet].tbrt_dst,
			    &dest->tba_netsa ) )
	    continue;
	 if( route->tbrt_flags & TBRTF_SUPERNET ) {
	    /* don't mention a supernet record on that network */
	    if( tb_samehost( &route->tbrt_dst, &dest->tba_netsa ) )
	       continue;
	    /* otherwise, find smallest metric of all our subnets */
	    metric = TBM_INFINITY + 1;
	    for( j=0; j<TB_ROUTE_SIZE; j++ ) {
	       if( tb_route[j].tbrt_flags & TBRTF_USED
		  && (tb_route[j].tbrt_flags & TBRTF_KILLED)==0
		  && (tb_route[j].tbrt_flags & TBRTF_TENTATIVE)==0
		  && tb_route[j].tbrt_flags & TBRTF_SUBNET
		  && tb_route[j].tbrt_supernet == i )
		  if( tb_route[j].tbrt_metric < metric ) {
		     metric = tb_route[j].tbrt_metric;
		  }
	    }
	    if( metric == TBM_INFINITY + 1 )
	       continue;  /* no more unkilled routes to that net */
	 } else
	    metric = route->tbrt_metric;

	 ni->rip_dst = route->tbrt_dst;
	 ni->rip_metric = metric;
	 if((void *) ++ni >= (void *) &page[PAGESIZE] ) {
	    warn0( ERCOUT_FULLOUT );
	    break;
	 }
      }
   }
   if( (void *) ni > (void *) ((struct rip *) page)->rip_nets ) {
      out_rip( (struct rip *) page, (void *) ni - (void *) page, 
	      dest, RIPCMD_RESPONSE );
   }
}


/*
 *	Request complete routing table from each interface
 */

void out_gettable( struct rip *rip, int maxlength )
{
   int ifi, length;
   struct tb_address dest;

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

      /* find appropriate address on which to transmit */
      if( tb_iface[ifi].tbif_flags & TBIFF_BROADCAST )
	 dest.tba_addr = tb_iface[ifi].tbif_broadaddr;
      else if( tb_iface[ifi].tbif_flags & TBIFF_POINTOPOINT )
	 dest.tba_addr = tb_iface[ifi].tbif_dstaddr;
      else
	 goto bad_iface;
      /* add the appropriate port */
      ((struct sockaddr_in *) &dest.tba_addr)->sin_port 
	 = htons(g_routedport);

      tb_chkaddr( &dest );
      if( (dest.tba_flags & TBAF_VALID) == 0 )
	 goto bad_iface;

      note2( ERCOUT_GETTABLE, &tb_iface[ifi], &dest );

      length = sizeof( rip->rip_cmd) + sizeof( rip->rip_vers )
	 + sizeof( rip->rip_res1 ) + sizeof( rip->rip_nets[0] );
      if( length > maxlength ) {
	 warn0( ERCOUT_FULLOUT );
	 continue;
      }
      rip->rip_nets[0].rip_dst.sa_family = 0;
      memset( rip->rip_nets[0].rip_dst.sa_data,
	     0, sizeof( rip->rip_nets[0].rip_dst.sa_data ) );
      rip->rip_nets[0].rip_metric = TBM_INFINITY;

      out_rip( rip, length, &dest, RIPCMD_REQUEST );
      continue;

   bad_iface:
      warn1( ERCOUT_BADIFACE, &tb_iface[ifi] );
   }
}
