/*
 * $OpenBSD: route.c,v 1.66 2004/11/17 01:47:20 itojun Exp $
 */
/*
 * $NetBSD: route.c,v 1.15 1996/05/07 02:55:06 thorpej Exp $
 */

/*
 * Copyright (c) 1983, 1988, 1993
 *        The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <net-snmp/net-snmp-config.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#include <net-snmp/net-snmp-includes.h>

netsnmp_feature_require(sockaddr_size);

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#ifndef INET
#define INET
#endif

#include "main.h"
#include "netstat.h"
#ifdef HAVE_WINSOCK_H
#include "winstub.h"
#endif

/*
 * inetCidrRouteTable
 */
#define SET_IFNO 0x01
#define SET_TYPE 0x02
#define SET_PRTO 0x04
#define SET_AGE  0x08
#define SET_AS   0x10
#define SET_MET1 0x20
#define SET_ALL  0x3f

/*
 * ip6RouteTable
 */
#define SET_HOP     0x40
#define SET_INVALID 0x80
#define SET_ALL6    0x67
/*
 * not invalid, and only the columns that we fetch
 */

struct route_entry {
    int             af;
    struct sockaddr_storage dst;
    struct sockaddr_storage hop;
    int             mask;
    int             ifNumber;
    int             type;
    int             proto;
    int             age;
    int             as;
    int             metric1;
    int             set_bits;
    char            ifname[64];
};


static void     pr_rtxhdr(int af, const char *table);
static void     p_rtnodex(struct route_entry *rp);

/*
 * Print routing tables.
 */
int
routexpr(int af)
{
    struct route_entry route, *rp = &route;
    oid             rtcol_oid[] = { 1, 3, 6, 1, 2, 1, 4, 24, 7, 1, 0 }; /* inetCidrRouteEntry */
    size_t          rtcol_len = OID_LENGTH(rtcol_oid);
    netsnmp_variable_list *var = NULL, *vp;
    int             hdr_af = AF_UNSPEC;
    int             printed = 0;

#define ADD_RTVAR( x ) rtcol_oid[ rtcol_len-1 ] = x; \
    snmp_varlist_add_variable( &var, rtcol_oid, rtcol_len, ASN_NULL, NULL,  0)
    ADD_RTVAR(7);               /* inetCidrRouteIfIndex */
    ADD_RTVAR(8);               /* inetCidrRouteType    */
    ADD_RTVAR(9);               /* inetCidrRouteProto   */
    ADD_RTVAR(10);              /* inetCidrRouteAge   */
    ADD_RTVAR(11);              /* inetCidrRouteNextHopAS   */
    ADD_RTVAR(12);              /* inetCidrRouteMetric1   */
#undef ADD_RTVAR

    /*
     * Now walk the inetCidrRouteTable, reporting the various route entries
     */
    while (1) {
        oid            *op;
        unsigned char  *cp;
        int             i;

        if (netsnmp_query_getnext(var, ss) != SNMP_ERR_NOERROR)
            break;
        rtcol_oid[rtcol_len - 1] = 7;   /* ifRouteIfIndex */
        if (snmp_oid_compare(rtcol_oid, rtcol_len,
                             var->name, rtcol_len) != 0)
            break;              /* End of Table */
        if (var->type == SNMP_NOSUCHOBJECT ||
            var->type == SNMP_NOSUCHINSTANCE ||
            var->type == SNMP_ENDOFMIBVIEW)
            break;
        memset(&route, 0, sizeof(struct route_entry));
        /*
         * Extract inetCidrRouteDest, inetCidrRoutePfxLen,
         * * inetCidrRouteNextHop from index
         */
        switch (var->name[rtcol_len]) {
        case 1:
            {
                struct sockaddr_in *sin =
                    (struct sockaddr_in *) &route.dst;
                int             len;
                route.af = AF_INET;
                sin->sin_family = AF_INET;
                op = var->name + rtcol_len + 1;
                len = *op++;
                cp = (unsigned char *) &sin->sin_addr;
                for (i = 0; i < len; i++)
                    *cp++ = *op++;
                route.mask = *op++;
                op += *op + 1;
                op++;           /* addrType */
                op++;           /* addrLen */
                sin = (struct sockaddr_in *) &route.hop;
                sin->sin_family = AF_INET;
                cp = (unsigned char *) &sin->sin_addr;
                for (i = 0; i < len; i++)
                    *cp++ = *op++;
                break;
            }
        case 2:
            {
                struct sockaddr_in6 *sin6 =
                    (struct sockaddr_in6 *) &route.dst;
                int             len;
                route.af = AF_INET6;
                sin6->sin6_family = AF_INET6;
                op = var->name + rtcol_len + 1;
                len = *op++;
                cp = (unsigned char *) &sin6->sin6_addr;
                for (i = 0; i < len; i++)
                    *cp++ = *op++;
                route.mask = *op++;
                op += *op + 1;
                op++;           /* addrType */
                op++;           /* addrLen */
                sin6 = (struct sockaddr_in6 *) &route.hop;
                sin6->sin6_family = AF_INET6;
                cp = (unsigned char *) &sin6->sin6_addr;
                for (i = 0; i < len; i++)
                    *cp++ = *op++;
                break;
            }
        default:
            fprintf(stderr, "Bad address type: %d\n",
                    (int) var->name[rtcol_len]);
            exit(1);
        }
        /*
         * Extract ipRouteDest index value
         */

        for (vp = var; vp; vp = vp->next_variable) {
            switch (vp->name[rtcol_len - 1]) {
            case 7:            /* inetCidrRouteIfIndex */
                rp->ifNumber = *vp->val.integer;
                rp->set_bits |= SET_IFNO;
                break;
            case 8:            /* inetCidrRouteType    */
                rp->type = *vp->val.integer;
                rp->set_bits |= SET_TYPE;
                break;
            case 9:            /* inetCidrRouteProto   */
                rp->proto = *vp->val.integer;
                rp->set_bits |= SET_PRTO;
                break;
            case 10:           /* inetCidrRouteAge   */
                rp->age = *vp->val.integer;
                rp->set_bits |= SET_AGE;
                break;
            case 11:           /* inetCidrRouteNextHopAS   */
                rp->as = *vp->val.integer;
                rp->set_bits |= SET_AS;
                break;
            case 12:           /* inetCidrRouteMetric1   */
                rp->metric1 = *vp->val.integer;
                rp->set_bits |= SET_MET1;
                break;
            }
        }
        if (rp->set_bits != SET_ALL) {
            continue;           /* Incomplete query */
        }

        if (af != AF_UNSPEC && rp->af != af)
            continue;

        if (hdr_af != rp->af) {
            if (hdr_af != AF_UNSPEC)
                printf("\n");
            hdr_af = rp->af;
            pr_rtxhdr(hdr_af, "inetCidrRouteTable");
        }
        p_rtnodex(rp);
        printed++;
    }
    snmp_free_varbind(var);
    return printed;
}

/*
 * Backwards-compatibility for the IPV6-MIB
 */
int
route6pr(int af)
{
    struct route_entry route, *rp = &route;
    oid             rtcol_oid[] = { 1, 3, 6, 1, 2, 1, 55, 1, 11, 1, 0 };        /* ipv6RouteEntry */
    size_t          rtcol_len = OID_LENGTH(rtcol_oid);
    netsnmp_variable_list *var = NULL, *vp;
    int             printed = 0;
    int             hdr_af = AF_UNSPEC;
    int             i;

    if (af != AF_UNSPEC && af != AF_INET6)
        return 0;

#define ADD_RTVAR( x ) rtcol_oid[ rtcol_len-1 ] = x; \
    snmp_varlist_add_variable( &var, rtcol_oid, rtcol_len, ASN_NULL, NULL,  0)
    ADD_RTVAR(4);               /* ipv6RouteIfIndex */
    ADD_RTVAR(5);               /* ipv6RouteNextHop */
    ADD_RTVAR(6);               /* ipv6RouteType    */
    ADD_RTVAR(7);               /* ipv6RouteProto   */
    ADD_RTVAR(11);              /* ipv6RouteMetric  */
    ADD_RTVAR(14);              /* ipv6RouteValid   */
#undef ADD_RTVAR

    /*
     * Now walk the ipv6RouteTable, reporting the various route entries
     */
    while (1) {
        oid            *op;
        unsigned char  *cp, *cp1;
        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &route.dst;

        if (netsnmp_query_getnext(var, ss) != SNMP_ERR_NOERROR)
            break;
        rtcol_oid[rtcol_len - 1] = 4;   /* ipv6RouteIfIndex */
        if (snmp_oid_compare(rtcol_oid, rtcol_len,
                             var->name, rtcol_len) != 0)
            break;              /* End of Table */
        if (var->type == SNMP_NOSUCHOBJECT ||
            var->type == SNMP_NOSUCHINSTANCE ||
            var->type == SNMP_ENDOFMIBVIEW)
            break;
        memset(&route, 0, sizeof(struct route_entry));
        rp->af = AF_INET6;
        sin6->sin6_family = AF_INET6;
        op = var->name + rtcol_len;
        cp = (unsigned char *) &sin6->sin6_addr;
        for (i = 0; i < 16; i++)
            *cp++ = *op++;
        route.mask = *op++;

        for (vp = var; vp; vp = vp->next_variable) {
            switch (vp->name[rtcol_len - 1]) {
            case 4:            /* ipv6RouteIfIndex  */
                rp->ifNumber = *vp->val.integer;
                /*
                 * This is, technically, an Ipv6IfIndex, which
                 * could maybe be different than the IfIndex
                 * for the same interface.  We ignore this
                 * possibility for now, in the hopes that
                 * nobody actually allocates these numbers
                 * differently.
                 */
                rp->set_bits |= SET_IFNO;
                break;
            case 5:            /* ipv6RouteNextHop  */
                cp1 = (unsigned char *) vp->val.string;
                sin6 = (struct sockaddr_in6 *) &rp->hop;
                sin6->sin6_family = AF_INET6;
                cp = (unsigned char *) &sin6->sin6_addr;
                for (i = 0; i < 16; i++)
                    *cp++ = *cp1++;
                rp->set_bits |= SET_HOP;
                break;
            case 6:            /* ipv6RouteType     */
                rp->type = *vp->val.integer;
                /*
                 * This enum maps to similar values in inetCidrRouteType
                 */
                rp->set_bits |= SET_TYPE;
                break;
            case 7:            /* ipv6RouteProtocol */
                rp->proto = *vp->val.integer;
                /*
                 * TODO: this does not map directly to the
                 * * inetCidrRouteProtocol values.  If we use
                 * * rp->proto more, we will have to manage this.
                 */
                rp->set_bits |= SET_PRTO;
                break;
            case 11:           /* ipv6RouteMetric   */
                rp->metric1 = *vp->val.integer;
                rp->set_bits |= SET_MET1;
                break;
            case 14:           /* ipv6RouteValid    */
                if (*vp->val.integer == 2)
                    rp->set_bits |= SET_INVALID;
                break;
            }
        }
        if (rp->set_bits != SET_ALL6) {
            continue;           /* Incomplete query */
        }

        if (hdr_af != rp->af) {
            if (hdr_af != AF_UNSPEC)
                printf("\n");
            hdr_af = rp->af;
            pr_rtxhdr(AF_INET6, "ip6RouteTable");
        }
        p_rtnodex(rp);
        printed++;
    }
    snmp_free_varbind(var);
    return printed;
}


/*
 * column widths; each followed by one space
 */
#ifndef NETSNMP_ENABLE_IPV6
#define        WID_DST(af)        26    /* width of destination column */
#define        WID_GW(af)        18     /* width of gateway column */
#else
/*
 * width of destination/gateway column
 */
/*
 * strlen("fe80::aaaa:bbbb:cccc:dddd@gif0") == 30, strlen("/128") == 4
 */
#define        WID_DST(af)        ((af) == AF_INET6 ? (nflag ? 34 : 26) : 26)
#define        WID_GW(af)        ((af) == AF_INET6 ? (nflag ? 39 : 26) : 26)
#endif                          /* NETSNMP_ENABLE_IPV6 */

/*
 * Print header for routing table columns.
 */
static void
pr_rtxhdr(int af, const char *table)
{
    switch (af) {
    case AF_INET:
        printf("IPv4 Routing tables (inetCidrRouteTable)\n");
        break;
    case AF_INET6:
        printf("IPv6 Routing tables (%s)\n", table);
        break;
    }
    printf("%-*.*s ", WID_DST(af), WID_DST(af), "Destination");
    printf("%-*.*s %-6.6s  %s\n",
           WID_GW(af), WID_GW(af), "Gateway", "Flags", "Interface");
}

#ifndef HAVE_INET_NTOP
/*
 * MSVC and MinGW
 */
#define inet_ntop netsnmp_inet_ntop
static const char *
netsnmp_inet_ntop(int af, const void *src, char *dst, size_t size)
{
    DWORD           out_len = size;

    switch (af) {
    case AF_INET:
        {
            struct sockaddr_in in;

            memset(&in, 0, sizeof(in));
            in.sin_family = af;
            memcpy(&in.sin_addr, src, 4);
            if (WSAAddressToString
                ((struct sockaddr *) &in, sizeof(in), NULL, dst,
                 &out_len) == 0)
                return dst;
        }
        break;
    case AF_INET6:
        {
            struct sockaddr_in6 in6;

            memset(&in6, 0, sizeof(in6));
            in6.sin6_family = af;
            memcpy(&in6.sin6_addr, src, 16);
            if (WSAAddressToString
                ((struct sockaddr *) &in6, sizeof(in6), NULL, dst,
                 &out_len) == 0)
                return dst;
        }
        break;
    }
    return NULL;
}
#endif

/*
 * Return the name of the network whose address is given.
 * The address is assumed to be that of a net or subnet, not a host.
 */
static char    *
netxname(struct sockaddr_storage *in, int mask)
{
    static char     host[MAXHOSTNAMELEN];
    static char     line[MAXHOSTNAMELEN];
    struct sockaddr_in *sin = (struct sockaddr_in *) in;
    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) in;

    switch (in->ss_family) {
    case AF_INET:
        inet_ntop(in->ss_family, &sin->sin_addr, host, sizeof(host));
        if (mask == 32)
            strlcpy(line, host, sizeof line);
        else
            snprintf(line, sizeof(line), "%s/%d", host, mask);
        break;
    case AF_INET6:
        inet_ntop(in->ss_family, &sin6->sin6_addr, host, sizeof(host));
        if (mask == 128)
            strlcpy(line, host, sizeof line);
        else
            snprintf(line, sizeof(line), "%s/%d", host, mask);
        break;
    }
    return line;
}

static char    *
routexname(const struct sockaddr_storage *in)
{
    char           *cp;
    static char     line[MAXHOSTNAMELEN];
    char            name[MAXHOSTNAMELEN];
    static char     domain[MAXHOSTNAMELEN];
    static int      first = 1;
    const struct sockaddr *sa = (const void *) in;

    if (first) {
        first = 0;
        if (gethostname(line, sizeof(line)) == 0
            && (cp = strchr(line, '.')))
            strlcpy(domain, cp + 1, sizeof(domain));
        else
            domain[0] = '\0';
    }
    if (getnameinfo(sa, netsnmp_sockaddr_size(sa), name, sizeof(name),
                    NULL, 0, nflag ? NI_NUMERICHOST : 0) < 0)
        strlcpy(name, "?", sizeof(name));
    if ((cp = strchr(name, '.')) && strcmp(cp + 1, domain) == 0)
        *cp = '\0';
    strlcpy(line, name, sizeof(line));
    return line;
}


static char    *
s_rtflagsx(struct route_entry *rp)
{
    static char     flag_buf[10];
    char           *cp = flag_buf;

    *cp++ = '<';
    *cp++ = 'U';                /* route is in use */
    if ((rp->af == AF_INET && rp->mask == 32) ||
        (rp->af == AF_INET6 && rp->mask == 128))
        *cp++ = 'H';            /* host */
    if (rp->proto == 4)
        *cp++ = 'D';            /* ICMP redirect */
    if (rp->type == 4)
        *cp++ = 'G';            /* remote destination/net */
    *cp++ = '>';
    *cp = 0;
    return flag_buf;
}

static void
p_rtnodex(struct route_entry *rp)
{
    get_ifname(rp->ifname, rp->ifNumber);
    if (rp->af == AF_INET) {
        struct sockaddr_in *sin = (struct sockaddr_in *) &rp->dst;
        printf("%-*s ",
               WID_DST(AF_INET),
               (sin->sin_addr.s_addr == INADDR_ANY) ? "default" :
               (rp->mask == 32 ?
                routexname(&rp->dst) : netxname(&rp->dst, rp->mask)));
    } else if (rp->af == AF_INET6) {
        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&rp->dst;
        struct in6_addr in6_addr_any;

        memset(&in6_addr_any, 0, sizeof(in6_addr_any)); /*IN6ADDR_ANY_INIT */
        printf("%-*s ",
               WID_DST(AF_INET6),
               memcmp(&sin6->sin6_addr, &in6_addr_any,
                      sizeof(in6_addr_any)) == 0 ? "default" : (rp->mask ==
                                                                128 ?
                                                                routexname
                                                                (&rp->dst)
                                                                :
                                                                netxname
                                                                (&rp->dst,
                                                                 rp->mask)));
    }
    printf("%-*s %-6.6s  %s",
           WID_GW(rp->af),
           1 ? routexname(&rp->hop) : "*", s_rtflagsx(rp), rp->ifname);
    if ((rp->set_bits & SET_AS) && rp->as != 0)
        printf(" (AS %d)", rp->as);
    printf("\n");
}
