root/usr/src/cmd/cmd-inet/usr.lib/in.ripngd/input.c
/*
 * 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.
 */

/*
 * 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;
                }
        }
}