/*
 * Public Release 3
 * 
 * $Id: ripng_recv.c,v 1.1.2.3 1998/10/20 19:31:39 swright Exp $
 */

/*
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1996, 1997, 1998 The Regents of the University of Michigan
 * All Rights Reserved
 *  
 * Royalty-free licenses to redistribute GateD Release
 * 3 in whole or in part may be obtained by writing to:
 * 
 * 	Merit GateDaemon Project
 * 	4251 Plymouth Road, Suite C
 * 	Ann Arbor, MI 48105
 *  
 * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
 * UNIVERSITY OF MICHIGAN AND MERIT DO NOT WARRANT THAT THE
 * FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
 * THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
 * University of Michigan and Merit shall not be liable for
 * any special, indirect, incidental or consequential damages with respect
 * to any claim by Licensee or any third party arising from use of the
 * software. GateDaemon was originated and developed through release 3.0
 * by Cornell University and its collaborators.
 * 
 * Please forward bug fixes, enhancements and questions to the
 * gated mailing list: gated-people@gated.merit.edu.
 * 
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1990,1991,1992,1993,1994,1995 by Cornell University.
 *     All rights reserved.
 * 
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * GateD is based on Kirton's EGP and UC Berkeley's routing
 * daemon	 (routed).
 * Development of GateD has been supported in part by the
 * National Science Foundation.
 * 
 * ------------------------------------------------------------------------
 * 
 * Portions of this software may fall under the following
 * copyrights:
 * 
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms are
 * permitted provided that the above copyright notice and
 * this paragraph are duplicated in all such forms and that
 * any documentation, advertising materials, and other
 * materials related to such distribution and use
 * acknowledge that the software was developed by the
 * University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote
 * products derived from this software without specific
 * prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "include.h"
#include "targets.h"
#include "inet6.h"
#include "ripng_internal.h"

void
ripng_recv __PF1(tp, task *)
{
    size_t size;
    int n_packets = TASK_PACKET_LIMIT;
    int pri = 0;
    char *reject_msg = (char *) 0;

#define REJECT(p, m)	{ reject_msg = (m); pri = (p); goto Reject; }

    while(n_packets-- && !task_receive_packet(tp, &size)) {
	gw_entry *gwp = (gw_entry *) 0;
	struct ripng *msg = task_get_recv_buffer(struct ripng *);
	struct ripng_netinfo *rte = (struct ripng_netinfo *) (msg + 1);
	void_t limit = (void_t) ((byte *) msg + size);

	if (socktype(task_recv_srcaddr) != AF_INET6) {
	    REJECT(0, "protocol not INET6");
	}

	/* Locate or create a gateway structure for this gateway */
	gwp = gw_locate(&ripng_gw_list,
			tp->task_rtproto,
			tp,
			(as_t) 0,
			(as_t) 0,
			task_recv_srcaddr,
			GWF_NEEDHOLD);

	if (msg->ripng_version != RIPNG_VERSION_1) {
	    REJECT(LOG_NOTICE, "ignoring invalid version packet");
	}

	ripng_trace(tp->task_trace,
		    0,
		    task_recv_interface,
		    gwp->gw_addr,
		    msg,
		    size,
		    TRUE);

        /* Verify that the packet hoplim must be 255 */
        if ((task_recv_hoplimit != RIPNG_MAXHOPLIM)
            && (inet6_scope_of(task_recv_dstaddr)) == INET6_SCOPE_MULTICAST) {
            trace_log_tp(tp,
                         0,
                         LOG_WARNING,
                         ("ripng_recv: packet hoplimit is %u",
                          task_recv_hoplimit));
            REJECT(LOG_WARNING, "ripng_packet: hop limit is not 255");
        }

	/* Verify that all reserved fields are zero */
	if (msg->ripng_null16) {
	    BIT_SET(gwp->gw_flags, GWF_FORMAT);
	    REJECT(LOG_WARNING, "ripng_header: reserved field not zero");
	}

	/* Subtrace header length from size */
	size -= sizeof (struct ripng);

	/* Process packet */
	switch(msg->ripng_command) {
	  case RIPNG_COMMAND_REQUEST:
	      {
	        target *tlp2, *tlp = (target *) 0;
#if defined(IPV6_RECVIF) || defined(IPV6_PKTINFO)
	        if (task_recv_interface) {

	          /* Find the Target for the receiving interface */
	          TARGET_LIST(tlp2, &ripng_targets) {
	 	      /* Look for the target for this interface */

		      if (BIT_TEST(tlp2->target_flags, TARGETF_SUPPLY)) {
		          if (tlp2->target_gwp == gwp) {
			      /* Found a target for this gateway */

			      tlp = tlp2;
			      break;
			  } else if (tlp2->target_ifap == task_recv_interface) {
			      /* Found a target for this interface */
			      /* rememberit, but keep looking in case */
			      /* there is one for the gateway */

			          tlp = tlp2;
			  }
		      }
		  } TARGET_LIST_END(tlp2, &ripng_targets);

	      } else {
#endif /* IPV6_RECVIF || IPV6_PKTINFO */
	          /* I cannot split-horizon without the receiving interface */
		  trace_tp(tp,
			   TR_ALL,
			   0,
			   ("ripng_recv: I cannot split-horizon without the receiving interface for %#A -> %#A",
			    task_recv_srcaddr,
			    task_recv_dstaddr));
		  break;
#if defined(IPV6_RECVIF) || defined(IPV6_PKTINFO)
              }
#endif /* IPV6_RECVIF || IPV6_PKTINFO */

	      if ((size == sizeof(struct ripng_netinfo))
#ifdef	SUNOS5_0
		  && (rte->ripng_prefix.s6_addr[0] == 0)
		  && (rte->ripng_prefix.s6_addr[1] == 0)
		  && (rte->ripng_prefix.s6_addr[2] == 0)
		  && (rte->ripng_prefix.s6_addr[3] == 0)
#else
		  && (rte->ripng_prefix.s6_addr32[0] == 0)
		  && (rte->ripng_prefix.s6_addr32[1] == 0)
		  && (rte->ripng_prefix.s6_addr32[2] == 0)
		  && (rte->ripng_prefix.s6_addr32[3] == 0)
#endif	/* SUNOS5_0 */
		  && (rte->ripng_prefixlen == 0)
		  && (rte->ripng_metric == RIPNG_METRIC_UNREACHABLE)) {

		  /* Request for the entire routing table */
		  if (tlp) {
		      ripng_supply(tlp,
			  	   task_recv_srcaddr,
				   tlp->target_flags,
				   0,
				   FALSE);
		  } else {
		      trace_tp(tp,
			       TR_ALL,
			       0,
			       ("ripng_recv: cannot detect the target for %A, ignoring",
				task_recv_srcaddr));
		  }
	      } else {
		  /* Request for just some routes */
                  rt_open(tp);

                  for(; (void_t)rte < limit; rte++) {
                      rt_parms rtparms;
                      rt_entry *rt;

                      rtparms.rtp_dest = sockbuild_in6(0,
                                                       (byte *)&rte->ripng_prefix);

                      rtparms.rtp_dest_mask = inet6_masks[rte->ripng_prefixlen];


                      rt = rt_locate(RTS_INTERIOR,
                                     rtparms.rtp_dest,
                                     rtparms.rtp_dest_mask,
                                     RTPROTO_BIT(tp->task_rtproto));
                      if (rt) {
                          rte->ripng_metric = rt->rt_metric;
		      } else {
			  rte->ripng_metric = RIPNG_METRIC_UNREACHABLE;
		      }
		  }

		  msg->ripng_command = RIPNG_COMMAND_RESPONSE;

		  ripng_send(tlp->target_task,
		             tlp->target_ifap,
                             tlp->target_flags,
			     task_recv_srcaddr,
		 	     msg,
			     size);
		  rt_close(tp, (gw_entry *) 0, 0, NULL);
	      }
            }
	  break;

	  case RIPNG_COMMAND_RESPONSE:
	    {
		if_addr *ifap;

		/* If this packet is from me, flag, the interface as up and */
		/* ignore the packet */
		if (task_recv_interface) {
		    IF_ADDR(ifap) {
			if ((ifap->ifa_link == task_recv_interface->ifa_link)
			    && (sockaddrcmp_in6(gwp->gw_addr, ifap->ifa_addr_local))) {
			    if (!BIT_TEST(ifap->ifa_state, IFS_SIMPLEX)) {
				/* If this interface is not simplex, */
				/* indicate that the media  is functioning */
				if_rtupdate(ifap);
			    }
			    REJECT(0, "packet from me");
			}
		    } IF_ADDR_END(ifap);

		    /* Update interface timer on interface */
		    /* that packet came in on. */
		    if_rtupdate(task_recv_interface);
		}

		if (inet6_scope_of(task_recv_srcaddr) != INET6_SCOPE_LINKLOCAL) {
		    REJECT(LOG_WARNING, "ripng_packet: response message, source address is not link-local");
                }

		if (task_recv_srcaddr->in6.gin6_port != htons(RIPNG_PORT)) {
		    REJECT(LOG_WARNING, "ripng_packet: response message, source port is not valid");
                }

		BIT_SET(gwp->gw_flags, GWF_ACCEPT);
		gwp->gw_time = time_sec;
	    }

            if (!ripng_timer_expire) {
                ripng_timer_expire = task_timer_create(tp,
                                                       "Expire",
                                                       0,
                                                       (time_t) RIPNG_T_EXPIRE,
                                                       0,
                                                       ripng_expire,
                                                       (void_t) 0);
            }

	    /* take in routes */
	    {
		int routes = 0;
		rt_parms rtparms;
		struct ifa_ps *ips = 0;
		sockaddr_un *router = task_recv_srcaddr;
		sockaddr_un *srcrouter = task_recv_srcaddr;

		if (task_recv_interface) {
		    ips = &task_recv_interface->ifa_ps[tp->task_rtproto];
		}

		bzero((caddr_t) &rtparms, sizeof(rtparms));
		rtparms.rtp_n_gw = 1;
		rtparms.rtp_gwp = gwp;

		rt_open(tp);

		for(; (void_t)rte < limit; rte++) {
		    rt_entry *rt;

		    if (rte->ripng_metric == RIPNG_METRIC_NEXTHOP) {
			sockaddr_un *nexthop;
			/* Next Hop Entry */

#define nhe ((struct ripng_nexthop *)rte)
			nexthop = sockbuild_in6(0, (byte *)&nhe->ripng_nexthop);
			if (IN6_IS_ADDR_UNSPECIFIED(&sock2in6(nexthop))
                            || (inet6_scope_of(nexthop) != INET6_SCOPE_LINKLOCAL)) {
 			    sockfree(router);
			    router = srcrouter;
                        } else {
			    sockfree(router);
			    router = nexthop;
                        }
#undef nhe
			continue;
		    }

		    /* Normal route */
		    routes++;

		    rtparms.rtp_dest = sockbuild_in6(0,
                                                     (byte *)&rte->ripng_prefix);
		    switch(inet6_scope_of(rtparms.rtp_dest)) {
		      case INET6_SCOPE_MULTICAST:
		      case INET6_SCOPE_LINKLOCAL:
			continue;
		    }

		    rtparms.rtp_preference = ripng_preference;

		    if (!rte->ripng_metric
			|| (rte->ripng_metric > RIPNG_METRIC_UNREACHABLE)) {
			trace_log_tp(tp,
				     0,
				     LOG_NOTICE,
				     ("ripng_recv: bad metric(%u) for net %A from %#A",
				      rte->ripng_metric,
				      rtparms.rtp_dest,
				      task_recv_srcaddr));
			continue;
		    }
		    rtparms.rtp_metric = rte->ripng_metric;

		    /*
		     * Now add hop count to metric and use infinitu if
                     *  the result is greater than infinity
		     */
                    rtparms.rtp_metric = MIN(rtparms.rtp_metric + (ips ? ips->ips_metric_in : 1), RIPNG_METRIC_UNREACHABLE);

		    rtparms.rtp_state = RTS_INTERIOR;

		    /* Determine the mask and router */
		    if (rte->ripng_prefixlen > 128) {
			trace_log_tp(tp,
				     0,
				     LOG_NOTICE,
				     ("ripng_recv: bad prefixlen(%u) for net %A from %#A",
				      rte->ripng_prefixlen,
				      rtparms.rtp_dest,
				      task_recv_srcaddr));
			continue;
		    }
		    rtparms.rtp_dest_mask = inet6_masks[rte->ripng_prefixlen];

		    rtparms.rtp_router = router;

		    rtparms.rtp_tag = rte->ripng_tag;

		    rt = rt_locate(rtparms.rtp_state,
				   rtparms.rtp_dest,
				   rtparms.rtp_dest_mask,
				   RTPROTO_BIT(tp->task_rtproto));

		    if (!rt) {
			rt_head *rth;

			/* No route installed. See if we are announcing */
			/* another route */
			rt = rt_locate(RTS_NETROUTE,
				       rtparms.rtp_dest,
				       rtparms.rtp_dest_mask,
				       RTPROTO_BIT_ANY);

			if (rt
			    && (rth = rt->rt_head)
			    && (rth->rth_n_announce)
			    && (rt == rth->rth_active
				|| rt == rth->rth_holddown)) {
			    /* We are announcing this route */
			    register target *tlp;

			    /* RIPng won't announce an active route if */
			    /* one is holddown.  so check the holddwn */
			    /* route first */
			    rt = rth->rth_holddown;
			    if (!rt)
			      rt = rth->rth_active;

			    TARGET_LIST(tlp, &ripng_targets) {
				if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)
				    && rtbit_isset(rt, tlp->target_rtbit)) {
				    /* We are sending to this target */
				    td_entry *tdp;

				    TD_TSI_GET(tlp, rth, tdp);

				    if (BIT_TEST(tdp->td_flags, TDF_HOLDDOWN|TDF_POISON)
					|| (tdp->td_metric < rtparms.rtp_metric)) {
					/* We are announcing this route */
					/* from another protocol */
					break;
				    }
				}
			    } TARGET_LIST_END(tlp, &ripng_targets);

			    if (tlp) {
				/* Announced via another protocol, */
				/* ignore this route */
				continue;
			    }
			}

			/* New route */
			if ((rtparms.rtp_metric < RIPNG_METRIC_UNREACHABLE)
			    && import(rtparms.rtp_dest,
				      rtparms.rtp_dest_mask,
				      ripng_import_list,
				      ips ? ips->ips_import : (adv_entry *) 0,
				      rtparms.rtp_gwp->gw_import,
				      &rtparms.rtp_preference,
				      (metric_t *) 0,
				      task_recv_interface,
				      (void_t) 0)) {

			    rt = rt_add(&rtparms); /* Add new route */
			} else {
			    BIT_SET(rtparms.rtp_gwp->gw_flags, GWF_IMPORT);
			}
		    } else if (rt->rt_gwp == gwp) {
			/* same route and same gateway */
			if ((rtparms.rtp_metric != rt->rt_metric)
		 	    || !sockaddrcmp_in6(rtparms.rtp_router, RT_ROUTER(rt))
			    || rtparms.rtp_tag != rt->rt_tag) {
			    (void) rt_change(rt,
					     rtparms.rtp_metric,
					     rtparms.rtp_metric2,
					     rtparms.rtp_tag,
					     rt->rt_preference,
					     rt->rt_preference2,
					     1,
					     &rtparms.rtp_router);
                        }
			rt_refresh(rt);
		    } else if ((rtparms.rtp_metric < rt->rt_metric
				|| ((rt_age(rt) > RIPNG_T_EXPIRE)
				    && rt->rt_metric == rtparms.rtp_metric))
			       && import(rtparms.rtp_dest,
					 rtparms.rtp_dest_mask,
					 ripng_import_list,
					 ips ? ips->ips_import : (adv_entry *) 0,
					 rtparms.rtp_gwp->gw_import,
					 &rtparms.rtp_preference,
					 (metric_t *) 0,
					 task_recv_interface,
					 (void_t) 0)) {
			/* Better metric or same metric and old route */
			/* has not been refreshed. */

			(void)rt_delete(rt);
			rt = rt_add(&rtparms);
		    }
		}		/* for each route */

		rt_close(tp, rtparms.rtp_gwp, routes, NULL);
	    }
	    break;

	  default:
	    pri = BIT_TEST(gwp->gw_flags, GWF_FORMAT) ? 0 : LOG_WARNING;
	    BIT_SET(gwp->gw_flags, GWF_FORMAT);
	    REJECT(0, "invalid or not implemented command");
	}
	continue;

      Reject:
	tracef("rip_recv: ignoring RIPng ");
	if (msg->ripng_command < RIPNG_COMMAND_MAX) {
	    tracef("%s",
		   trace_state(ripng_cmd_bits, msg->ripng_command));
	} else {
	    tracef("#%d",
		   msg->ripng_command);
	}
	trace_log_tp(tp,
		     0,
		     pri,
		     (" packet from %#A - %s",
		      task_recv_srcaddr,
		      reject_msg));
	trace_only_tp(tp,
		      0,
		      (NULL));

	if (gwp) {
	    BIT_SET(gwp->gw_flags, GWF_REJECT);
	}
    }
}
