/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1999 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * Portions of this source code were derived from Berkeley 4.3 BSD
 * under license from the Regents of the University of California.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Routing Table Management Daemon
 */
#include "defs.h"

static char	buf1[INET6_ADDRSTRLEN];
static char	buf2[INET6_ADDRSTRLEN];

static void	rip_input(struct sockaddr_in6 *from, int size, uint_t hopcount,
    struct interface *ifp);

/*
 * Return a pointer to the specified option buffer.
 * If not found return NULL.
 */
static void *
find_ancillary(struct msghdr *rmsg, int cmsg_type)
{
	struct cmsghdr *cmsg;

	for (cmsg = CMSG_FIRSTHDR(rmsg); cmsg != NULL;
	    cmsg = CMSG_NXTHDR(rmsg, cmsg)) {
		if (cmsg->cmsg_level == IPPROTO_IPV6 &&
		    cmsg->cmsg_type == cmsg_type) {
			return (CMSG_DATA(cmsg));
		}
	}
	return (NULL);
}

/*
 * Read a packet and passes it to rip_input() for processing.
 */
void
in_data(struct interface *ifp)
{
	struct sockaddr_in6 from;
	int len;
	struct msghdr rmsg;
	struct iovec iov;
	uchar_t *hopcntopt;

	iov.iov_base = packet;
	iov.iov_len = IPV6_MAX_PACKET;
	rmsg.msg_name = &from;
	rmsg.msg_namelen = (socklen_t)sizeof (from);
	rmsg.msg_iov = &iov;
	rmsg.msg_iovlen = 1;
	rmsg.msg_control = control;
	rmsg.msg_controllen = IPV6_MAX_PACKET;

	if ((len = recvmsg(ifp->int_sock, &rmsg, 0)) < 0) {
		/*
		 * Only syslog if a true error occurred.
		 */
		if (errno != EINTR)
			syslog(LOG_ERR, "in_data: recvmsg: %m");
		return;
	}
	if (len == 0)
		return;

	if (tracing & INPUT_BIT) {
		(void) inet_ntop(from.sin6_family, &from.sin6_addr, buf1,
		    sizeof (buf1));
	}

	/* Ignore packets > 64k or control buffers that don't fit */
	if (rmsg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
		if (tracing & INPUT_BIT) {
			(void) fprintf(stderr,
			    "Truncated message: msg_flags 0x%x from %s\n",
			    rmsg.msg_flags, buf1);
		}
		return;
	}

	if ((hopcntopt = find_ancillary(&rmsg, IPV6_HOPLIMIT)) == NULL) {
		if (tracing & INPUT_BIT) {
			(void) fprintf(stderr, "Unknown hop limit from %s\n",
			    buf1);
		}
		return;
	}
	rip_input(&from, len, *(uint_t *)hopcntopt, ifp);
}

/*
 * Process a newly received packet.
 */
static void
rip_input(struct sockaddr_in6 *from, int size, uint_t hopcount,
    struct interface *ifp)
{
	struct rt_entry *rt;
	struct netinfo6 *n;
	int newsize;
	boolean_t changes = _B_FALSE;
	int answer = supplier;
	struct in6_addr prefix;
	struct in6_addr nexthop;
	struct in6_addr *gate;
	boolean_t foundnexthop = _B_FALSE;
	struct sioc_addrreq sa;
	struct sockaddr_in6 *sin6;

	TRACE_INPUT(ifp, from, size);
	if (tracing & INPUT_BIT) {
		(void) inet_ntop(from->sin6_family, (void *)&from->sin6_addr,
		    buf1, sizeof (buf1));
	}

	/*
	 * If the packet is recevied on an interface with IFF_NORTEXCH flag set,
	 * we ignore the packet.
	 */
	if (ifp->int_flags & RIP6_IFF_NORTEXCH) {
		if (tracing & INPUT_BIT) {
			(void) fprintf(ftrace,
			    "Ignore received RIPng packet on %s "
			    "(no route exchange on interface)\n",
			    ifp->int_name);
			(void) fflush(ftrace);
		}
		return;
	}
	if (msg->rip6_vers != RIPVERSION6) {
		if (tracing & INPUT_BIT) {
			(void) fprintf(ftrace,
			    "Bad version number %d in packet from %s\n",
			    msg->rip6_vers, buf1);
			(void) fflush(ftrace);
		}
		return;
	}
	if (ntohs(msg->rip6_res1) != 0) {
		if (tracing & INPUT_BIT) {
			(void) fprintf(ftrace,
			    "Non-zero reserved octets found in packet from "
			    "%s\n",
			    buf1);
			(void) fflush(ftrace);
		}
	}

	switch (msg->rip6_cmd) {

	case RIPCMD6_REQUEST:		/* multicasted request */
		ifp->int_ipackets++;
		newsize = 0;

		/*
		 * Adjust size by the length of the command, version and
		 * reserved fields (which are in total 32-bit aligned).
		 */
		size -= sizeof (msg->rip6_cmd) + sizeof (msg->rip6_vers) +
		    sizeof (msg->rip6_res1);

		/*
		 * From section 2.4.1 of RFC 2080:
		 *
		 *	If there is exactly one entry in the request with a
		 *	destination prefix of zero, a prefix length of zero and
		 *	an infinite metric, then supply the entire routing
		 *	table.
		 */
		n = msg->rip6_nets;
		if (size == sizeof (struct netinfo6) &&
		    n->rip6_prefix_length == 0 &&
		    n->rip6_metric == HOPCNT_INFINITY) {
			rtcreate_prefix(&n->rip6_prefix, &prefix,
			    n->rip6_prefix_length);
			if (IN6_IS_ADDR_UNSPECIFIED(&prefix)) {
				supply(from, ifp, 0,
				    from->sin6_port == rip6_port);
				return;
			}
		}
		for (; size >= sizeof (struct netinfo6);
		    size -= sizeof (struct netinfo6), n++) {
			if (n->rip6_prefix_length > IPV6_ABITS) {
				if (tracing & INPUT_BIT) {
					(void) fprintf(ftrace,
					    "Bad prefix length %d in request "
					    "from %s\n",
					    n->rip6_prefix_length, buf1);
					(void) fflush(ftrace);
				}
				continue;
			}
			if (IN6_IS_ADDR_LINKLOCAL(&n->rip6_prefix) ||
			    IN6_IS_ADDR_MULTICAST(&n->rip6_prefix)) {
				if (tracing & INPUT_BIT) {
					(void) fprintf(ftrace,
					    "Bad prefix %s in request from "
					    "%s\n",
					    inet_ntop(AF_INET6,
						(void *)&n->rip6_prefix, buf2,
						sizeof (buf2)),
					    buf1);
					(void) fflush(ftrace);
				}
				continue;
			}
			rtcreate_prefix(&n->rip6_prefix, &prefix,
			    n->rip6_prefix_length);
			rt = rtlookup(&prefix, n->rip6_prefix_length);

			n->rip6_metric = (rt == NULL ?
			    HOPCNT_INFINITY :
			    min(rt->rt_metric, HOPCNT_INFINITY));
			newsize += sizeof (struct netinfo6);
		}
		if (size > 0) {
			if (tracing & INPUT_BIT) {
				(void) fprintf(ftrace,
				    "Ignoring %d octets of trailing data in "
				    "request from %s\n",
				    size, buf1);
				(void) fflush(ftrace);
			}
		}
		if (answer && newsize > 0) {
			/*
			 * Adjust newsize by the length of the command, version
			 * and reserved fields (which are in total 32-bit
			 * aligned).
			 */
			msg->rip6_cmd = RIPCMD6_RESPONSE;
			newsize += sizeof (msg->rip6_cmd) +
			    sizeof (msg->rip6_vers) + sizeof (msg->rip6_res1);
			sendpacket(from, ifp, newsize, 0);
		}
		return;

	case RIPCMD6_RESPONSE:
		if (hopcount != IPV6_MAX_HOPS) {
			if (tracing & INPUT_BIT) {
				(void) fprintf(ftrace,
				    "Bad hop count %d in response from %s\n",
				    hopcount, buf1);
				(void) fflush(ftrace);
			}
			return;
		}

		if (from->sin6_port != rip6_port) {
			if (tracing & INPUT_BIT) {
				(void) fprintf(ftrace,
				    "Bad source port %d in response from %s\n",
				    from->sin6_port, buf1);
				(void) fflush(ftrace);
			}
			return;
		}

		if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) {
			if (tracing & INPUT_BIT) {
				(void) fprintf(ftrace,
				    "Bad source address (not link-local) in "
				    "response from %s\n", buf1);
				(void) fflush(ftrace);
			}
			return;
		}
		ifp->int_ipackets++;

		/*
		 * Adjust size by the length of the command, version and
		 * reserved fields (which are in total 32-bit aligned).
		 */
		size -= sizeof (msg->rip6_cmd) + sizeof (msg->rip6_vers) +
		    sizeof (msg->rip6_res1);
		for (n = msg->rip6_nets;
		    supplier && size >= sizeof (struct netinfo6);
		    size -= sizeof (struct netinfo6), n++) {
			/*
			 * From section 2.1.1 of RFC 2080:
			 *
			 * This is a next hop RTE if n->rip6_metric is set to
			 * HOPCNT_NEXTHOP.  If the next hop address (which is
			 * placed in the prefix field of this special RTE) is
			 * unspecified or is not a link-local address, then use
			 * the originator's address instead (effectively turning
			 * off next hop RTE processing.)
			 */
			if (n->rip6_metric == HOPCNT_NEXTHOP) {
				/*
				 * First check to see if the unspecified address
				 * was given as the next hop address.  This is
				 * the correct way of specifying the end of use
				 * of a next hop address.
				 */
				if (IN6_IS_ADDR_UNSPECIFIED(&n->rip6_prefix)) {
					foundnexthop = _B_FALSE;
					continue;
				}
				/*
				 * A next hop address that is not a link-local
				 * address is treated as the unspecified one.
				 * Trace this event if input tracing is enabled.
				 */
				if (!IN6_IS_ADDR_LINKLOCAL(&n->rip6_prefix)) {
					foundnexthop = _B_FALSE;
					if (tracing & INPUT_BIT) {
						(void) fprintf(ftrace,
						    "Bad next hop %s in "
						    "response from %s\n",
						    inet_ntop(AF_INET6,
							(void *)&n->rip6_prefix,
							buf2, sizeof (buf2)),
						    buf1);
					}
					continue;
				}
				/*
				 * Verify that the next hop address is not one
				 * of our own.
				 */
				sin6 = (struct sockaddr_in6 *)&sa.sa_addr;
				sin6->sin6_family = AF_INET6;
				sin6->sin6_addr = n->rip6_prefix;
				if (ioctl(iocsoc, SIOCTMYADDR,
				    (char *)&sa) < 0) {
					syslog(LOG_ERR,
					    "rip_input: "
					    "ioctl (verify my address): %m");
					return;
				}
				if (sa.sa_res != 0) {
					foundnexthop = _B_FALSE;
					if (tracing & INPUT_BIT) {
						(void) fprintf(ftrace,
						    "Bad next hop %s is self "
						    "in response from %s\n",
						    inet_ntop(AF_INET6,
							(void *)&n->rip6_prefix,
							buf2, sizeof (buf2)),
						    buf1);
					}
					continue;
				}
				foundnexthop = _B_TRUE;
				nexthop = n->rip6_prefix;
				continue;
			}
			if (foundnexthop)
				gate = &nexthop;
			else
				gate = &from->sin6_addr;

			if (n->rip6_metric > HOPCNT_INFINITY ||
			    n->rip6_metric < 1) {
				if (tracing & INPUT_BIT) {
					(void) fprintf(ftrace,
					    "Bad metric %d in response from "
					    "%s\n",
					    n->rip6_metric, buf1);
					(void) fflush(ftrace);
				}
				continue;
			}
			if (n->rip6_prefix_length > IPV6_ABITS) {
				if (tracing & INPUT_BIT) {
					(void) fprintf(ftrace,
					    "Bad prefix length %d in response "
					    "from %s\n",
					    n->rip6_prefix_length, buf1);
					(void) fflush(ftrace);
				}
				continue;
			}

			if (IN6_IS_ADDR_LINKLOCAL(&n->rip6_prefix) ||
			    IN6_IS_ADDR_MULTICAST(&n->rip6_prefix)) {
				if (tracing & INPUT_BIT) {

					(void) fprintf(ftrace,
					    "Bad prefix %s in response from "
					    "%s\n",
					    inet_ntop(AF_INET6,
						(void *)&n->rip6_prefix, buf2,
						sizeof (buf2)),
					    buf1);
					(void) fflush(ftrace);
				}
				continue;
			}
			/* Include metric for incoming interface */
			n->rip6_metric += IFMETRIC(ifp);

			rtcreate_prefix(&n->rip6_prefix, &prefix,
			    n->rip6_prefix_length);
			rt = rtlookup(&prefix, n->rip6_prefix_length);
			if (rt == NULL) {
				if (n->rip6_metric < HOPCNT_INFINITY) {
					rtadd(&prefix,
					    gate, n->rip6_prefix_length,
					    n->rip6_metric, n->rip6_route_tag,
					    _B_FALSE, ifp);
					changes = _B_TRUE;
				}
				continue;
			}

			/*
			 * If the supplied metric is at least HOPCNT_INFINITY
			 * and the current metric of the route is
			 * HOPCNT_INFINITY, then this particular RTE is ignored.
			 */
			if (n->rip6_metric >= HOPCNT_INFINITY &&
			    rt->rt_metric == HOPCNT_INFINITY)
				continue;

			/*
			 * From section 2.4.2 of RFC 2080:
			 *
			 * Update if any one of the following is true
			 *
			 *	1) From current gateway and a different metric.
			 *	2) From current gateway and a different index.
			 *	3) A shorter (smaller) metric.
			 *	4) Equivalent metric and an age at least
			 *	   one-half of EXPIRE_TIME.
			 *
			 * Otherwise, update timer for the interface on which
			 * the packet arrived.
			 */
			if (IN6_ARE_ADDR_EQUAL(gate, &rt->rt_router)) {
				if (n->rip6_metric != rt->rt_metric ||
				    rt->rt_ifp != ifp) {
					rtchange(rt, gate, n->rip6_metric, ifp);
					changes = _B_TRUE;
				} else if (n->rip6_metric < HOPCNT_INFINITY) {
					rt->rt_timer = 0;
				}
			} else if (n->rip6_metric < rt->rt_metric ||
			    (rt->rt_timer > (EXPIRE_TIME / 2) &&
				rt->rt_metric == n->rip6_metric)) {
				rtchange(rt, gate, n->rip6_metric, ifp);
				changes = _B_TRUE;
			}
		}
		if (changes && supplier)
			dynamic_update(ifp);
		return;

	default:
		if (tracing & INPUT_BIT) {
			(void) fprintf(ftrace,
			    "Bad command %d in packet from %s\n",
			    msg->rip6_cmd, buf1);
			(void) fflush(ftrace);
		}
		return;
	}
}

/*
 * If changes have occurred, and if we have not sent a multicast
 * recently, send a dynamic update.  This update is sent only
 * on interfaces other than the one on which we received notice
 * of the change.  If we are within MIN_WAIT_TIME of a full update,
 * don't bother sending; if we just sent a dynamic update
 * and set a timer (nextmcast), delay until that time.
 * If we just sent a full update, delay the dynamic update.
 * Set a timer for a randomized value to suppress additional
 * dynamic updates until it expires; if we delayed sending
 * the current changes, set needupdate.
 */
void
dynamic_update(struct interface *ifp)
{
	int delay;

	if (now.tv_sec - lastfullupdate.tv_sec >=
	    supplyinterval - MIN_WAIT_TIME)
		return;

	if (now.tv_sec - lastmcast.tv_sec >= MIN_WAIT_TIME &&
	    /* BEGIN CSTYLED */
	    timercmp(&nextmcast, &now, <)) {
	    /* END CSTYLED */
		TRACE_ACTION("send dynamic update",
		    (struct rt_entry *)NULL);
		supplyall(&allrouters, RTS_CHANGED, ifp, _B_TRUE);
		lastmcast = now;
		needupdate = _B_FALSE;
		nextmcast.tv_sec = 0;
	} else {
		needupdate = _B_TRUE;
		TRACE_ACTION("delay dynamic update",
		    (struct rt_entry *)NULL);
	}

	if (nextmcast.tv_sec == 0) {
		delay = GET_RANDOM(MIN_WAIT_TIME * 1000000,
		    MAX_WAIT_TIME * 1000000);
		if (tracing & ACTION_BIT) {
			(void) fprintf(ftrace,
			    "inhibit dynamic update for %d msec\n",
			    delay / 1000);
			(void) fflush(ftrace);
		}
		nextmcast.tv_sec = delay / 1000000;
		nextmcast.tv_usec = delay % 1000000;
		timevaladd(&nextmcast, &now);
		/*
		 * If the next possibly dynamic update
		 * is within MIN_WAIT_TIME of the next full
		 * update, force the delay past the full
		 * update, or we might send a dynamic update
		 * just before the full update.
		 */
		if (nextmcast.tv_sec >
		    lastfullupdate.tv_sec + supplyinterval - MIN_WAIT_TIME) {
			nextmcast.tv_sec =
			    lastfullupdate.tv_sec + supplyinterval + 1;
		}
	}
}