#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;
	register int OK = TRUE;
	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");
	}

	/* If we have a list of trusted gateways,
	   verify that this gateway is trusted */
	if (ripng_n_trusted && !BIT_TEST(gwp->gw_flags, GWF_TRUSTED)) {
	    OK = FALSE;
	}
	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;
#ifdef IPV6_RECVIF
	        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 */

	          /* 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;
#ifdef IPV6_RECVIF
              }
#endif /* IPV6_RECVIF */

	      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 (!OK) {
#ifndef	notdef
		    REJECT(LOG_WARNING,
			   "ripng_packet: not on trustedgateways list");
#else	/* notdef */
		    continue;
#endif	/* notdef */
		}
		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 = sockdup(task_recv_srcaddr);
		sockaddr_un *srcrouter = sockdup(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 = sockdup(sockbuild_in6(0,
				 	                (byte *)&nhe->ripng_nexthop));
			if (IN6_IS_ADDR_UNSPECIFIED(&sock2in6(nexthop))
                            || (inet6_scope_of(nexthop) != INET6_SCOPE_LINKLOCAL)) {
 			    sockfree(router);
			    router = sockdup(srcrouter);
                        } else {
			    sockfree(router);
			    router = sockdup(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;
		    }
	   
		    /* Now add hop count to metric and use infinity if the result is greater than infinity */
                    rtparms.rtp_metric = MIN(rte->ripng_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 = sockdup(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 holddown */
			    /* 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(...) */) {
			    /* Add new route */

			    rt = rt_add(&rtparms);
			} 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(...) */) {
			/* 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);
	}
    }
}
