/*
 * $OpenBSD: inet6.c,v 1.31 2004/11/17 01:47:20 itojun Exp $
 */
/*
 * BSDI inet.c,v 2.3 1995/10/24 02:19:29 prb 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>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_WINSOCK_H
#include "winstub.h"
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#if 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>

#include "main.h"
#include "netstat.h"

struct stat_table {
    unsigned int    entry;      /* entry number in table */
    /*
     * format string to printf(description, value)
     * warning: the %d must be before the %s
     */
    char            description[80];
};

void            inetxprint(int, struct sockaddr_in6, int, const char *,
                           int);

/*
 * Print a summary of TCP connections
 * Listening processes are suppressed unless the
 *   -a (all) flag is specified.
 */
const char     *tcpxstates[] = {
    "",
    "CLOSED",
    "LISTEN",
    "SYNSENT",
    "SYNRECEIVED",
    "ESTABLISHED",
    "FINWAIT1",
    "FINWAIT2",
    "CLOSEWAIT",
    "LASTACK",
    "CLOSING",
    "TIMEWAIT"
};

#define TCP_NSTATES 11

typedef struct {
    int             stat;
    int             hcstat;
    const char     *str;
} systemstats_t;

systemstats_t   systemstats[] = {
    {3, 4, "datagrams input"},
    {5, 6, "octets received"},
    {7, 0, "hdr errors input"},
    {8, 0, "no routes input"},
    {9, 0, "address errors input"},
    {10, 0, "unknown protocol input"},
    {12, 13, "input datagrams forwarded"},
    {11, 0, "truncated datagrams input"},
    {14, 0, "input reassembly required"},
    {15, 0, "input reassemled OK"},
    {16, 0, "input reassembly failed"},
    {17, 0, "input datagrams discarded"},
    {18, 19, "input datagrams received"},
    {20, 21, "output datagram requests"},
    {22, 0, "output no route"},
    {23, 24, "datagrams forwarded"},
    {25, 0, "output datagrams discarded"},
    {26, 0, "output datagrams fragmentation required"},
    {27, 0, "output datagrams fragmented"},
    {28, 0, "output fragmentation failed"},
    {29, 0, "fragments created"},
    {30, 31, "datagrams transmitted"},
    {32, 33, "octets transmitted"},
    {0}
};

systemstats_t   icmpstats[] = {
    {2, 0, "input messages"},
    {3, 0, "input errors"},
    {4, 0, "output messages"},
    {5, 0, "output errors"},
    {0}
};

typedef struct {
    int             code;
    const char     *name;
} codelist_t;

codelist_t      icmpcodes[] = {
    {0, "Echo reply"},
    {3, "Destination unreachable"},
    {4, "Source quench"},
    {5, "Redirect"},
    {6, "Alternate host address"},
    {8, "Echo request"},
    {9, "Router advertisement"},
    {10, "Router selection"},
    {11, "Time exceeded"},
    {12, "Parameter problem"},
    {13, "Timestamp request"},
    {14, "Timestamp reply"},
    {15, "Information request"},
    {16, "Information reply"},
    {17, "Address mask request"},
    {18, "Address mask reply"},
    {0}
};

codelist_t      icmp6codes[] = {
    {1, "Destination Unreachable"},
    {2, "Packet Too Big"},
    {3, "Time Exceeded"},
    {4, "Parameter Problem"},
    {100, "Private experimentation 100"},
    {101, "Private experimentation 101"},
    {127, "Reserved for expansion of ICMPv6 error messages"},
    {128, "Echo Request"},
    {129, "Echo Reply"},
    {130, "Multicast Listener Query"},
    {131, "Multicast Listener Report"},
    {132, "Multicast Listener Done"},
    {133, "Router Solicitation"},
    {134, "Router Advertisement"},
    {135, "Neighbor Solicitation"},
    {136, "Neighbor Advertisement"},
    {137, "Redirect Message"},
    {138, "Router Renumbering"},
    {139, "ICMP Node Information Query"},
    {140, "ICMP Node Information Response"},
    {141, "Inverse Neighbor Discovery Solicitation Message"},
    {142, "Inverse Neighbor Discovery Advertisement Message"},
    {143, "Version 2 Multicast Listener Report"},
    {144, "Home Agent Address Discovery Request Message"},
    {145, "Home Agent Address Discovery Reply Message"},
    {146, "Mobile Prefix Solicitation"},
    {147, "Mobile Prefix Advertisement"},
    {148, "Certification Path Solicitation Message"},
    {149, "Certification Path Advertisement Message"},
    {151, "Multicast Router Advertisement"},
    {152, "Multicast Router Solicitation"},
    {153, "Multicast Router Termination"},
    {154, "FMIPv6 Messages"},
    {155, "RPL Control Message"},
    {0}
};


void
tcpxprotopr(const char *name)
{
    netsnmp_variable_list *var, *vp, *pvar;
    oid             tcpConnectionState_oid[] =
        { 1, 3, 6, 1, 2, 1, 6, 19, 1, 7 };
    size_t          tcpConnectionState_len =
        OID_LENGTH(tcpConnectionState_oid);
    int             state, i;
    struct sockaddr_in6 localAddr, remoteAddr;
    int             localPort, remotePort, pid = 0;
    int             localType, remoteType, inx;
    int             first = 1;
    static int      done = 0;

    if (done++)
        return;

    /*
     * Walking the v6 tcpConnectionState column will provide all
     *   the necessary information.
     */
    var = NULL;
    snmp_varlist_add_variable(&var, tcpConnectionState_oid,
                              tcpConnectionState_len, ASN_NULL, NULL, 0);
    if (netsnmp_query_walk(var, ss) != SNMP_ERR_NOERROR) {
        snmp_free_varbind(var);
        return;
    }
    if ((var->type & 0xF0) == 0x80) {   /* Exception */
        snmp_free_varbind(var);
        return;
    }

    for (vp = var; vp; vp = vp->next_variable) {
        char            lname[5];
        state = *vp->val.integer;
        inx = tcpConnectionState_len;
        pvar = NULL;

        vp->name[inx - 1] = 8;
        snmp_varlist_add_variable(&pvar, vp->name, vp->name_length,
                                  ASN_NULL, NULL, 0);
        if (netsnmp_query_get(pvar, ss) != SNMP_ERR_NOERROR) {
            snmp_free_var(pvar);
            return;
        }
        if ((pvar->type & 0xF0) != 0x80)        /* Exception */
            pid = *pvar->val.integer;

        /*
         * Extract the local/remote information from the index values
         */
        localType = vp->name[inx++];
        for (i = 0; i < vp->name[inx]; i++)
            localAddr.sin6_addr.s6_addr[i] = vp->name[inx + i + 1];
        inx += vp->name[inx] + 1;
        localPort = vp->name[inx++];
        remoteType = vp->name[inx++];
        for (i = 0; i < vp->name[inx]; i++)
            remoteAddr.sin6_addr.s6_addr[i] = vp->name[inx + i + 1];
        inx += vp->name[inx] + 1;
        remotePort = vp->name[inx++];

        snmp_free_varbind(pvar);

        if (af == AF_INET && localType == 2)
            continue;
        if (af == AF_INET6 && localType != 2)
            continue;

        if (first) {
            printf("Active Internet (%s) Connections", "tcp");
            putchar('\n');
            printf("%-5.5s %-27.27s %-27.27s %11.11s %5.5s\n",
                   "Proto", "Local Address", "Remote Address", "State",
                   "PID");
            first = 0;
        }

        strcpy(lname, "tcp");
        if (localType == 2)
            lname[3] = '6';
        else
            lname[3] = '4';
        lname[4] = 0;
        printf("%-5.5s", lname);
        inetxprint(localType, localAddr, localPort, "tcp", 1);
        inetxprint(remoteType, remoteAddr, remotePort, "tcp", 0);
        if (state < 1 || state > TCP_NSTATES)
            printf(" %11d %5d\n", state, pid);
        else
            printf(" %11s %5d\n", tcpxstates[state], pid);
    }
    snmp_free_varbind(var);

    if (aflag)
        listenxprotopr(name);
}

/*
 * Print a summary of listening "connections"
 */
void
listenxprotopr(const char *name)
{
    netsnmp_variable_list *var, *vp;
    oid             tcpListenerProcess_oid[] =
        { 1, 3, 6, 1, 2, 1, 6, 20, 1, 4 };
    size_t          tcpListenerProcess_len =
        OID_LENGTH(tcpListenerProcess_oid);
    struct sockaddr_in6 localAddr;
    int             localType, localPort, pid;
    int             i, inx;

    /*
     * Walking a single column of the udpTable will provide
     *   all the necessary information from the index values.
     */
    var = NULL;
    snmp_varlist_add_variable(&var, tcpListenerProcess_oid,
                              tcpListenerProcess_len, ASN_NULL, NULL, 0);
    if (netsnmp_query_walk(var, ss) != SNMP_ERR_NOERROR)
        return;
    if ((var->type & 0xF0) == 0x80)     /* Exception */
        return;

    printf("Listening Internet (%s) Connections\n", "tcp");
    printf("%-5.5s %-27.27s %5s\n", "Proto", "Local Address", "PID");
    for (vp = var; vp; vp = vp->next_variable) {
        char            lname[5];
        inx = tcpListenerProcess_len;
        /*
         * Extract the local port from the index values, but take
         *   the IP address from the varbind value, (which is why
         *   we walked udpLocalAddress rather than udpLocalPort)
         */
        localType = vp->name[inx++];
        if (af == AF_INET && localType == 2)
            continue;
        if (af == AF_INET6 && localType != 2)
            continue;

        for (i = 0; i < vp->name[inx]; i++)
            localAddr.sin6_addr.s6_addr[i] = vp->name[inx + i + 1];
        inx += vp->name[inx] + 1;
        localPort = vp->name[inx++];
        pid = *vp->val.integer;
        strcpy(lname, "tcp");
        if (localType == 2)
            lname[3] = '6';
        else
            lname[3] = '4';
        lname[4] = 0;
        printf("%-5.5s", lname);
        inetxprint(localType, localAddr, localPort, "tcp", 1);
        printf(" %5d\n", pid);
    }
    snmp_free_varbind(var);
}

/*
 * Print a summary of UDPv6 "connections"
 *    XXX - what about "listening" services ??
 */
void
udpxprotopr(const char *name)
{
    netsnmp_variable_list *var, *vp;
    oid             udpEndpointProcess_oid[] =
        { 1, 3, 6, 1, 2, 1, 7, 7, 1, 8 };
    size_t          udpEndpointProcess_len =
        OID_LENGTH(udpEndpointProcess_oid);
    struct sockaddr_in6 localAddr, remoteAddr;
    int             localType, remoteType, localPort, remotePort, pid;
    int             i, inx;
    static int      done = 0;

    if (done++)
        return;

    /*
     * Walking a single column of the udpTable will provide
     *   all the necessary information from the index values.
     */
    var = NULL;
    snmp_varlist_add_variable(&var, udpEndpointProcess_oid,
                              udpEndpointProcess_len, ASN_NULL, NULL, 0);
    if (netsnmp_query_walk(var, ss) != SNMP_ERR_NOERROR) {
        snmp_free_varbind(var);
        return;
    }
    if ((var->type & 0xF0) == 0x80) {   /* Exception */
        snmp_free_varbind(var);
        return;
    }

    printf("Active Internet (%s) Connections\n", "udp");
    printf("%-5.5s %-27.27s %-27.27s %5s\n", "Proto", "Local Address",
           "Remote Address", "PID");
    for (vp = var; vp; vp = vp->next_variable) {
        char            lname[5];
        inx = udpEndpointProcess_len;
        /*
         * Extract the local port from the index values, but take
         *   the IP address from the varbind value, (which is why
         *   we walked udpLocalAddress rather than udpLocalPort)
         */
        localType = vp->name[inx++];
        if (af == AF_INET && localType == 2)
            continue;
        if (af == AF_INET6 && localType != 2)
            continue;
        for (i = 0; i < vp->name[inx]; i++)
            localAddr.sin6_addr.s6_addr[i] = vp->name[inx + i + 1];
        inx += vp->name[inx] + 1;
        localPort = vp->name[inx++];
        remoteType = vp->name[inx++];
        for (i = 0; i < vp->name[inx]; i++)
            remoteAddr.sin6_addr.s6_addr[i] = vp->name[inx + i + 1];
        inx += vp->name[inx] + 1;
        remotePort = vp->name[inx++];
        pid = *vp->val.integer;
        strcpy(lname, "udp");
        if (localType == 2)
            lname[3] = '6';
        else
            lname[3] = '4';
        lname[4] = 0;
        printf("%-5.5s", lname);
        inetxprint(localType, localAddr, localPort, "udp", 1);
        inetxprint(remoteType, remoteAddr, remotePort, "udp", 1);
        printf(" %5d\n", pid);
    }
    snmp_free_varbind(var);
}

static void
statsprint(const char *name, const systemstats_t * st, int proto,
           const oid * tbl, size_t tbllen)
{
    oid             var[32];
    size_t          len;
    netsnmp_variable_list *vb;

    memcpy(var, tbl, tbllen * sizeof(oid));
    var[tbllen + 1] = proto;
    len = tbllen + 2;

    printf("%s:\n", name);
    while (st->stat) {
        vb = NULL;
        if (st->hcstat) {
            var[tbllen] = st->hcstat;
            snmp_varlist_add_variable(&vb, var, len, ASN_NULL, NULL, 0);
            if (netsnmp_query_get(vb, ss) != SNMP_ERR_NOERROR) {
                snmp_free_var(vb);
                vb = NULL;
            }
        }
        if (!vb) {
            var[tbllen] = st->stat;
            snmp_varlist_add_variable(&vb, var, len, ASN_NULL, NULL, 0);
            if (netsnmp_query_get(vb, ss) != SNMP_ERR_NOERROR) {
                snmp_free_var(vb);
                vb = NULL;
            }
        }
        if (vb) {
            if (vb->type == ASN_COUNTER) {
                if (*vb->val.integer > 0 || sflag == 1)
                    printf("%14lu %s\n", *vb->val.integer, st->str);
            } else if (vb->type == ASN_COUNTER64) {
                char            a64buf[I64CHARSZ + 1];
                printU64(a64buf, vb->val.counter64);
                if (strcmp(a64buf, "0") != 0 || sflag == 1)
                    printf("%14s %s\n", a64buf, st->str);
            } else
                printf("%14s %s\n", "-", st->str);
            snmp_free_varbind(vb);
        } else {
            printf("%14s %s\n", "-", st->str);
        }
        st++;
    }
}

static void
prhisto(const char *name, const oid * var, size_t len, int ver,
        codelist_t * cs)
{
    netsnmp_variable_list *vb = NULL, *vp;
    codelist_t     *cp;
    int             code;
    char            nocode[32];

    snmp_varlist_add_variable(&vb, var, len, ASN_NULL, NULL, 0);
    if (netsnmp_query_walk(vb, ss) != SNMP_ERR_NOERROR) {
        snmp_free_var(vb);
        return;
    }
    printf("     %s histogram:\n", name);
    printf("     %10s %10s %s\n", "input", "output", "type");
    for (code = 0; code < 256; code++) {
        unsigned long   inp = 0, out = 0;
        int             found = 0;
        vp = vb;
        while (vp && found != 2) {
            if (vp->name[11] == code && vp->name[10] == ver) {
                if (vp->name[9] == 3)
                    inp = *vp->val.integer;
                else
                    out = *vp->val.integer;
                found++;
            }
            vp = vp->next_variable;
        }
        if (found) {
            cp = cs;
            while (cp->name && cp->code != code)
                cp++;
            if (inp || out || sflag == 1) {
                if (!cp->code)
                    snprintf(nocode, sizeof nocode, "type %d", code);
                printf("     %10lu %10lu %s\n", inp, out,
                       cp->name ? cp->name : nocode);
            }
        }
    }
    snmp_free_varbind(vb);
}

void
ipx_stats(const char *name)
{
    oid             ipsysstat_oid[] = { 1, 3, 6, 1, 2, 1, 4, 31, 1, 1 };
    size_t          ipsysstat_len =
        sizeof(ipsysstat_oid) / sizeof(ipsysstat_oid[0]);
    static int      first = 1;

    if (!first)
        return;
    first = 0;

    if (!name || strcmp(name, "ip") == 0)
        statsprint("ip", systemstats, 1, ipsysstat_oid, ipsysstat_len);
    if (!name || strcmp(name, "ip6") == 0)
        statsprint("ip6", systemstats, 2, ipsysstat_oid, ipsysstat_len);
}

void
icmpx_stats(const char *name)
{
    oid             icmpstat_oid[] = { 1, 3, 6, 1, 2, 1, 5, 29, 1 };
    size_t          icmpstat_len =
        sizeof(icmpstat_oid) / sizeof(icmpstat_oid[0]);
    oid             icmpmsg_oid[] = { 1, 3, 6, 1, 2, 1, 5, 30, 1 };
    size_t          icmpmsg_len =
        sizeof(icmpmsg_oid) / sizeof(icmpmsg_oid[0]);
    static int      first = 1;

    if (!first)
        return;
    first = 0;

    if (!name || strcmp(name, "icmp") == 0) {
        statsprint("icmp", icmpstats, 1, icmpstat_oid, icmpstat_len);
        prhisto("icmp", icmpmsg_oid, icmpmsg_len, 1, icmpcodes);
    }
    if (!name || strcmp(name, "icmp6") == 0) {
        statsprint("icmp6", icmpstats, 2, icmpstat_oid, icmpstat_len);
        prhisto("icmp6", icmpmsg_oid, icmpmsg_len, 2, icmp6codes);
    }
}


static void
unknownprint(void)
{
    char            line[80], *cp;
    int             width = 27;

    if (vflag)
        snprintf(line, sizeof line, "%s.", "*");
    else
        snprintf(line, sizeof line, "%.*s.", width - 9, "*");
    cp = strchr(line, '\0');
    snprintf(cp, line + sizeof line - cp, vflag ? "%s" : "%.8s", "*");
    if (vflag && width < strlen(line))
        width = strlen(line);
    printf(" %-*.*s", width, width, line);
}

/*
 * Pretty print an Internet address (net address + port).
 * If the nflag was specified, use numbers instead of names.
 */

void
inetxprint(int proto, struct sockaddr_in6 in6, int port, const char *name,
           int local)
{

    if (proto == 2)
        inet6print(&in6.sin6_addr, port, name, local);
    else if (proto == 1)
        inetprint((struct in_addr *) &in6.sin6_addr.s6_addr, port, name,
                  local);
    else if (proto == 0)
        unknownprint();
    else
        abort();
}
