/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: hello.c,v 1.4.2.1 92/06/24 13:39:03 marker Exp $"
/*
 *   CENTER FOR THEORY AND SIMULATION IN SCIENCE AND ENGINEERING
 *			CORNELL UNIVERSITY
 *
 *      Portions of this software may fall under the following
 *      copyrights: 
 *
 *	Copyright (c) 1983 Regents of the University of California.
 *	All rights reserved.  The Berkeley software License Agreement
 *	specifies the terms and conditions for redistribution.
 *
 *  GATED - based on Kirton's EGP, UC Berkeley's routing daemon (routed),
 *	    and DCN's HELLO routing Protocol.
 */

#ifndef	lint
static char *rcsid = "$Header: hello.c,v 1.4.2.1 92/06/24 13:39:03 marker Exp $";
#endif	not lint

/*
 *	Hello output routines were taken from Mike Petry (petry@trantor.umd.edu)
 *	Also, hello input routines were written by Bill Nesheim, Cornell
 *	CS Dept,  Currently at nesheim@think.com
 */

#include "include.h"

helloin(hellobuf)
char *hellobuf;
{
  register int dsize, index, i;
  struct hellohdr *hi;
  struct m_hdr *mh;
  struct type1pair *t1;
  struct ip *ip;
  struct sockaddr_in gateway;
  struct rt_entry *rt, *exrt;
  struct interface *ifp, *ifpc;
  int change = FALSE;
  int fromproto;
  int latched;			/* Fuzzball has this route in hold down */
  u_short hello_cksum(), cksum;
    
  ip = (struct ip *)hellobuf;
    
  /* drop the ip header	*/
  dsize = ip->ip_len  /* - (ip->ip_hl<<2)  data length here, NOT total */;
  index = ip->ip_hl << 2;
  hi = (struct hellohdr *) &hellobuf[index];
  index +=  /* sizeof (struct hellohdr) */ 10;

  TRACE_HELLOPKT(HELLO RECV, ip->ip_src, ip->ip_dst, (char *)hi, dsize, -1);

  /* ignore if not from a trusted HELLOer */
  if (trustedhelloerlist != (struct advlist *)NULL) {
    register struct advlist *ad;      /* list of trusted HELLOers */
    register int OK = 0;

    for(ad = trustedhelloerlist; ad; ad = ad->next)
      if (ip->ip_src.s_addr == ad->destnet.s_addr) {
        OK++;
        break;
      }
    if (OK == 0)
      return;
  }

  /* Do we share a net with the sender? */
  bzero((char *)&gateway, sizeof (gateway));
  gateway.sin_family = AF_INET;
  gateway.sin_addr = ip->ip_src;
  if ((ifp = if_withnet(&gateway)) == NULL) {
    TRACE_EXT("helloin: gw %s no shared net?\n", inet_ntoa(ip->ip_src));
    return;
  }
  if (ifp->int_flags & IFF_NOHELLOIN)
	return;

  /*
   * Are we talking to ourselves?
   *
   * if_ifwithaddr() handles PTP links also.  If packet came
   * from other end of a PTP, let it fall through for further
   * processing.  We shouldn't ever hear our own RIPs on a PTP link.
   */
  if (ifpc = if_ifwithaddr((struct sockaddr *)&gateway)) {
    rt_ifupdate(ifpc);
    if ((ifpc->int_flags & IFF_POINTOPOINT) == 0) {
      return;
    }
  }
  /*
   * update the interface timer on interface the packet came in on.
   */
  rt_ifupdate(ifp);

  fromproto = if_updateactivegw(ifp, gateway.sin_addr.s_addr, RTPROTO_HELLO);
  /* check the hello checksum */

  cksum = hello_cksum((u_short *) hi, dsize);
  if (cksum == hi->h_cksum) {
    
    /* message is made up of one or more sub messages */
    dsize -= /* sizeof (struct hellohdr) */ 10; 
    while (dsize > 0) {
      mh = (struct m_hdr *) &hellobuf[index];
      index += sizeof (struct m_hdr);
      dsize -= sizeof (struct m_hdr);
      switch (mh->m_type) {
        case 0:
          index += mh->m_count * sizeof (struct type0pair);
          dsize -= mh->m_count * sizeof (struct type0pair);
          /* not interested in type 0 messages */
          break;
	case 1:
          t1 = (struct type1pair *)&hellobuf[index];
          for (i = 0; i < mh->m_count; i++, t1++) {
            int delay = ntohs(t1->d1_delay);
            struct sockaddr_in dst;

            bzero((char *)&dst, sizeof (dst));
            dst.sin_family = AF_INET;
            /* dogone alignment problems! */
            bcopy((char *)&t1->d1_dst, (char *)&dst.sin_addr,
                    sizeof (struct in_addr));

            index += sizeof (struct type1pair);
            dsize -= sizeof (struct type1pair);

	    /*
	     * Fuzzballs set the top bit of the delay field when the
	     * delay jumps too high too fast, and latches the route.
	     * They unlatch the route when the metric by that route
	     * goes down some, or when the hold-down-latch period
	     * expires (in which case they start saying infinity)
	     * 
	     * The current (Dec 1987) idea is to insulate the RIP
	     * world from a Fuzzball hold down and to avoid making
	     * any routing changes during a hold down.  
	     * 
	     * If the route in hold down is not being used, the
	     * new route is ignored, even if it is more attractive.
	     * 
	     * If we are using the route, we will not update the
	     * routing table with the new metric, but continue to
	     * use the last non-hold down metric.  We will also not
	     * update the hello window.  We will however, reset the
	     * timer so that the route is not expired during hold down.
	     */
	    if ((u_short)delay & 0x8000) {	/* latched */
		delay = (int)((u_short)delay & 0x7fff);
		latched = TRUE;
	    } else {
		latched = FALSE;
	    }
	/*
	 *	Add the interface metric converted to a HELLO delay.
	 */
            delay += mapmetric(RIP_TO_HELLO, (u_short)ifp->int_metric);

            if (tracing & TR_UPDATE) {
              TRACE_HEL("HELLO: %s time delay %d %s\n",
				inet_ntoa(dst.sin_addr), delay,
				latched ? "latched" : "");
            }

            /* check for internal route */
            rt = rt_lookup((int)INTERIOR, &dst);
            if (rt == NULL) {	/* new route */
              struct rt_entry rttmp;

              if (delay >= DELAY_INFINITY)
                continue;
              /*
               * If the route is in Fuzzball hold down, ignore
               * it.  We don't want to use it until it stabilizes.
               */
              if (latched) {
                continue;
              }
              /*  is this new net acceptable? */
              bzero((char *)&rttmp, sizeof(rttmp));
              rttmp.rt_listenlist = control_lookup(RT_NOLISTEN, &dst);
              rttmp.rt_srclisten = control_lookup(RT_SRCLISTEN, &dst);
              if (is_valid_in(&rttmp, RTPROTO_HELLO, ifp, &gateway) == 0) {
                continue;
              }
              /*
               *	Check for and ignore martain nets
               */
              if (is_martian(dst.sin_addr)) {
                char badgate[16];
                (void) strcpy(badgate, inet_ntoa(gateway.sin_addr));
                TRACE_EXT("helloin: ignoring invalid net %s from %s at %s", inet_ntoa(dst.sin_addr), badgate, strtime);
                continue;
              }
              /* 
               * delete old external route(s), if any,
               * and add to internal table.
               */
              while (exrt = rt_lookup((int)EXTERIOR, &dst)) {
                rt_delete(exrt, KERNEL_INTR);
              }
              (void) rt_add((int)INTERIOR,(struct sockaddr *)&dst,(struct sockaddr *)&gateway,delay,0,RTPROTO_HELLO, fromproto, 0, 0);
              change = TRUE;
            } else {	/* update existing route */
              if ((rt->rt_flags & RTF_GATEWAY) == 0) {
                continue;	/* don't mess with non-gw's */
              }
              if (rt->rt_state & (RTS_INTERFACE | RTS_STATIC)) {
                continue;
              }
              if (is_valid_in(rt, RTPROTO_HELLO, ifp, &gateway) == 0) {
                continue;
              }
              if (exrt = rt_lookup((int)EXTERIOR, &dst)) {
                do {
                  rt_delete(exrt, KERNEL_INTR);
                } while (exrt = rt_lookup((int)EXTERIOR, &dst));
              }
              if (delay >= DELAY_INFINITY) {
                /* destination now unreachable */
                if (equal (&rt->rt_router, &gateway)) {
		  if ( latched ) {
                    TRACE_ACTION(LATCHED, rt);
                    change = TRUE;
                    rt->rt_timer = 0;
                  } else {
                    if (rt->rt_metric < DELAY_INFINITY) {
                      (void) rt_unreach(rt);
                      change = TRUE;
                    }
                    add_win(&rt->rt_hwindow, DELAY_INFINITY);
                  }
                }
                continue;
              }
              if (equal (&rt->rt_router, &gateway)) {
		if (latched) {
                  TRACE_ACTION(LATCHED, rt);
                  change = TRUE;
		} else {
                  if (METRIC_DIFF(rt->rt_metric,delay) >= HELLO_HYST(rt->rt_metric)) {
                    if (rt_change(rt, (struct sockaddr *)&gateway, delay, RTPROTO_HELLO, fromproto, 0, 0)) {
                      change = TRUE;
                    }
		  }
                  add_win(&rt->rt_hwindow, delay);
		}
                rt->rt_timer = 0;
              } else {
                /*
                 * if the current metric is INFINITY, then we
                 * will only listen to our current gateway.
                 * yes, I know this is a hold down. Yes, I
                 * know it's yucky.  If nothing is heard from our
                 * gateway within 120 seconds, the route is
                 * deleted.
                 */
                if (rt->rt_metric >= DELAY_INFINITY)
                  continue;
		/*
		 * If the route is in Fuzzball hold down, ignore
		 * it.  We don't want to use it until it stabilizes.
		 */
		if (latched)
		  continue;
                /*
                 * use the new gateway.
                 */
                if (((delay < rt->rt_metric) &&
                    (METRIC_DIFF(delay,rt->rt_metric) >= HELLO_HYST(rt->rt_metric))) ||
                    (rt->rt_timer > MINPOLLINT &&
                    !(rt->rt_state & RTS_CHANGED))) {
                   if (rt_change(rt, (struct sockaddr *)&gateway, delay, RTPROTO_HELLO, fromproto, 0, 0))
                     change = TRUE;
                   add_win(&rt->rt_hwindow, delay);
                   rt->rt_timer = 0;
                }
              }
            }
          } /* for each advertized net */

          break;
        default:
          syslog(LOG_ERR, "helloin: invalid type %d\n", mh->m_type);
      } /* switch (mh->m_type) */
    } /* while dsize */
  }	/* checksum */	
    
  if ( (change && (tracing & TR_RT)) || (tracing & TR_HELLO) ) {
    printf("hello_update: above %d routes from %s updated %s\n",
                   mh->m_count,inet_ntoa(ip->ip_src), strtime);
  }
} 

u_short
hello_cksum(cksumaddr, len)
u_short *cksumaddr;
int len;
{
  register int nleft = len - 2 ;
  register u_short *w = cksumaddr + 1 ;
  register int sum = 0;

  while (nleft > 0) {
    sum += *w++ ;
    if (sum & 0x10000) {
      sum &= 0xffff ;
      sum++ ;
    }
    nleft -= 2;
  }

  if (nleft == -1)
  sum += *(u_char *)w;

  return (~sum);
}

hellojob()
{
  struct interface *ifp;
  struct sockaddr_in *sin,*din;
  register struct advlist *ad;

  if (!(hello_pointopoint)) {
    for (ifp = ifnet; ifp != 0; ifp = ifp->int_next) {
      TRACE_JOB("hellojob: Checking interface %s\n", ifp->int_name);
      if (ifp->int_flags & IFF_PASSIVE) {
        TRACE_JOB("hellojob: No HELLO - %s is passive\n", ifp->int_name);
        continue;
      }
      if ( !(ifp->int_flags & IFF_UP) ) {
        TRACE_JOB("hellojob: No HELLO - %s is down\n", ifp->int_name);
        continue;
      }
      if (ifp->int_flags & IFF_NOHELLOOUT) {
        TRACE_JOB("hellojob: HELLO not allowed out %s\n", ifp->int_name);
        continue;
      }
      din = (ifp->int_flags & IFF_BROADCAST) ?
                (struct sockaddr_in *)&ifp->int_broadaddr :
                (ifp->int_flags & IFF_POINTOPOINT) ?
                    (struct sockaddr_in *)&ifp->int_dstaddr :
                    (struct sockaddr_in *)&ifp->int_addr;
      sin = (struct sockaddr_in *)&ifp->int_addr;
      TRACE_JOB("hellojob: Sending HELLO packet to %s, interface %s\n",
                inet_ntoa(din->sin_addr), ifp->int_name);
      hellotsend(din->sin_addr, sin->sin_addr, ifp);
    }
  }
  for (ad = srchellolist; ad; ad = ad->next) {
    struct sockaddr_in tmpdst;

    bzero((char *)&tmpdst, sizeof(tmpdst));
    tmpdst.sin_family = AF_INET;
    tmpdst.sin_addr = ad->destnet;
    if ((ifp = if_withnet(&tmpdst)) <= (struct interface *)0) {
      TRACE_INT("Source HELLO gateway %s not on same net\n",
                     inet_ntoa(tmpdst.sin_addr));
      continue;
    }
    if (((ifp->int_flags & IFF_UP) == 0) ||
        (ifp->int_flags & IFF_NOHELLOOUT))
      continue;
    sin = (struct sockaddr_in *)&ifp->int_addr;
    TRACE_JOB("hellojob: Sending HELLO packet to %s, interface %s\n",
              inet_ntoa(sock_inaddr(&tmpdst)), ifp->int_name);
    hellotsend(tmpdst.sin_addr, sin->sin_addr, ifp);
  }
}

hellotsend( dst, src, ifp)
	struct in_addr dst, src;  /* dest. and source internet address */
	struct interface *ifp;
{
  struct rt_entry *rt;
  struct rthash *rh;
  struct type1pair *t1;
  struct sockaddr_in sdst;
  struct m_hdr *mh;
  u_short hello_cksum();
  u_short delay, split_horizon;
  char hpkt[HELLOMAXPACKETSIZE];
  int index, save_index;
  
  /*
   * Advertise only nets that are reachable by an interface other then
   * the one we are going to use (ifp).  if that is the case, announce
   * with a metric of INFINITY.
   */

  index = /* sizeof (struct hellohdr) */ 10;

  mh = (struct m_hdr *) &hpkt[index];
  save_index = index += sizeof (struct m_hdr);
  mh->m_type = 1;
  mh->m_count = 0;

  for (rh = nethash; rh < &nethash[ROUTEHASHSIZ]; rh++) {
    for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw ) {
      if (rt->rt_proto & RTPROTO_REDIRECT) {
        continue;
      }
      if (rt->rt_state & RTS_SUBNET) {
        continue;
      }
      if ((ifp == rt->rt_ifp) &&
          (rt->rt_fromproto & (RTPROTO_HELLO|RTPROTO_DIRECT|RTPROTO_KERNEL)) &&
          !(rt->rt_state & RTS_STATIC) ) {
        split_horizon = DELAY_INFINITY;
      } else {
        split_horizon = 0;
      }
      switch (rt->rt_proto) {
        case RTPROTO_RIP:
        case RTPROTO_HELLO:
        case RTPROTO_DIRECT:
        case RTPROTO_KERNEL:
          if (!is_valid(rt, RTPROTO_HELLO, ifp)) {
            continue;
          }
          if (!(rt->rt_state & RTS_INTERIOR)) {
            continue;
          }
          delay = rt->rt_metric + mapmetric(RIP_TO_HELLO, (u_short)ifp->int_metric) + 100;
          break;
        case RTPROTO_REDIRECT:
          continue;
        case RTPROTO_DEFAULT:
          if (!is_valid(rt, RTPROTO_HELLO, ifp)) {
            continue;
          }
          if (!hello_gateway) {
            continue;
          }
          delay = hello_default + mapmetric(RIP_TO_HELLO, (u_short)ifp->int_metric);
          split_horizon = 0;
          break;
        case RTPROTO_EGP:
          if (rt->rt_as == mysystem) {
            continue;
          }
          switch (sendAS(my_aslist, rt->rt_as)) {
            case 0:	/* not valid to send */
              continue;
            case 2:	/* valid to send - announce clauses apply */
              if (!is_valid(rt, RTPROTO_HELLO, ifp)) {
                continue;
              }
              break;
            case 1:	/* valid to send */
              break;
            case -1:	/* no AS restrictions */
              continue;
          }
          delay = rt->rt_metric + mapmetric(RIP_TO_HELLO, (u_short)ifp->int_metric);
          split_horizon = 0;
          break;
        default:
          syslog(LOG_ERR, "supply: Unknown protocol %d for net %s",
                 rt->rt_proto, inet_ntoa(sock_inaddr(&rt->rt_dst)));
          TRACE_TRC("supply: Unknown protocol %d for net %s\n",
                 rt->rt_proto, inet_ntoa(sock_inaddr(&rt->rt_dst)));
      }
      if (split_horizon) {
        delay = split_horizon;
      }
      if (!(rt->rt_ifp->int_flags & IFF_UP)) {
        delay = DELAY_INFINITY;
      }
      if ((ifp->int_flags & IFF_HELLOFIXEDMETRIC) &&
          (rt->rt_proto != RTPROTO_DEFAULT) &&
          (delay < DELAY_INFINITY)) {
        delay = ifp->int_hellofixedmetric;
      }
      if ( index + sizeof(struct type1pair) >= HELLOMAXPACKETSIZE ) {
        hello_send(dst, src, (u_char *)hpkt, index, (int) mh->m_count);
        index = save_index;	/* Reset to start of network info */
	mh->m_count = 0;
      }
      t1 = (struct type1pair *)&hpkt[index];
      t1->d1_delay = htons(delay);
      if (tracing & TR_UPDATE) {
        TRACE_HEL("HELLO_SND: net %s time delay %d\n",
                inet_ntoa( sock_inaddr(&rt->rt_dst)), ntohs(t1->d1_delay));
      }
      t1->d1_offset = htons(0);		/* should be signed clock offset */
      sdst.sin_addr.s_addr =
                         htonl((u_long)gd_inet_netof(sock_inaddr(&rt->rt_dst)));
      bcopy((char *)&sdst.sin_addr,(char *)&t1->d1_dst,sizeof(struct in_addr));
      index += sizeof (struct type1pair);
      mh->m_count++;
    }
  }
  hello_send(dst, src, (u_char *)hpkt, index, (int) mh->m_count);
}

/*
 * hello_send():
 * 	Fill in the hello header and checksum, then send the packet.
 */

hello_send(dst, src, hello, length, n_nets)
     struct in_addr dst, src;  /* destination and source internet address */
     u_char	*hello;		/* pointer to start of hello packet */
     int	length;		/* length in octets of hello packet */
     int	n_nets;		/* number of nets in this update for debugging */
{
  struct sockaddr_in hellosaddr;
  struct hellohdr *hi;
  struct interface   *ifp;
  struct tm *gmt;
  struct timeval tp;
  int	error = FALSE;
  int	sendhello = TRUE;		/*  for debugging */

  hi = (struct hellohdr *) hello;
    
  (void) gettimeofday(&tp, (struct timezone *)0);
  gmt = (struct tm *) gmtime(&tp.tv_sec);
    
  /*
   * set the date field in the HELLO header.  Be very careful here as
   * the last two bits (14&15) should be set so the Fuzzware doesn't use
   * this packet to synchronize its Master Clock.  Using bitwise OR's
   * instead of addition just to be safe when dealing with h_date which
   * is an unsigned short.
   */
  bzero((char *)&hi->h_date, sizeof(hi->h_date));
#ifdef notdef
  hi->h_date = htons((u_short)(((gmt->tm_year - 72) & 037) |
                     ((gmt->tm_mday & 037) << 5) |
                     (((gmt->tm_mon + 1) & 037) << 10) |
                     ((1 & 037) << 15)));
#else notdef
  hi->h_date = htons((u_short)0xC000);
#endif notdef
  /*
   * milliseconds since midnight UT of current day
   */
  hi->h_time = htonl((u_long)((gmt->tm_sec + gmt->tm_min * 60 + gmt->tm_hour
                        * 3600) * 1000 + tp.tv_usec / 1000));
  /*
   * 16 bit field used in rt calculation,  0 for ethernets
   */
  hi->h_tstp = 0;
  hi->h_cksum = 0;
  hi->h_cksum = hello_cksum((u_short *)hello, length);

  bzero((char *)&hellosaddr, sizeof (hellosaddr));
  hellosaddr.sin_family = AF_INET;

  /* find interface to send packet on */
  hellosaddr.sin_addr = src;
  ifp = if_withnet(&hellosaddr);
  if (ifp == NULL) {
    syslog(LOG_ERR, "hello_send: no interface for source address %s\n",
              inet_ntoa( src));
    error = TRUE;
  } else {
    extern int hello_socket;

    hellosaddr.sin_addr = dst;
    if (sendhello) {
      register int flags;

      flags = ((ifp->int_flags & IFF_INTERFACE) &&
               !(ifp->int_flags & IFF_POINTOPOINT)) ? MSG_DONTROUTE : 0;
      if (sendto(hello_socket, (char *)hello, length, flags,
                     (struct sockaddr *)&hellosaddr, sizeof(hellosaddr)) < 0) {
        (void) sprintf(err_message, "hello_send: sendto() error sending %d bytes to %s",
          length, inet_ntoa(dst));
        p_error(err_message);
        error = TRUE;
      }
    }
  }
  if (!error) {
    TRACE_HELLOPKT(HELLO SENT, src, dst, (char *)hello, length, n_nets);
  } else {
    TRACE_HELLOPKT(HELLO *NOT* SENT, src, dst, (char *)hello, length, n_nets);
  }
  return;
}
