/*
 *  UDP MIB group Table implementation - udpTable.c
 *
 */

/* Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/*
 * Portions of this file are copyrighted by:
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */

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

#if HAVE_NETINET_UDP_H
#include <netinet/udp.h>
#endif
#if HAVE_NETINET_UDP_VAR_H
#include <netinet/udp_var.h>
#endif

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

#ifdef linux
#include "tcpTable.h"
#endif
#include "udp.h"
#include "udpTable.h"
#include "sysORTable.h"

#ifdef hpux11
#define	UDPTABLE_ENTRY_TYPE	mib_udpLsnEnt 
#define	UDPTABLE_LOCALADDRESS	LocalAddress 
#define	UDPTABLE_LOCALPORT	LocalPort 
#define	UDPTABLE_IS_TABLE
#else

#ifdef solaris2
typedef struct netsnmp_udpEntry_s netsnmp_udpEntry;
struct netsnmp_udpEntry_s {
    mib2_udpEntry_t   entry;
    netsnmp_udpEntry *inp_next;
};
#define	UDPTABLE_ENTRY_TYPE	netsnmp_udpEntry
#define	UDPTABLE_LOCALADDRESS	entry.udpLocalAddress 
#define	UDPTABLE_LOCALPORT	entry.udpLocalPort 
#define	UDPTABLE_IS_LINKED_LIST
#else

#ifdef WIN32
#include <iphlpapi.h>
#define	UDPTABLE_ENTRY_TYPE	MIB_UDPROW		/* ??? */
#define	UDPTABLE_LOCALADDRESS	dwLocalAddr
#define	UDPTABLE_LOCALPORT	dwLocalPort 
#define	UDPTABLE_IS_TABLE
#else			/* everything else */

#ifdef linux
#define INP_NEXT_SYMBOL		inp_next
#endif

#if defined(freebsd4) || defined(darwin)
typedef struct netsnmp_inpcb_s netsnmp_inpcb;
struct netsnmp_inpcb_s {
    struct inpcb    pcb;
    int             state;
    netsnmp_inpcb  *inp_next;
};
#define	UDPTABLE_ENTRY_TYPE	netsnmp_inpcb 
#define	UDPTABLE_LOCALADDRESS	pcb.inp_laddr.s_addr 
#define	UDPTABLE_LOCALPORT	pcb.inp_lport
#else
#define	UDPTABLE_ENTRY_TYPE	struct inpcb 
#define	UDPTABLE_LOCALADDRESS	inp_laddr.s_addr 
#define	UDPTABLE_LOCALPORT	inp_lport
#endif
#define	UDPTABLE_IS_LINKED_LIST

#endif                          /* WIN32 */
#endif                          /* solaris2 */
#endif                          /* hpux11 */

				/* Head of linked list, or root of table */
UDPTABLE_ENTRY_TYPE	*udp_head  = NULL;
int                      udp_size  = 0;	/* Only used for table-based systems */


	/*
	 *
	 * Initialization and handler routines are common to all architectures
	 *
	 */
#ifndef MIB_STATS_CACHE_TIMEOUT
#define MIB_STATS_CACHE_TIMEOUT	5
#endif
#ifndef UDP_STATS_CACHE_TIMEOUT
#define UDP_STATS_CACHE_TIMEOUT	MIB_STATS_CACHE_TIMEOUT
#endif

oid             udpTable_oid[] = { SNMP_OID_MIB2, 7, 5 };

void
init_udpTable(void)
{
    netsnmp_table_registration_info *table_info;
    netsnmp_iterator_info           *iinfo;
    netsnmp_handler_registration    *reginfo;

    DEBUGMSGTL(("mibII/udpTable", "Initialising UDP Table\n"));
    /*
     * Create the table data structure, and define the indexing....
     */
    table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
    if (!table_info) {
        return;
    }
    netsnmp_table_helper_add_indexes(table_info, ASN_IPADDRESS,
                                                 ASN_INTEGER, 0);
    table_info->min_column = UDPLOCALADDRESS;
    table_info->max_column = UDPLOCALPORT;


    /*
     * .... and iteration information ....
     */
    iinfo      = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
    if (!iinfo) {
        return;
    }
    iinfo->get_first_data_point = udpTable_first_entry;
    iinfo->get_next_data_point  = udpTable_next_entry;
    iinfo->table_reginfo        = table_info;
#if defined(WIN32) || defined(solaris2)
    iinfo->flags               |= NETSNMP_ITERATOR_FLAG_SORTED;
#endif /* WIN32 || solaris2 */


    /*
     * .... and register the table with the agent.
     */
    reginfo = netsnmp_create_handler_registration("udpTable",
            udpTable_handler,
            udpTable_oid, OID_LENGTH(udpTable_oid),
            HANDLER_CAN_RONLY),
    netsnmp_register_table_iterator(reginfo, iinfo);

    /*
     * .... with a local cache
     */
    netsnmp_inject_handler( reginfo,
		    netsnmp_get_cache_handler(UDP_STATS_CACHE_TIMEOUT,
			   		udpTable_load, udpTable_free,
					udpTable_oid, OID_LENGTH(udpTable_oid)));
}



int
udpTable_handler(netsnmp_mib_handler          *handler,
                 netsnmp_handler_registration *reginfo,
                 netsnmp_agent_request_info   *reqinfo,
                 netsnmp_request_info         *requests)
{
    netsnmp_request_info  *request;
    netsnmp_variable_list *requestvb;
    netsnmp_table_request_info *table_info;
    UDPTABLE_ENTRY_TYPE	  *entry;
    oid      subid;
    long     port;

    DEBUGMSGTL(("mibII/udpTable", "Handler - mode %s\n",
                    se_find_label_in_slist("agent_mode", reqinfo->mode)));
    switch (reqinfo->mode) {
    case MODE_GET:
        for (request=requests; request; request=request->next) {
            requestvb = request->requestvb;
            DEBUGMSGTL(( "mibII/udpTable", "oid: "));
            DEBUGMSGOID(("mibII/udpTable", requestvb->name,
                                           requestvb->name_length));
            DEBUGMSG((   "mibII/udpTable", "\n"));

            entry = (UDPTABLE_ENTRY_TYPE *)netsnmp_extract_iterator_context(request);
            if (!entry)
                continue;
            table_info = netsnmp_extract_table_info(request);
            subid      = table_info->colnum;

            switch (subid) {
            case UDPLOCALADDRESS:
#if defined(osf5) && defined(IN6_EXTRACT_V4ADDR)
	        snmp_set_var_typed_value(requestvb, ASN_IPADDRESS,
                              (u_char*)IN6_EXTRACT_V4ADDR(pcb->inp_laddr),
                                sizeof(IN6_EXTRACT_V4ADDR(pcb->inp_laddr)));
#else
	        snmp_set_var_typed_value(requestvb, ASN_IPADDRESS,
                                 (u_char *)&entry->UDPTABLE_LOCALADDRESS,
                                     sizeof(entry->UDPTABLE_LOCALADDRESS));
#endif
                break;
            case UDPLOCALPORT:
                port = ntohs((u_short)entry->UDPTABLE_LOCALPORT);
	        snmp_set_var_typed_value(requestvb, ASN_INTEGER,
                                 (u_char *)&port, sizeof(port));
                break;
	    }
	}
        break;

    case MODE_GETNEXT:
    case MODE_GETBULK:
    case MODE_SET_RESERVE1:
    case MODE_SET_RESERVE2:
    case MODE_SET_ACTION:
    case MODE_SET_COMMIT:
    case MODE_SET_FREE:
    case MODE_SET_UNDO:
        snmp_log(LOG_WARNING, "mibII/udpTable: Unsupported mode (%d)\n",
                               reqinfo->mode);
        break;
    default:
        snmp_log(LOG_WARNING, "mibII/udpTable: Unrecognised mode (%d)\n",
                               reqinfo->mode);
        break;
    }

    return SNMP_ERR_NOERROR;
}

	/*
	 * Two forms of iteration hook routines:
	 *    One for when the UDP table is stored as a table
	 *    One for when the UDP table is stored as a linked list
	 *
	 * Also applies to the cache-handler free routine
	 */

#ifdef	UDPTABLE_IS_TABLE
netsnmp_variable_list *
udpTable_first_entry(void **loop_context,
                     void **data_context,
                     netsnmp_variable_list *index,
                     netsnmp_iterator_info *data)
{
    /*
     * XXX - How can we tell if the cache is valid?
     *       No access to 'reqinfo'
     */
    if (udp_size == 0)
        return NULL;

    /*
     * Point to the first entry, and use the
     * 'next_entry' hook to retrieve this row
     */
    *loop_context = 0;
    return udpTable_next_entry( loop_context, data_context, index, data );
}

netsnmp_variable_list *
udpTable_next_entry( void **loop_context,
                     void **data_context,
                     netsnmp_variable_list *index,
                     netsnmp_iterator_info *data)
{
    int i = (int)*loop_context;
    long port;

    if (udp_size < i)
        return NULL;

    /*
     * Set up the indexing for the specified row...
     */
#ifdef WIN32
    port = ntohl((u_long)udp_head[i].UDPTABLE_LOCALADDRESS);
    snmp_set_var_value(index, (u_char *)&port,
                                  sizeof(udp_head[i].UDPTABLE_LOCALADDRESS));
#else
    snmp_set_var_value(index, (u_char *)&udp_head[i].UDPTABLE_LOCALADDRESS,
                                  sizeof(udp_head[i].UDPTABLE_LOCALADDRESS));
#endif 
    port = ntohs((u_short)udp_head[i].UDPTABLE_LOCALPORT);
    snmp_set_var_value(index->next_variable,
                               (u_char*)&port, sizeof(port));
    /*
     * ... return the data structure for this row,
     * and update the loop context ready for the next one.
     */
    *data_context = (void*)&udp_head[i];
    *loop_context = (void*)++i;
    return index;
}

void
udpTable_free(netsnmp_cache *cache, void *magic)
{
#ifdef WIN32
    if (udp_head) {
		/* the allocated structure is a count followed by table entries */
		free((char *)(udp_head) - sizeof(DWORD));
	}
#else
    if (udp_head)
        free(udp_head);
#endif
    udp_head = NULL;
    udp_size = 0;
}
#else
#ifdef UDPTABLE_IS_LINKED_LIST
netsnmp_variable_list *
udpTable_first_entry(void **loop_context,
                     void **data_context,
                     netsnmp_variable_list *index,
                     netsnmp_iterator_info *data)
{
    /*
     * XXX - How can we tell if the cache is valid?
     *       No access to 'reqinfo'
     */
    if (udp_head == 0)
        return NULL;

    /*
     * Point to the first entry, and use the
     * 'next_entry' hook to retrieve this row
     */
    *loop_context = (void*)udp_head;
    return udpTable_next_entry( loop_context, data_context, index, data );
}

netsnmp_variable_list *
udpTable_next_entry( void **loop_context,
                     void **data_context,
                     netsnmp_variable_list *index,
                     netsnmp_iterator_info *data)
{
    UDPTABLE_ENTRY_TYPE	 *entry = (UDPTABLE_ENTRY_TYPE *)*loop_context;
    long port;

    if (!entry)
        return NULL;

    /*
     * Set up the indexing for the specified row...
     */
#if defined(osf5) && defined(IN6_EXTRACT_V4ADDR)
                snmp_set_var_value(index,
                              (u_char*)&IN6_EXTRACT_V4ADDR(entry->inp_laddr),
                                 sizeof(IN6_EXTRACT_V4ADDR(entry->inp_laddr)));
#else
    snmp_set_var_value(index, (u_char*)&entry->UDPTABLE_LOCALADDRESS,
                                 sizeof(entry->UDPTABLE_LOCALADDRESS));
#endif
    port = ntohs(entry->UDPTABLE_LOCALPORT);
    snmp_set_var_value(index->next_variable,
                               (u_char*)&port, sizeof(port));

    /*
     * ... return the data structure for this row,
     * and update the loop context ready for the next one.
     */
    *data_context = (void*)entry;
    *loop_context = (void*)entry->INP_NEXT_SYMBOL;
    return index;
}

void
udpTable_free(netsnmp_cache *cache, void *magic)
{
    UDPTABLE_ENTRY_TYPE	 *p;
    while (udp_head) {
        p = udp_head;
        udp_head = udp_head->INP_NEXT_SYMBOL;
        free(p);
    }

    udp_head = NULL;
}
#endif		/* UDPTABLE_IS_LINKED_LIST */
#endif		/* UDPTABLE_IS_TABLE */


	/*
	 *
	 * The cache-handler loading routine is the main
	 *    place for architecture-specific code
	 *
	 * Load into either a table structure, or a linked list
	 *    depending on the system architecture
	 */


#ifdef hpux11
int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    int             fd;
    struct nmparms  p;
    int             val = 0;
    unsigned int    ulen;
    int             ret;

    udpTable_free(NULL, NULL);

    if ((fd = open_mib("/dev/ip", O_RDONLY, 0, NM_ASYNC_OFF)) >= 0) {
        p.objid = ID_udpLsnNumEnt;
        p.buffer = (void *) &val;
        ulen = sizeof(int);
        p.len = &ulen;
        if ((ret = get_mib_info(fd, &p)) == 0)
            udp_size = val;

        if (udp_size > 0) {
            ulen = (unsigned) udp_size *sizeof(mib_udpLsnEnt);
            udp_head = (mib_udpLsnEnt *) malloc(ulen);
            p.objid = ID_udpLsnTable;
            p.buffer = (void *) udp_head;
            p.len = &ulen;
            if ((ret = get_mib_info(fd, &p)) < 0) {
                udp_size = 0;
            }
        }

        close_mib(fd);
    }

    if (udp_size > 0) {
        DEBUGMSGTL(("mibII/udpTable", "Loaded UDP Table\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (hpux11)\n"));
    return -1;
}
#else                           /* hpux11 */

#ifdef linux
int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    FILE           *in;
    char            line[256];

    udpTable_free(cache, NULL);

    if (!(in = fopen("/proc/net/udp", "r"))) {
        DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (linux1)\n"));
        snmp_log(LOG_ERR, "snmpd: cannot open /proc/net/udp ...\n");
        return -1;
    }

    /*
     * scan proc-file and build up a linked list 
     * This will actually be built up in reverse,
     *   but since the entries are unsorted, that doesn't matter.
     */
    while (line == fgets(line, sizeof(line), in)) {
        struct inpcb    pcb, *nnew;
        unsigned int    state, lport;

        if (3 != sscanf(line, "%*d: %x:%x %*x:%*x %x",
                        &pcb.inp_laddr.s_addr, &lport, &state))
            continue;

        if (state != 7)         /* fix me:  UDP_LISTEN ??? */
            continue;

        pcb.inp_lport = htons((unsigned short) (lport));
        pcb.inp_fport = htons(pcb.inp_fport);

        nnew = SNMP_MALLOC_TYPEDEF(struct inpcb);
        if (nnew == NULL)
            break;
        memcpy(nnew, &pcb, sizeof(struct inpcb));
        nnew->inp_next = udp_head;
        udp_head       = nnew;
    }

    fclose(in);

    DEBUGMSGTL(("mibII/udpTable", "Loaded UDP Table\n"));
    return 0;
}
#else                           /* linux */

#ifdef solaris2
static int
UDP_Cmp(void *addr, void *ep)
{
    if (memcmp((mib2_udpEntry_t *) ep, (mib2_udpEntry_t *) addr,
               sizeof(mib2_udpEntry_t)) == 0)
        return (0);
    else
        return (1);
}


int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    mib2_udpEntry_t   entry;
    netsnmp_udpEntry *nnew;
    netsnmp_udpEntry *prev_entry = NULL;


    udpTable_free(NULL, NULL);

    if (getMibstat(MIB_UDP_LISTEN, &entry, sizeof(mib2_udpEntry_t),
                   GET_FIRST, &UDP_Cmp, &entry) != 0) {
        DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (solaris)\n"));
        return -1;
    }

    while (1) {
        /*
         * Not interested in 'idle' entries, apparently....
         */
        if (entry.udpEntryInfo.ue_state != MIB2_UDP_idle) {
            /*
             * Build up a linked list copy of the getMibstat results
             * Note that since getMibstat returns rows in sorted order,
	     *    we need to retain this order while building the list
	     *    so new entries are added onto the end of the list.
             */
            nnew = SNMP_MALLOC_TYPEDEF(netsnmp_udpEntry);
            if (nnew == NULL)
                break;
            memcpy(&(nnew->entry), &entry, sizeof(mib2_udpEntry_t));
            if (!prev_entry)
	        udp_head = nnew;
	    else
	        prev_entry->inp_next = nnew;
	    prev_entry = nnew;
	}

        if (getMibstat(MIB_UDP_LISTEN, &entry, sizeof(mib2_udpEntry_t),
                       GET_NEXT, &UDP_Cmp, &entry) != 0)
	    break;
    }

    if (udp_head) {
        DEBUGMSGTL(("mibII/udpTable", "Loaded UDP Table\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (solaris)\n"));
    return -1;
}
#else                           /* solaris2 */

#ifdef WIN32
int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    PMIB_UDPTABLE pUdpTable = NULL;
    DWORD         dwActualSize = 0;
    DWORD         status = NO_ERROR;

    /*
     * query for the buffer size needed 
     */
    status = GetUdpTable(pUdpTable, &dwActualSize, TRUE);
    if (status == ERROR_INSUFFICIENT_BUFFER) {
        pUdpTable = (PMIB_UDPTABLE) malloc(dwActualSize);
        if (pUdpTable != NULL) {
            /*
             * Get the sorted UDP table 
             */
            status = GetUdpTable(pUdpTable, &dwActualSize, TRUE);
        }
    }
    if (status == NO_ERROR) {
        DEBUGMSGTL(("mibII/udpTable", "Loaded UDP Table\n"));
        udp_size = pUdpTable->dwNumEntries -1;  /* entries are counted starting with 0 */
        udp_head = pUdpTable->table;
        return 0;
    }
    DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (win32)\n"));
    if (pUdpTable)
	free(pUdpTable);
    return -1;
}
#else                           /* WIN32 */

#if (defined(CAN_USE_SYSCTL) && defined(UDPCTL_PCBLIST))
int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    size_t   len;
    int      sname[] = { CTL_NET, PF_INET, IPPROTO_UDP, UDPCTL_PCBLIST };
    char     *udpcb_buf = NULL;
    struct xinpgen *xig = NULL;
    UDPTABLE_ENTRY_TYPE  *nnew;

    udpTable_free(NULL, NULL);

    /*
     *  Read in the buffer containing the UDP table data
     */
    len = 0;
    if (sysctl(sname, 4, 0, &len, 0, 0) < 0 ||
       (udpcb_buf = malloc(len)) == NULL)
        return -1;
    if (sysctl(sname, 4, udpcb_buf, &len, 0, 0) < 0) {
        free(udpcb_buf);
        return -1;
    }

    /*
     *  Unpick this into the constituent 'xinpgen' structures, and extract
     *     the 'inpcb' elements into a linked list (built in reverse)
     */
    xig = (struct xinpgen *) udpcb_buf;
    xig = (struct xinpgen *) ((char *) xig + xig->xig_len);

    while (xig && (xig->xig_len > sizeof(struct xinpgen))) {
        nnew = SNMP_MALLOC_TYPEDEF(UDPTABLE_ENTRY_TYPE);
        if (!nnew)
            break;
#if defined(freebsd4) || defined(darwin)
        memcpy(nnew, &((struct xinpcb *) xig)->xi_inp, sizeof(struct inpcb));
	nnew->inp_next = udp_head;
#else
        memcpy(nnew, ((struct xinpcb *) xig)->xi_inp, sizeof(struct inpcb));
	nnew->next = udp_head;		/* XXX - ?? Check 'next' pointer */
#endif

	udp_head   = nnew;
        xig = (struct xinpgen *) ((char *) xig + xig->xig_len);
    }

    free(udpcb_buf);
    if (udp_head) {
        DEBUGMSGTL(("mibII/udpTable", "Loaded UDP Table\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (sysctl)\n"));
    return -1;
}
#else		/* (defined(CAN_USE_SYSCTL) && defined(UDPCTL_PCBLIST)) */
#ifdef PCB_TABLE
int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    struct inpcbtable table;
    struct inpcb   *nnew, *entry;

    udpTable_free(NULL, NULL);

    if (!auto_nlist(UDB_SYMBOL, (char *) &table, sizeof(table))) {
        DEBUGMSGTL(("mibII/udpTable", "Failed to read inpcbtable\n"));
        return -1;
    }

    /*
     *  Set up a linked list
     */
    entry  = table.inpt_queue.cqh_first;
    while (entry) {
   
        nnew = SNMP_MALLOC_TYPEDEF(struct inpcb);
        if (!nnew)
            break;
        klookup((unsigned long) entry, (char *) nnew, sizeof(struct inpcb));

        entry    = nnew->inp_queue.cqe_next;	/* Next kernel entry */
	nnew->inp_queue.cqe_next = udp_head;
	udp_head = nnew;

        if (entry == table.inpt_queue.cqh_first)
            break;
    }

    if (udp_head) {
        DEBUGMSGTL(("mibII/udpTable", "Loaded UDP Table\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (pcb_table)\n"));
    return -1;
}

#else				/* PCB_TABLE */
#ifdef UDB_SYMBOL
int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    struct inpcb   udp_inpcb;
    struct inpcb   *nnew, *entry;

    udpTable_free(NULL, NULL);

    if (!auto_nlist(UDB_SYMBOL, (char *) &udp_inpcb, sizeof(udp_inpcb))) {
        DEBUGMSGTL(("mibII/udpTable", "Failed to read udb_symbol\n"));
        return -1;
    }

    /*
     *  Set up a linked list
     */
    entry  = udp_inpcb.INP_NEXT_SYMBOL;
    while (entry) {
   
        nnew = SNMP_MALLOC_TYPEDEF(struct inpcb);
        if (!nnew)
            break;
        klookup((unsigned long) entry, (char *) nnew, sizeof(struct inpcb));

        entry    = nnew->INP_NEXT_SYMBOL;		/* Next kernel entry */
	nnew->INP_NEXT_SYMBOL = udp_head;
	udp_head = nnew;

        if (entry == udp_inpcb.INP_NEXT_SYMBOL)
            break;
    }

    if (udp_head) {
        DEBUGMSGTL(("mibII/udpTable", "Loaded UDP Table\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/udpTable", "Failed to load UDP Table (udb_symbol)\n"));
    return -1;
}

#else				/* UDB_SYMBOL */
int
udpTable_load(netsnmp_cache *cache, void *vmagic)
{
    DEBUGMSGTL(("mibII/udpTable", "Loading UDP Table not implemented\n"));
    return -1;
}
#endif				/* UDB_SYMBOL */
#endif				/* PCB_TABLE */
#endif		/* (defined(CAN_USE_SYSCTL) && defined(UDPCTL_PCBLIST)) */
#endif                          /* WIN32 */
#endif                          /* linux */
#endif                          /* solaris2 */
#endif                          /* hpux11 */
