/*
 *  TCP MIB group Table implementation - tcpTable.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 <net-snmp/net-snmp-features.h>
#include "mibII_common.h"

#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifdef HAVE_NETINET_TCP_TIMER_H
#include <netinet/tcp_timer.h>
#endif
#ifdef HAVE_NETINET_TCPIP_H
#include <netinet/tcpip.h>
#endif
#ifdef HAVE_NETINET_TCP_VAR_H
#include <netinet/tcp_var.h>
#endif
#ifdef HAVE_NETLINK_NETLINK_H
#include <netlink/netlink.h>
#include <netlink/msg.h>
#include <linux/inet_diag.h>
#endif

#ifdef HAVE_KVM_GETFILES
#if defined(HAVE_KVM_GETFILE2) || !defined(openbsd5)
#undef HAVE_KVM_GETFILES
#endif
#endif

#ifdef HAVE_KVM_GETFILES
#include <kvm.h>
#include <sys/sysctl.h>
#define _KERNEL
#include <sys/file.h>
#undef _KERNEL
#endif

#if defined(cygwin) || defined(mingw32)
#include <winerror.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>

#include "tcp.h"
#include "tcpTable.h"

netsnmp_feature_child_of(tcptable_all, libnetsnmpmibs);

netsnmp_feature_child_of(tcp_count_connections, tcptable_all);

#ifdef hpux11
#define	TCPTABLE_ENTRY_TYPE	mib_tcpConnEnt 
#define	TCPTABLE_STATE		State 
#define	TCPTABLE_LOCALADDRESS	LocalAddress 
#define	TCPTABLE_LOCALPORT	LocalPort 
#define	TCPTABLE_REMOTEADDRESS	RemAddress 
#define	TCPTABLE_REMOTEPORT	RemPort 
#define	TCPTABLE_IS_TABLE

#elif defined(solaris2)
typedef struct netsnmp_tcpConnEntry_s netsnmp_tcpConnEntry;
struct netsnmp_tcpConnEntry_s {
    mib2_tcpConnEntry_t   entry;
    netsnmp_tcpConnEntry *inp_next;
};
#define	TCPTABLE_ENTRY_TYPE	netsnmp_tcpConnEntry
#define	TCPTABLE_STATE		entry.tcpConnState 
#define	TCPTABLE_LOCALADDRESS	entry.tcpConnLocalAddress 
#define	TCPTABLE_LOCALPORT	entry.tcpConnLocalPort 
#define	TCPTABLE_REMOTEADDRESS	entry.tcpConnRemAddress 
#define	TCPTABLE_REMOTEPORT	entry.tcpConnRemPort 
#define	TCPTABLE_IS_LINKED_LIST

#elif defined(HAVE_IPHLPAPI_H)
#include <iphlpapi.h>
#define	TCPTABLE_ENTRY_TYPE	MIB_TCPROW
#define	TCPTABLE_STATE		dwState 
#define	TCPTABLE_LOCALADDRESS	dwLocalAddr
#define	TCPTABLE_LOCALPORT	dwLocalPort 
#define	TCPTABLE_REMOTEADDRESS	dwRemoteAddr 
#define	TCPTABLE_REMOTEPORT	dwRemotePort 
#define	TCPTABLE_IS_TABLE

#elif defined(linux)
#define	TCPTABLE_ENTRY_TYPE	struct inpcb 
#define	TCPTABLE_STATE		inp_state 
#define	TCPTABLE_LOCALADDRESS	inp_laddr.s_addr 
#define	TCPTABLE_LOCALPORT	inp_lport
#define	TCPTABLE_REMOTEADDRESS	inp_faddr.s_addr 
#define	TCPTABLE_REMOTEPORT	inp_fport
#define	TCPTABLE_IS_LINKED_LIST

#elif defined(HAVE_KVM_GETFILES)
#define	TCPTABLE_ENTRY_TYPE	struct kinfo_file
#define	TCPTABLE_STATE		t_state 
#define	TCPTABLE_LOCALADDRESS	inp_laddru[0]
#define	TCPTABLE_LOCALPORT	inp_lport
#define	TCPTABLE_REMOTEADDRESS	inp_faddru[0]
#define	TCPTABLE_REMOTEPORT	inp_fport
#define	TCPTABLE_IS_TABLE

#else			/* everything else */

typedef struct netsnmp_inpcb_s netsnmp_inpcb;
struct netsnmp_inpcb_s {
#if __FreeBSD_version >= 1200026
    struct xinpcb   pcb;
#elif defined(__NetBSD__) && __NetBSD_Version__ >= 999010400
    struct in4pcb    pcb;
#else
    struct inpcb    pcb;
#endif
    int             state;
    netsnmp_inpcb  *inp_next;
};
#undef INP_NEXT_SYMBOL
#define INP_NEXT_SYMBOL		inp_next
#define	TCPTABLE_ENTRY_TYPE	netsnmp_inpcb 
#define	TCPTABLE_STATE		state 
#define	TCPTABLE_IS_LINKED_LIST
#define INP_NEXT_SYMBOL		inp_next
#if defined(__NetBSD__) && __NetBSD_Version__ >= 999010400
#define	TCPTABLE_LOCALADDRESS	pcb.in4p_ip.ip_src.s_addr 
#define	TCPTABLE_LOCALPORT	pcb.in4p_pcb.inp_lport
#define	TCPTABLE_REMOTEADDRESS	pcb.in4p_ip.ip_dst.s_addr 
#define	TCPTABLE_REMOTEPORT	pcb.in4p_pcb.inp_fport
#define	TCPTABLE_TCPCB          pcb.in4p_pcb.inp_ppcb
#else
#define	TCPTABLE_LOCALADDRESS	pcb.inp_laddr.s_addr 
#define	TCPTABLE_LOCALPORT	pcb.inp_lport
#define	TCPTABLE_REMOTEADDRESS	pcb.inp_faddr.s_addr 
#define	TCPTABLE_REMOTEPORT	pcb.inp_fport
#define TCPTABLE_TCPCB		pcb.inp_ppcb
#endif

#endif                          /* hpux11 */

				/* Head of linked list, or root of table */
TCPTABLE_ENTRY_TYPE	*tcp_head  = NULL;
int                      tcp_size  = 0;	/* Only used for table-based systems */
int                      tcp_estab = 0;


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

#if defined(TCP_PORTS_IN_HOST_ORDER) && TCP_PORTS_IN_HOST_ORDER
#define TCP_PORT_TO_HOST_ORDER(x) x
#else
#define TCP_PORT_TO_HOST_ORDER(x) ntohs(x)
#endif

void
init_tcpTable(void)
{
    const oid tcpTable_oid[] = { SNMP_OID_MIB2, 6, 13 };

    netsnmp_table_registration_info *table_info;
    netsnmp_iterator_info           *iinfo;
    netsnmp_handler_registration    *reginfo;
    int                              rc;

    DEBUGMSGTL(("mibII/tcpTable", "Initialising TCP 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,
                                                 ASN_IPADDRESS,
                                                 ASN_INTEGER, 0);
    table_info->min_column = TCPCONNSTATE;
    table_info->max_column = TCPCONNREMOTEPORT;


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


    /*
     * .... and register the table with the agent.
     */
    reginfo = netsnmp_create_handler_registration("tcpTable",
            tcpTable_handler,
            tcpTable_oid, OID_LENGTH(tcpTable_oid),
            HANDLER_CAN_RONLY),
    rc = netsnmp_register_table_iterator2(reginfo, iinfo);
    if (rc != SNMPERR_SUCCESS)
        return;

    /*
     * .... with a local cache
     *    (except for Solaris, which uses a different approach)
     */
    netsnmp_inject_handler( reginfo,
		    netsnmp_get_cache_handler(TCP_STATS_CACHE_TIMEOUT,
			   		tcpTable_load, tcpTable_free,
					tcpTable_oid, OID_LENGTH(tcpTable_oid)));
}



int
tcpTable_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;
    TCPTABLE_ENTRY_TYPE	  *entry;
#ifdef HAVE_KVM_GETFILES
    int      StateMap[] = { 1, 2, 3, 4, 5, 8, 6, 10, 9, 7, 11 };
#endif
    oid      subid;
    long     port;
    long     state;

    DEBUGMSGTL(("mibII/tcpTable", "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/tcpTable", "oid: "));
            DEBUGMSGOID(("mibII/tcpTable", requestvb->name,
                                           requestvb->name_length));
            DEBUGMSG((   "mibII/tcpTable", "\n"));

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

            switch (subid) {
            case TCPCONNSTATE:
#ifdef HAVE_KVM_GETFILES
                state = StateMap[entry->TCPTABLE_STATE];
#else
                state = entry->TCPTABLE_STATE;
#endif
	        snmp_set_var_typed_value(requestvb, ASN_INTEGER,
                                 (u_char *)&state, sizeof(state));
                break;
            case TCPCONNLOCALADDRESS:
#if defined(osf5) && defined(IN6_EXTRACT_V4ADDR)
	        snmp_set_var_typed_value(requestvb, ASN_IPADDRESS,
                              (u_char*)IN6_EXTRACT_V4ADDR(&entry->pcb.inp_laddr),
                                sizeof(IN6_EXTRACT_V4ADDR(&entry->pcb.inp_laddr)));
#else
	        snmp_set_var_typed_value(requestvb, ASN_IPADDRESS,
                                 (u_char *)&entry->TCPTABLE_LOCALADDRESS,
                                     sizeof(entry->TCPTABLE_LOCALADDRESS));
#endif
                break;
            case TCPCONNLOCALPORT:
                port = TCP_PORT_TO_HOST_ORDER((u_short)entry->TCPTABLE_LOCALPORT);
	        snmp_set_var_typed_value(requestvb, ASN_INTEGER,
                                 (u_char *)&port, sizeof(port));
                break;
            case TCPCONNREMOTEADDRESS:
#if defined(osf5) && defined(IN6_EXTRACT_V4ADDR)
	        snmp_set_var_typed_value(requestvb, ASN_IPADDRESS,
                              (u_char*)IN6_EXTRACT_V4ADDR(&entry->pcb.inp_laddr),
                                sizeof(IN6_EXTRACT_V4ADDR(&entry->pcb.inp_laddr)));
#else
	        snmp_set_var_typed_value(requestvb, ASN_IPADDRESS,
                                 (u_char *)&entry->TCPTABLE_REMOTEADDRESS,
                                     sizeof(entry->TCPTABLE_REMOTEADDRESS));
#endif
                break;
            case TCPCONNREMOTEPORT:
                port = TCP_PORT_TO_HOST_ORDER((u_short)entry->TCPTABLE_REMOTEPORT);
	        snmp_set_var_typed_value(requestvb, ASN_INTEGER,
                                 (u_char *)&port, sizeof(port));
                break;
	    }
	}
        break;

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

    return SNMP_ERR_NOERROR;
}

#ifndef NETSNMP_FEATURE_REMOVE_TCP_COUNT_CONNECTIONS
int
TCP_Count_Connections( void ) {
#if (defined(CAN_USE_SYSCTL) && defined(TCPCTL_PCBLIST))
    tcpTable_load(NULL, NULL);
#endif
    return tcp_estab;
}
#endif /* NETSNMP_FEATURE_REMOVE_TCP_COUNT_CONNECTIONS */

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

#ifdef	TCPTABLE_IS_TABLE
netsnmp_variable_list *
tcpTable_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 (tcp_size == 0)
        return NULL;

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

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

#ifdef HAVE_KVM_GETFILES
    while (i < tcp_size && (tcp_head[i].so_protocol != IPPROTO_TCP
	    || tcp_head[i].so_family != AF_INET))
	i++;
#endif
    if (tcp_size < i)
        return NULL;

    /*
     * Set up the indexing for the specified row...
     */
    idx = index;
#if defined (WIN32) || defined (cygwin) || defined(openbsd5)
    port = ntohl((u_long)tcp_head[i].TCPTABLE_LOCALADDRESS);
    snmp_set_var_value(idx, (u_char *)&port,
                                sizeof(tcp_head[i].TCPTABLE_LOCALADDRESS));
#else
    snmp_set_var_value(idx, (u_char *)&tcp_head[i].TCPTABLE_LOCALADDRESS,
                                sizeof(tcp_head[i].TCPTABLE_LOCALADDRESS));
#endif

    port = TCP_PORT_TO_HOST_ORDER((u_short)tcp_head[i].TCPTABLE_LOCALPORT);
    idx = idx->next_variable;
    snmp_set_var_value(idx, (u_char*)&port, sizeof(port));

    idx = idx->next_variable;
#if defined (WIN32) || defined (cygwin) || defined(openbsd5)
    port = ntohl((u_long)tcp_head[i].TCPTABLE_REMOTEADDRESS);
    snmp_set_var_value(idx, (u_char *)&port,
                                sizeof(tcp_head[i].TCPTABLE_REMOTEADDRESS));
#else
    snmp_set_var_value(idx, (u_char *)&tcp_head[i].TCPTABLE_REMOTEADDRESS,
                                sizeof(tcp_head[i].TCPTABLE_REMOTEADDRESS));
#endif

    port = TCP_PORT_TO_HOST_ORDER((u_short)tcp_head[i].TCPTABLE_REMOTEPORT);
    idx = idx->next_variable;
    snmp_set_var_value(idx, (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*)&tcp_head[i];
    *loop_context = (void*)(intptr_t)++i;

    return index;
}

void
tcpTable_free(netsnmp_cache *cache, void *magic)
{
#if defined (WIN32) || defined (cygwin)
    if (tcp_head) {
		/* the allocated structure is a count followed by table entries */
		free((char *)(tcp_head) - sizeof(DWORD));
	}
#elif defined(openbsd5)
#else
    if (tcp_head)
        free(tcp_head);
#endif
    tcp_head  = NULL;
    tcp_size  = 0;
    tcp_estab = 0;
}
#else
#ifdef TCPTABLE_IS_LINKED_LIST
netsnmp_variable_list *
tcpTable_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 (tcp_head == NULL)
        return NULL;

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

netsnmp_variable_list *
tcpTable_next_entry( void **loop_context,
                     void **data_context,
                     netsnmp_variable_list *index,
                     netsnmp_iterator_info *data)
{
    TCPTABLE_ENTRY_TYPE	 *entry = (TCPTABLE_ENTRY_TYPE *)*loop_context;
    netsnmp_variable_list *idx;
    long addr, port;

    if (!entry)
        return NULL;

    /*
     * Set up the indexing for the specified row...
     */
    idx = index;
#if defined(osf5) && defined(IN6_EXTRACT_V4ADDR)
    addr = ntohl(IN6_EXTRACT_V4ADDR(&entry->pcb.inp_laddr));
#else
    addr = ntohl(entry->TCPTABLE_LOCALADDRESS);
#endif
    snmp_set_var_value(idx, (u_char *)&addr, sizeof(addr));

    port = TCP_PORT_TO_HOST_ORDER(entry->TCPTABLE_LOCALPORT);
    idx = idx->next_variable;
    snmp_set_var_value(idx, (u_char*)&port, sizeof(port));

    idx = idx->next_variable;
#if defined(osf5) && defined(IN6_EXTRACT_V4ADDR)
    addr = ntohl(IN6_EXTRACT_V4ADDR(&entry->pcb.inp_faddr));
#else
    addr = ntohl(entry->TCPTABLE_REMOTEADDRESS);
#endif
    snmp_set_var_value(idx, (u_char *)&addr, sizeof(addr));

    port = TCP_PORT_TO_HOST_ORDER(entry->TCPTABLE_REMOTEPORT);
    idx = idx->next_variable;
    snmp_set_var_value(idx, (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
tcpTable_free(netsnmp_cache *cache, void *magic)
{
    TCPTABLE_ENTRY_TYPE *p;
    while (tcp_head) {
        p = tcp_head;
        tcp_head = tcp_head->INP_NEXT_SYMBOL;
        free(p);
    }

    tcp_head  = NULL;
    tcp_size  = 0;
    tcp_estab = 0;
}
#endif		/* TCPTABLE_IS_LINKED_LIST */
#endif		/* TCPTABLE_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
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    int             fd;
    struct nmparms  p;
    int             val = 0;
    unsigned int    ulen;
    int             ret;
    int             i;

    tcpTable_free(NULL, NULL);

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

        if (tcp_size > 0) {
            ulen = (unsigned) tcp_size *sizeof(mib_tcpConnEnt);
            tcp_head = (mib_tcpConnEnt *) malloc(ulen);
            p.objid = ID_tcpConnTable;
            p.buffer = (void *) tcp_head;
            p.len = &ulen;
            if ((ret = get_mib_info(fd, &p)) < 0) {
                tcp_size = 0;
            }
        }

        close_mib(fd);
    }

    /*
     * Count the number of established connections
     * Probably not actually necessary for HP-UX
     */
    for (i = 0; i < tcp_size; i++) {
        if (tcp_head[i].State == 5 /* established */ ||
            tcp_head[i].State == 8 /*  closeWait  */ )
            tcp_estab++;
    }

    if (tcp_size > 0) {
        DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table (hpux11)\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (hpux11)\n"));
    return -1;
}

#elif defined(linux)

/*  see <netinet/tcp.h> */
#define TCP_ALL ((1 << (TCP_CLOSING + 1)) - 1)

static const int linux_states[12] = { 1, 5, 3, 4, 6, 7, 11, 1, 8, 9, 2, 10 };

#ifdef HAVE_NETLINK_NETLINK_H

#ifndef HAVE_LIBNL3
#error libnl-3 is required. Please install the libnl-3 and libnl-route-3 development packages and remove --without-nl from the configure options if necessary.
#endif

static int
tcpTable_load_netlink(void)
{
	/* TODO: perhaps use permanent nl socket ? */
	struct nl_sock *nl = nl_socket_alloc();
	struct inet_diag_req req = {
		.idiag_family = AF_INET,
		.idiag_states = TCP_ALL,
	};

	struct nl_msg *nm;

	struct sockaddr_nl peer;
	unsigned char *buf = NULL;
	int running = 1, len, err;

	if (nl == NULL) {
		DEBUGMSGTL(("mibII/tcpTable", "Failed to allocate netlink handle\n"));
		snmp_log(LOG_ERR, "snmpd: Failed to allocate netlink handle\n");
		return -1;
	}

	err = nl_connect(nl, NETLINK_INET_DIAG);
	if (err < 0) {
		DEBUGMSGTL(("mibII/tcpTable", "Failed to connect to netlink: %s\n", nl_geterror(err)));
		snmp_log(LOG_ERR, "snmpd: Couldn't connect to netlink: %s\n", nl_geterror(err));
		nl_socket_free(nl);
		return -1;
	}

	nm = nlmsg_alloc_simple(TCPDIAG_GETSOCK, NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST);
	nlmsg_append(nm, &req, sizeof(struct inet_diag_req), 0);

	err = nl_send_auto_complete(nl, nm);
	if (err < 0) {
		DEBUGMSGTL(("mibII/tcpTable", "nl_send_autocomplete(): %s\n", nl_geterror(err)));
		snmp_log(LOG_ERR, "snmpd: nl_send_autocomplete(): %s\n", nl_geterror(err));
		nl_socket_free(nl);
		return -1;
	}
	nlmsg_free(nm);

	while (running) {
		struct nlmsghdr *h;
		if ((len = nl_recv(nl, &peer, &buf, NULL)) <= 0) {
			DEBUGMSGTL(("mibII/tcpTable", "nl_recv(): %s\n", nl_geterror(len)));
			snmp_log(LOG_ERR, "snmpd: nl_recv(): %s\n", nl_geterror(len));
			nl_socket_free(nl);
			return -1;
		}

		h = (struct nlmsghdr*)buf;

		while (nlmsg_ok(h, len)) {
			struct inet_diag_msg *r = nlmsg_data(h);
			struct inpcb    pcb, *nnew;

			if (h->nlmsg_type == NLMSG_DONE) {
				running = 0;
				break;
			}

			/** handle the case where the kernel doesn't have netlink socket 
			 * diagnostics enabled */
			if ((h->nlmsg_type == NLMSG_ERROR) && 
				(((struct nlmsgerr *)r)->error != 0)) {
				int nlerr = ((struct nlmsgerr *)r)->error;
				running = 0;
				DEBUGMSGTL(("mibII/tcpTable", "netlink error: %d\n", nlerr));
				snmp_log(LOG_ERR, "snmpd: netlink error: %d\n", nlerr);
				break;
			}

			if (r->idiag_family != AF_INET) {
				h = nlmsg_next(h, &len);
				continue;
			}

                        memset(&pcb, 0, sizeof(pcb));
			memcpy(&pcb.inp_laddr.s_addr, r->id.idiag_src, r->idiag_family == AF_INET ? 4 : 6);
			memcpy(&pcb.inp_faddr.s_addr, r->id.idiag_dst, r->idiag_family == AF_INET ? 4 : 6);

			pcb.inp_lport = r->id.idiag_sport;
			pcb.inp_fport = r->id.idiag_dport;

			pcb.inp_state = (r->idiag_state & 0xf) < 12 ? linux_states[r->idiag_state & 0xf] : 2;
			if (pcb.inp_state == 5 /* established */ ||
				pcb.inp_state == 8 /*  closeWait  */ )
				tcp_estab++;
			pcb.uid = r->idiag_uid;

			nnew = SNMP_MALLOC_TYPEDEF(struct inpcb);
			if (nnew == NULL) {
				running = 0;
				/*  XXX report malloc error and return -1? */
				break;
			}
			memcpy(nnew, &pcb, sizeof(struct inpcb));
			nnew->inp_next = tcp_head;
			tcp_head       = nnew;

			h = nlmsg_next(h, &len);
		}
		free(buf);
	}

	nl_socket_free(nl);

	if (tcp_head) {
		DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table using netlink\n"));
		return 0;
	}
	DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (netlink)\n"));
	return -1;
}
#endif

int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    FILE           *in;
    char            line[256];

    tcpTable_free(cache, NULL);

#ifdef HAVE_NETLINK_NETLINK_H
	if (tcpTable_load_netlink() == 0) {
		return 0;
	}
#endif

    if (!(in = fopen("/proc/net/tcp", "r"))) {
        DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (linux1)\n"));
        NETSNMP_LOGONCE((LOG_ERR, "snmpd: cannot open /proc/net/tcp ...\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    lp, fp;
        int             state, uid;

        memset(&pcb, 0, sizeof(pcb));
        if (6 != sscanf(line,
                        "%*d: %x:%x %x:%x %x %*X:%*X %*X:%*X %*X %d",
                        &pcb.inp_laddr.s_addr, &lp,
                        &pcb.inp_faddr.s_addr, &fp, &state, &uid))
            continue;

        pcb.inp_lport = htons((unsigned short) lp);
        pcb.inp_fport = htons((unsigned short) fp);

        pcb.inp_state = (state & 0xf) < 12 ? linux_states[state & 0xf] : 2;
        if (pcb.inp_state == 5 /* established */ ||
            pcb.inp_state == 8 /*  closeWait  */ )
            tcp_estab++;
        pcb.uid = uid;

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

    fclose(in);

    DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table (linux)\n"));
    return 0;
}

#elif defined(solaris2)

static int
TCP_Cmp(void *addr, void *ep)
{
    if (memcmp((mib2_tcpConnEntry_t *) ep, (mib2_tcpConnEntry_t *) addr,
               sizeof(mib2_tcpConnEntry_t)) == 0)
        return (0);
    else
        return (1);
}

int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    mib2_tcpConnEntry_t   entry;
    netsnmp_tcpConnEntry *nnew;
    netsnmp_tcpConnEntry *prev_entry = NULL;

    tcpTable_free(NULL, NULL);

    if (getMibstat(MIB_TCP_CONN, &entry, sizeof(mib2_tcpConnEntry_t),
                   GET_FIRST, &TCP_Cmp, &entry) != 0) {
        DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (solaris)\n"));
        return -1;
    }

    while (1) {
        /*
         * 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.
         * Note 2: at least Solaris 8-10 do not return rows in
         *    sorted order anymore
         */
        nnew = SNMP_MALLOC_TYPEDEF(netsnmp_tcpConnEntry);
        if (nnew == NULL)
            break;
        memcpy(&(nnew->entry), &entry, sizeof(mib2_tcpConnEntry_t));
        if (!prev_entry)
            tcp_head = nnew;
        else
            prev_entry->inp_next = nnew;
        prev_entry = nnew;

        if (getMibstat(MIB_TCP_CONN, &entry, sizeof(mib2_tcpConnEntry_t),
                       GET_NEXT, &TCP_Cmp, &entry) != 0)
	    break;
    }

    if (tcp_head) {
        DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table (solaris)\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (solaris)\n"));
    return -1;
}

#elif defined(HAVE_KVM_GETFILES)

int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    int i, count;
    int      StateMap[] = { 1, 2, 3, 4, 5, 8, 6, 10, 9, 7, 11 };

    tcp_head = kvm_getfiles(kd, KERN_FILE_BYFILE, DTYPE_SOCKET, sizeof(struct kinfo_file), &count);
    tcp_size = count;
    for (i = 0; i < tcp_size; i++) {
	if (tcp_head[i].so_protocol != IPPROTO_TCP || tcp_head[i].so_family != AF_INET)
	    continue;
	if (StateMap[tcp_head[i].TCPTABLE_STATE] == 5 /* established */ ||
	    StateMap[tcp_head[i].TCPTABLE_STATE] == 8 /*  closeWait  */ )
	    tcp_estab++;
    }

    return 0;
}

#elif defined (WIN32) || defined (cygwin)

int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    PMIB_TCPTABLE pTcpTable = NULL;
    DWORD         dwActualSize = 0;
    DWORD         status = NO_ERROR;

    /*
     * query for the buffer size needed 
     */
    status = GetTcpTable(pTcpTable, &dwActualSize, TRUE);
    if (status == ERROR_INSUFFICIENT_BUFFER) {
        pTcpTable = (PMIB_TCPTABLE) malloc(dwActualSize);
        if (pTcpTable != NULL) {
            /*
             * Get the sorted TCP table 
             */
            status = GetTcpTable(pTcpTable, &dwActualSize, TRUE);
        }
    }

    if (status == NO_ERROR) {
        int           i;

        DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table (WIN32)\n"));
        tcp_size = pTcpTable->dwNumEntries -1;  /* entries are counted starting with 0 */
        tcp_head = pTcpTable->table;

	/*
	 * Count the number of established connections
	 * Probably not actually necessary for Windows
	 */
	for (i = 0; i < tcp_size; i++) {
		if (tcp_head[i].dwState == 5 /* established */ ||
			tcp_head[i].dwState == 8 /*  closeWait  */ )
			tcp_estab++;
	}
        return 0;
    }

    DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (win32)\n"));
	if (pTcpTable)
		free(pTcpTable);
    return -1;
}

#elif (defined(NETSNMP_CAN_USE_SYSCTL) && defined(TCPCTL_PCBLIST))

#if defined(freebsd4) || defined(darwin)
    #define NS_ELEM struct xtcpcb
#else
    #define NS_ELEM struct xinpcb
#endif

int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    size_t   len;
    int      sname[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_PCBLIST };
    char     *tcpcb_buf = NULL;
#if defined(dragonfly)
    struct xinpcb  *xig = NULL;
    int      StateMap[] = { 1, 1, 2, 3, 4, 5, 8, 6, 10, 9, 7, 11 };
#else
    struct xinpgen *xig = NULL;
    int      StateMap[] = { 1, 2, 3, 4, 5, 8, 6, 10, 9, 7, 11 };
#endif
    netsnmp_inpcb  *nnew;

    tcpTable_free(NULL, NULL);

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

    /*
     *  Unpick this into the constituent 'xinpgen' structures, and extract
     *     the 'inpcb' elements into a linked list (built in reverse)
     */
#if defined(dragonfly)
    xig = (struct xinpcb  *) tcpcb_buf;
#else
    xig = (struct xinpgen *) tcpcb_buf;
    xig = (struct xinpgen *) ((char *) xig + xig->xig_len);
#endif

#if defined(dragonfly)
    while (xig && ((char *)xig + xig->xi_len < tcpcb_buf + len))
#else
    while (xig && (xig->xig_len > sizeof(struct xinpgen)))
#endif
    {
        nnew = SNMP_MALLOC_TYPEDEF(netsnmp_inpcb);
        if (!nnew)
            break;
#if __FreeBSD_version >= 1200026
        nnew->state = StateMap[((NS_ELEM *) xig)->t_state];
#else
        nnew->state = StateMap[((NS_ELEM *) xig)->xt_tp.t_state];
#endif
        if (nnew->state == 5 /* established */ ||
            nnew->state == 8 /*  closeWait  */ )
            tcp_estab++;
        memcpy(&(nnew->pcb), &(((NS_ELEM *) xig)->xt_inp),
#if __FreeBSD_version >= 1200026
                           sizeof(struct xinpcb));
#else
                           sizeof(struct inpcb));
#endif

#ifdef INP_ISIPV6
	if (INP_ISIPV6(&nnew->pcb))
#else
	if (nnew->pcb.inp_vflag & INP_IPV6)
#endif
	    free(nnew);
	else {
	    nnew->inp_next = tcp_head;
	    tcp_head   = nnew;
	}
#if defined(dragonfly)
        xig = (struct xinpcb  *) ((char *) xig + xig->xi_len);
#else
        xig = (struct xinpgen *) ((char *) xig + xig->xig_len);
#endif
    }

    free(tcpcb_buf);
    if (tcp_head) {
        DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table (sysctl)\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (sysctl)\n"));
    return -1;
}
#undef NS_ELEM

#elif defined(PCB_TABLE)

int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    struct inpcbtable table;
    struct inpcb   *entry;
    struct tcpcb    tcpcb;
    netsnmp_inpcb  *nnew;
    int      StateMap[] = { 1, 2, 3, 4, 5, 8, 6, 10, 9, 7, 11 };

    tcpTable_free(NULL, NULL);

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

    /*
     *  Set up a linked list
     */
#if defined(__NetBSD__) && __NetBSD_Version__ >= 699002800
    entry  = TAILQ_FIRST(&table.inpt_queue);
#else
    entry  = table.INP_FIRST_SYMBOL;
#endif
    while (entry) {
   
        nnew = SNMP_MALLOC_TYPEDEF(netsnmp_inpcb);
        if (!nnew)
            break;
        if (!NETSNMP_KLOOKUP(entry, (char *)&(nnew->pcb), sizeof(nnew->pcb))) {
            DEBUGMSGTL(("mibII/tcpTable:TcpTable_load", "klookup failed\n"));
            break;
        }

        if (!NETSNMP_KLOOKUP(nnew->TCPTABLE_TCPCB, (char *)&tcpcb, sizeof(struct tcpcb))) {
            DEBUGMSGTL(("mibII/tcpTable:TcpTable_load", "klookup failed\n"));
            break;
        }
	nnew->state = StateMap[tcpcb.t_state];
        if (nnew->state == 5 /* established */ ||
            nnew->state == 8 /*  closeWait  */ )
            tcp_estab++;

        entry      = nnew->INP_NEXT_SYMBOL;	/* Next kernel entry */
	nnew->inp_next = tcp_head;
	tcp_head   = nnew;

#if defined(__NetBSD__) && __NetBSD_Version__ >= 699002800
        if (entry == TAILQ_FIRST(&table.inpt_queue))
#else
        if (entry == table.INP_FIRST_SYMBOL)
#endif
            break;
    }

    if (tcp_head) {
        DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table (pcb_table)\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (pcb_table)\n"));
    return -1;
}

#elif defined(TCP_SYMBOL)

int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    struct inpcb   tcp_inpcb;
    struct tcpcb   tcpcb;
    netsnmp_inpcb  *nnew;
    struct inpcb   *entry;
#ifdef hpux
    int      StateMap[] = { 1, 2, 3, -1, 4, 5, 8, 6, 10, 9, 7, 11 };
#else
    int      StateMap[] = { 1, 2, 3,     4, 5, 8, 6, 10, 9, 7, 11 };
#endif

    tcpTable_free(NULL, NULL);

    if (!auto_nlist(TCP_SYMBOL, (char *) &tcp_inpcb, sizeof(tcp_inpcb))) {
        DEBUGMSGTL(("mibII/tcpTable", "Failed to read tcp_symbol\n"));
        return -1;
    }

    /*
     *  Set up a linked list
     */
    entry  = tcp_inpcb.INP_NEXT_SYMBOL;
    while (entry) {
   
        nnew = SNMP_MALLOC_TYPEDEF(netsnmp_inpcb);
        if (!nnew)
            break;
        if (!NETSNMP_KLOOKUP(entry, (char *)&(nnew->pcb), sizeof(struct inpcb))) {
            DEBUGMSGTL(("mibII/tcpTable:tcpTable_load", "klookup failed\n"));
            break;
        }
        if (!NETSNMP_KLOOKUP(nnew->pcb.inp_ppcb, (char *)&tcpcb, sizeof(struct tcpcb))) {
            DEBUGMSGTL(("mibII/tcpTable:tcpTable_load", "klookup failed\n"));
            break;
        }
	nnew->state    = StateMap[tcpcb.t_state];
        if (nnew->state == 5 /* established */ ||
            nnew->state == 8 /*  closeWait  */ )
            tcp_estab++;

        entry          = nnew->pcb.INP_NEXT_SYMBOL;	/* Next kernel entry */
	nnew->inp_next = tcp_head;
	tcp_head       = nnew;

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

    if (tcp_head) {
        DEBUGMSGTL(("mibII/tcpTable", "Loaded TCP Table (tcp_symbol)\n"));
        return 0;
    }
    DEBUGMSGTL(("mibII/tcpTable", "Failed to load TCP Table (tcp_symbol)\n"));
    return -1;
}

#else				/* TCP_SYMBOL */
int
tcpTable_load(netsnmp_cache *cache, void *vmagic)
{
    DEBUGMSGTL(("mibII/tcpTable", "Loading TCP Table not implemented\n"));
    return -1;
}
#endif				/* TCP_SYMBOL */
