
/*
 *  TCP MIB group implementation - tcp.c
 *
 */

#include <config.h>
#include <unistd.h>
#include "mib_module_config.h"
#if TIME_WITH_SYS_TIME
# ifdef WIN32
#  include <sys/timeb.h>
# else
#  include <sys/time.h>
# endif
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#if HAVE_SYS_PROTOSW_H
#include <sys/protosw.h>
#endif

#if HAVE_SYS_SYSMP_H
#include <sys/sysmp.h>
#endif
#if defined(IFNET_NEEDS_KERNEL) && !defined(_KERNEL)
#define _KERNEL 1
#define _I_DEFINED_KERNEL
#endif
#include <net/if.h>
#if HAVE_NET_IF_VAR_H
#include <net/if_var.h>
#endif
#ifdef _I_DEFINED_KERNEL
#undef _KERNEL
#endif
#if HAVE_SYS_STREAM_H
#include <sys/stream.h>
#endif
#ifdef HAVE_NET_ROUTE_H
#include <net/route.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#if HAVE_SYS_QUEUE_H
#include <sys/queue.h>
#endif
#if HAVE_NETINET_IP_VAR_H
#include <netinet/ip_var.h>
#endif
#if HAVE_SYS_SOCKETVAR_H
#include <sys/socketvar.h>
#endif
#if HAVE_NETINET_IN_PCB_H
#include <netinet/in_pcb.h>
#endif
#if HAVE_INET_MIB2_H
#include <inet/mib2.h>
#endif
#if STDC_HEADERS
#include <string.h>
#include <stdlib.h>
#else
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#endif
#ifdef solaris2
#include "kernel_sunos5.h"
#else
#include "kernel.h"
#endif
#include "../mibincl.h"
#include "../../../snmplib/system.h"
#if HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#if defined(osf4) || defined(aix4) || defined(hpux10)
/* these are undefed to remove a stupid warning on osf compilers
   because they get redefined with a slightly different notation of the
   same value.  -- Wes */
#undef TCP_NODELAY
#undef TCP_MAXSEG
#endif
#include <netinet/tcp.h>
#if HAVE_NETINET_TCPIP_H
#include <netinet/tcpip.h>
#endif
#if HAVE_NETINET_TCP_TIMER_H
#include <netinet/tcp_timer.h>
#endif
#if HAVE_NETINET_TCP_VAR_H
#include <netinet/tcp_var.h>
#endif
#if HAVE_NETINET_TCP_FSM_H
#include <netinet/tcp_fsm.h>
#endif
#if HAVE_SYS_TCPIPSTATS_H
#include <sys/tcpipstats.h>
#endif

#include "auto_nlist.h"

#ifdef hpux
#include <sys/mib.h>
#include <netinet/mib_kern.h>
#endif /* hpux */

/* #include "../common_header.h" */
#include "tcp.h"

	/*********************
	 *
	 *  Kernel & interface information,
	 *   and internal forward declarations
	 *
	 *********************/

#ifdef linux
static void linux_read_tcp_stat (struct tcp_mib *);
#endif

	/*********************
	 *
	 *  Initialisation & common implementation functions
	 *
	 *********************/

struct variable13 tcp_variables[] = {
    {TCPRTOALGORITHM, ASN_INTEGER, RONLY, var_tcp, 1, {1}},
    {TCPRTOMIN, ASN_INTEGER, RONLY, var_tcp, 1, {2}},
#ifndef sunV3
    {TCPRTOMAX, ASN_INTEGER, RONLY, var_tcp, 1, {3}},
#endif
    {TCPMAXCONN, ASN_INTEGER, RONLY, var_tcp, 1, {4}},
#ifndef sunV3
    {TCPACTIVEOPENS, ASN_COUNTER, RONLY, var_tcp, 1, {5}},
    {TCPPASSIVEOPENS, ASN_COUNTER, RONLY, var_tcp, 1, {6}},
    {TCPATTEMPTFAILS, ASN_COUNTER, RONLY, var_tcp, 1, {7}},
    {TCPESTABRESETS, ASN_COUNTER, RONLY, var_tcp, 1, {8}},
#endif
    {  TCPCURRESTAB, ASN_GAUGE, RONLY, var_tcp, 1, {9}},
#ifndef sunV3
    {TCPINSEGS, ASN_COUNTER, RONLY, var_tcp, 1, {10}},
    {TCPOUTSEGS, ASN_COUNTER, RONLY, var_tcp, 1, {11} },
    {TCPRETRANSSEGS, ASN_COUNTER, RONLY, var_tcp, 1, {12}},
#endif
    {TCPCONNSTATE, ASN_INTEGER, RONLY, var_tcpEntry, 3, {13, 1, 1}},
    {TCPCONNLOCALADDRESS, ASN_IPADDRESS, RONLY, var_tcpEntry, 3, {13, 1, 2}},
    {TCPCONNLOCALPORT, ASN_INTEGER, RONLY, var_tcpEntry, 3, {13, 1, 3}},
    {TCPCONNREMADDRESS, ASN_IPADDRESS, RONLY, var_tcpEntry, 3, {13, 1, 4}},
    {TCPCONNREMPORT, ASN_INTEGER, RONLY, var_tcpEntry, 3, {13, 1, 5}},
    {TCPINERRS, ASN_COUNTER, RONLY, var_tcp, 1, {14}},
    {TCPOUTRSTS, ASN_COUNTER, RONLY, var_tcp, 1, {15}}
};

/* Define the OID pointer to the top of the mib tree that we're
   registering underneath */
oid tcp_variables_oid[] = { 1,3,6,1,2,1,6 };

void init_tcp(void)
{
  /* register ourselves with the agent to handle our mib tree */
  REGISTER_MIB("mibII/tcp", tcp_variables, variable13, tcp_variables_oid);

#ifdef TCPSTAT_SYMBOL
  auto_nlist( TCPSTAT_SYMBOL,0,0 );
#endif
#ifdef TCP_SYMBOL
  auto_nlist( TCP_SYMBOL,0,0 );
#endif

}

/*
  header_tcp(...
  Arguments:
  vp	  IN      - pointer to variable entry that points here
  name    IN/OUT  - IN/name requested, OUT/name found
  length  IN/OUT  - length of IN/OUT oid's 
  exact   IN      - TRUE if an exact match was requested
  var_len OUT     - length of variable or 0 if function returned
  write_method
  
*/

static int
header_tcp(struct variable *vp,
	   oid *name,
	   int *length,
	   int exact,
	   int *var_len,
	   WriteMethod **write_method)
{
#define TCP_NAME_LENGTH	8
    oid newname[MAX_OID_LEN];
    int result;
    char c_oid[SPRINT_MAX_LEN];

    if (snmp_get_do_debugging()) {
      sprint_objid (c_oid, name, *length);
      DEBUGMSGTL(("mibII/tcp", "var_tcp: %s %d\n", c_oid, exact));
    }

    memcpy( (char *)newname,(char *)vp->name, (int)vp->namelen * sizeof(oid));
    newname[TCP_NAME_LENGTH] = 0;
    result = snmp_oid_compare(name, *length, newname, (int)vp->namelen + 1);
    if ((exact && (result != 0)) || (!exact && (result >= 0)))
        return(MATCH_FAILED);
    memcpy( (char *)name,(char *)newname, ((int)vp->namelen + 1) * sizeof(oid));
    *length = vp->namelen + 1;

    *write_method = 0;
    *var_len = sizeof(long);	/* default to 'long' results */
    return(MATCH_SUCCEEDED);
}

	/*********************
	 *
	 *  System specific implementation functions
	 *
	 *********************/

#ifndef solaris2

#ifdef HAVE_SYS_TCPIPSTATS_H

u_char *
var_tcp(struct variable *vp,
	oid *name,
	int *length,
	int exact,
	int *var_len,
	WriteMethod **write_method)
{
    static struct kna tcpipstats;

    /*
     *	Allow for a kernel w/o TCP
     */

	if (header_tcp(vp, name, length, exact, var_len, write_method) == MATCH_FAILED )
	    return NULL;

	/*
	 *  Get the TCP statistics from the kernel...
	 */

    if (sysmp (MP_SAGET, MPSA_TCPIPSTATS, &tcpipstats, sizeof tcpipstats) == -1) {
	perror ("sysmp(MP_SAGET)(MPSA_TCPIPSTATS)");
    }
#define tcpstat tcpipstats.tcpstat

	switch (vp->magic){
	    case TCPRTOALGORITHM:
#ifndef linux
		long_return = 4;	/* Van Jacobsen's algorithm *//* XXX */
#else
                if (! tcpstat.TcpRtoAlgorithm) {
		    /* 0 is illegal: assume `other' algorithm: */
		    long_return = 1;
		    return (u_char *) &long_return;
                }
                return (u_char *) &tcpstat.TcpRtoAlgorithm;
#endif
		return (u_char *) &long_return;
	    case TCPRTOMIN:
#ifndef linux
		long_return = TCPTV_MIN / PR_SLOWHZ * 1000;
		return (u_char *) &long_return;
#else
		return (u_char *) &tcpstat.TcpRtoMin;
#endif
	    case TCPRTOMAX:
#ifndef linux
		long_return = TCPTV_REXMTMAX / PR_SLOWHZ * 1000;
		return (u_char *) &long_return;
#else
		return (u_char *) &tcpstat.TcpRtoMax;
#endif
	    case TCPMAXCONN:
#ifndef linux
		long_return = -1;
		return (u_char *) &long_return;
#else
		return (u_char *) &tcpstat.TcpMaxConn;
#endif
	    case TCPACTIVEOPENS:
                long_return = tcpstat.tcps_connattempt;
		return (u_char *) &long_return;
	    case TCPPASSIVEOPENS:
                long_return = tcpstat.tcps_accepts;
		return (u_char *) &long_return;
	    case TCPATTEMPTFAILS:
#ifdef hpux
		long_return = MIB_tcpcounter[7];
#else
		long_return = tcpstat.tcps_conndrops;	/* XXX */
#endif
		return (u_char *) &long_return;
	    case TCPESTABRESETS:
#ifdef hpux
		long_return = MIB_tcpcounter[8];
#else
		long_return = tcpstat.tcps_drops;	/* XXX */
#endif
		return (u_char *) &long_return;
		/*
		 * NB:  tcps_drops is actually the sum of the two MIB
		 *	counters tcpAttemptFails and tcpEstabResets.
		 */
	    case TCPCURRESTAB:
#ifndef linux
		long_return = TCP_Count_Connections();
		return (u_char *) &long_return;
#else
		return (u_char *) &tcpstat.TcpCurrEstab;
#endif
	    case TCPINSEGS:
                long_return = tcpstat.tcps_rcvtotal;
		return (u_char *) &long_return;
	    case TCPOUTSEGS:
		long_return = tcpstat.tcps_sndtotal
			    - tcpstat.tcps_sndrexmitpack;
		/*
		 * RFC 1213 defines this as the number of segments sent
		 * "excluding those containing only retransmitted octets"
		 */
		return (u_char *) &long_return;
	    case TCPRETRANSSEGS:
                long_return = tcpstat.tcps_sndrexmitpack;
		return (u_char *) &long_return;
#ifndef linux
	    case TCPINERRS:
		long_return = tcpstat.tcps_rcvbadsum + tcpstat.tcps_rcvbadoff 
#ifdef STRUCT_TCPSTAT_HAS_TCPS_RCVMEMDROP
                  + tcpstat.tcps_rcvmemdrop
#endif
                  + tcpstat.tcps_rcvshort;
		return (u_char *) &long_return;
	    case TCPOUTRSTS:
		long_return = tcpstat.tcps_sndctrl - tcpstat.tcps_closed;
		return (u_char *) &long_return;
#endif /* linux */
	    default:
		ERROR_MSG("");
	}
    return NULL;
}

#else /* not HAVE_SYS_TCPIPSTATS_H */

u_char *
var_tcp(struct variable *vp,
	oid *name,
	int *length,
	int exact,
	int *var_len,
	WriteMethod **write_method)
{
    static struct tcpstat tcpstat;
#ifdef hpux
    static	counter MIB_tcpcounter[MIB_tcpMAXCTR+1];
#endif

    /*
     *	Allow for a kernel w/o TCP
     */
#ifndef linux
#ifndef TCPSTAT_SYMBOL
    return NULL;
#else
    if (auto_nlist_value(TCPSTAT_SYMBOL) == -1) return(NULL);
#endif
#endif
    
	if (header_tcp(vp, name, length, exact, var_len, write_method) == MATCH_FAILED )
	    return NULL;

	/*
	 *  Get the TCP statistics from the kernel...
	 */
#if !defined(CAN_USE_SYSCTL) || !defined(TCPCTL_STATS)
#ifndef linux
	auto_nlist(TCPSTAT_SYMBOL, (char *)&tcpstat, sizeof (tcpstat));
#ifdef MIB_TCPCOUNTER_SYMBOL
	auto_nlist(MIB_TCPCOUNTER_SYMBOL, (char *)&MIB_tcpcounter,
                   (MIB_tcpMAXCTR+1)*sizeof (counter));
#endif
#else /* linux */
	linux_read_tcp_stat (&tcpstat);
#endif /* linux */
#else /* can use sysctl and net.inet.tcp.stats exists */
	{
		int sname[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_STATS };
		size_t len;

		len = sizeof tcpstat;
		if (sysctl(sname, 4, &tcpstat, &len, 0, 0) < 0)
			return NULL;
	}
#endif

	switch (vp->magic) {
	    case TCPRTOALGORITHM:
#ifndef linux
		long_return = 4;	/* Van Jacobsen's algorithm *//* XXX */
#else
                if (! tcpstat.TcpRtoAlgorithm) {
		    /* 0 is illegal: assume `other' algorithm: */
		    long_return = 1;
		    return (u_char *) &long_return;
                }
                return (u_char *) &tcpstat.TcpRtoAlgorithm;
#endif
		return (u_char *) &long_return;
	    case TCPRTOMIN:
#ifndef linux
		long_return = TCPTV_MIN / PR_SLOWHZ * 1000;
		return (u_char *) &long_return;
#else
		return (u_char *) &tcpstat.TcpRtoMin;
#endif
	    case TCPRTOMAX:
#ifndef linux
		long_return = TCPTV_REXMTMAX / PR_SLOWHZ * 1000;
		return (u_char *) &long_return;
#else
		return (u_char *) &tcpstat.TcpRtoMax;
#endif
	    case TCPMAXCONN:
#ifndef linux
		long_return = -1;
		return (u_char *) &long_return;
#else
		return (u_char *) &tcpstat.TcpMaxConn;
#endif
	    case TCPACTIVEOPENS:
                long_return = tcpstat.tcps_connattempt;
		return (u_char *) &long_return;
	    case TCPPASSIVEOPENS:
                long_return = tcpstat.tcps_accepts;
		return (u_char *) &long_return;
	    case TCPATTEMPTFAILS:
#ifdef hpux
		long_return = MIB_tcpcounter[7];
#else
		long_return = tcpstat.tcps_conndrops;	/* XXX */
#endif
		return (u_char *) &long_return;
	    case TCPESTABRESETS:
#ifdef hpux
		long_return = MIB_tcpcounter[8];
#else
		long_return = tcpstat.tcps_drops;	/* XXX */
#endif
		return (u_char *) &long_return;
		/*
		 * NB:  tcps_drops is actually the sum of the two MIB
		 *	counters tcpAttemptFails and tcpEstabResets.
		 */
	    case TCPCURRESTAB:
#ifndef linux
		long_return = TCP_Count_Connections();
#else
		long_return = tcpstat.TcpCurrEstab;
#endif
		return (u_char *) &long_return;
	    case TCPINSEGS:
                long_return = tcpstat.tcps_rcvtotal;
		return (u_char *) &long_return;
	    case TCPOUTSEGS:
		long_return = tcpstat.tcps_sndtotal
			    - tcpstat.tcps_sndrexmitpack;
		/*
		 * RFC 1213 defines this as the number of segments sent
		 * "excluding those containing only retransmitted octets"
		 */
		return (u_char *) &long_return;
	    case TCPRETRANSSEGS:
                long_return = tcpstat.tcps_sndrexmitpack;
		return (u_char *) &long_return;
#ifndef linux
	    case TCPINERRS:
		long_return = tcpstat.tcps_rcvbadsum + tcpstat.tcps_rcvbadoff 
#ifdef STRUCT_TCPSTAT_HAS_TCPS_RCVMEMDROP
                  + tcpstat.tcps_rcvmemdrop
#endif
                  + tcpstat.tcps_rcvshort;
		return (u_char *) &long_return;
	    case TCPOUTRSTS:
		long_return = tcpstat.tcps_sndctrl - tcpstat.tcps_closed;
		return (u_char *) &long_return;
#endif /* linux */
	    default:
		ERROR_MSG("");
	}
    return NULL;
}

#endif /* not HAVE_SYS_TCPIPSTATS_H */

u_char *
var_tcpEntry(struct variable *vp,
	     oid *name,
	     int *length,
	     int exact,
	     int *var_len,
	     WriteMethod **write_method)
{
    int i;
    oid newname[MAX_OID_LEN], lowest[MAX_OID_LEN], *op;
    u_char *cp;
    int State, LowState;
    static struct inpcb inpcb, Lowinpcb;
    
    /*
     *	Allow for a kernel w/o TCP
     */
#ifdef TCPSTAT_SYMBOL
#ifndef linux
    if (auto_nlist_value(TCPSTAT_SYMBOL) == -1) return(NULL);
#endif
#endif
    
	memcpy( (char *)newname,(char *)vp->name, (int)vp->namelen * sizeof(oid));
	lowest[0] = 9999;

	/* find "next" connection */
Again:
LowState = -1;	    /* Don't have one yet */
	TCP_Scan_Init();
	for (;;) {
	    if ((i = TCP_Scan_Next(&State, &inpcb)) < 0) goto Again;
	    if (i == 0) break;	    /* Done */
	    cp = (u_char *)&inpcb.inp_laddr.s_addr;
	    op = newname + 10;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    
	    newname[14] = ntohs(inpcb.inp_lport);

	    cp = (u_char *)&inpcb.inp_faddr.s_addr;
	    op = newname + 15;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    *op++ = *cp++;
	    
	    newname[19] = ntohs(inpcb.inp_fport);

	    if (exact){
		if (snmp_oid_compare(newname, 20, name, *length) == 0){
		    memcpy( (char *)lowest,(char *)newname, 20 * sizeof(oid));
		    LowState = State;
		    Lowinpcb = inpcb;
		    break;  /* no need to search further */
		}
	    } else {
		if (snmp_oid_compare(newname, 20, name, *length) > 0 &&
		     snmp_oid_compare(newname, 20, lowest, 20) < 0){
		    /*
		     * if new one is greater than input and closer to input than
		     * previous lowest, save this one as the "next" one.
		     */
		    memcpy( (char *)lowest,(char *)newname, 20 * sizeof(oid));
		    LowState = State;
		    Lowinpcb = inpcb;
		}
	    }
	}
	if (LowState < 0) return(NULL);
	memcpy( (char *)name,(char *)lowest, ((int)vp->namelen + 10) * sizeof(oid));
	*length = vp->namelen + 10;
	*write_method = 0;
	*var_len = sizeof(long);
	switch (vp->magic) {
	    case TCPCONNSTATE: {
#ifndef hpux
		static int StateMap[]={1, 2, 3, 4, 5, 8, 6, 10, 9, 7, 11};
#else
              static int StateMap[]={1, 2, 3, -1, 4, 5, 8, 6, 10, 9, 7, 11};
#endif
		return (u_char *) &StateMap[LowState];
	    }
	    case TCPCONNLOCALADDRESS:
		return (u_char *) &Lowinpcb.inp_laddr.s_addr;
	    case TCPCONNLOCALPORT:
		long_return = ntohs(Lowinpcb.inp_lport);
		return (u_char *) &long_return;
	    case TCPCONNREMADDRESS:
		return (u_char *) &Lowinpcb.inp_faddr.s_addr;
	    case TCPCONNREMPORT:
		long_return = ntohs(Lowinpcb.inp_fport);
		return (u_char *) &long_return;
	}
    return NULL;
}

#else  /* solaris2 - tcp */


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);
}

u_char *
var_tcp(struct variable *vp,
	oid *name,
	int *length,
	int exact,
	int *var_len,
	WriteMethod **write_method)
{
  mib2_tcp_t tcpstat;
  mib2_ip_t ipstat;

    if (header_tcp(vp, name, length, exact, var_len, write_method) == MATCH_FAILED )
	return NULL;

    /*
     *  Get the TCP statistics from the kernel...
     */
    if (getMibstat(MIB_TCP, &tcpstat, sizeof(mib2_tcp_t), GET_FIRST, &Get_everything, NULL) < 0)
      return (NULL);		/* Things are ugly ... */

    switch (vp->magic){
    case TCPRTOALGORITHM:
      long_return = tcpstat.tcpRtoAlgorithm;
      return(u_char *) &long_return;
    case TCPRTOMIN:
      long_return = tcpstat.tcpRtoMin;
      return(u_char *) &long_return;
    case TCPRTOMAX:
      long_return = tcpstat.tcpRtoMax;
      return(u_char *) &long_return;
    case TCPMAXCONN:
      long_return = tcpstat.tcpMaxConn;
      return(u_char *) &long_return;
    case TCPACTIVEOPENS:
      long_return = tcpstat.tcpActiveOpens;
      return(u_char *) &long_return;
    case TCPPASSIVEOPENS:
      long_return = tcpstat.tcpPassiveOpens;
      return(u_char *) &long_return;
    case TCPATTEMPTFAILS:
      long_return = tcpstat.tcpAttemptFails;
      return(u_char *) &long_return;
    case TCPESTABRESETS:
      long_return = tcpstat.tcpEstabResets;
      return(u_char *) &long_return;
    case TCPCURRESTAB:
      long_return = tcpstat.tcpCurrEstab;
      return(u_char *) &long_return;
    case TCPINSEGS:
      long_return = tcpstat.tcpInSegs;
      return(u_char *) &long_return;
    case TCPOUTSEGS:
      long_return = tcpstat.tcpOutSegs;
      return(u_char *) &long_return;
    case TCPRETRANSSEGS:
      long_return = tcpstat.tcpRetransSegs;
      return(u_char *) &long_return;
    case TCPINERRS:
      if (getMibstat(MIB_IP, &ipstat, sizeof(mib2_ip_t), GET_FIRST, &Get_everything, NULL) < 0)
	return (NULL);		/* Things are ugly ... */
      long_return = ipstat.tcpInErrs;
      return(u_char *) &long_return;
    default:
      ERROR_MSG("");
      return (NULL);
    }
}


u_char *
var_tcpEntry(struct variable *vp,
	     oid *name,
	     int *length,
	     int exact,
	     int *var_len,
	     WriteMethod **write_method)
{
  oid newname[MAX_OID_LEN], lowest[MAX_OID_LEN], *op;
  u_char *cp;

#define TCP_CONN_LENGTH	20
#define TCP_LOCADDR_OFF	10
#define TCP_LOCPORT_OFF	14
#define TCP_REMADDR_OFF	15
#define TCP_REMPORT_OFF	19
    mib2_tcpConnEntry_t	Lowentry, Nextentry, entry;
    req_e  		req_type;
    int			Found = 0;
    
    memset (&Lowentry, 0, sizeof (Lowentry));
    memcpy( (char *)newname,(char *)vp->name, (int)vp->namelen * sizeof(oid));
    if (*length == TCP_CONN_LENGTH) /* Assume that the input name is the lowest */
      memcpy( (char *)lowest,(char *)name, TCP_CONN_LENGTH * sizeof(oid));
    for (Nextentry.tcpConnLocalAddress = (u_long)-1, req_type = GET_FIRST;
	 ;
	 Nextentry = entry, req_type = GET_NEXT) {
      if (getMibstat(MIB_TCP_CONN, &entry, sizeof(mib2_tcpConnEntry_t),
		 req_type, &TCP_Cmp, &entry) != 0)
	break;
      COPY_IPADDR(cp, (u_char *)&entry.tcpConnLocalAddress, op, newname + TCP_LOCADDR_OFF);
      newname[TCP_LOCPORT_OFF] = entry.tcpConnLocalPort;
      COPY_IPADDR(cp, (u_char *)&entry.tcpConnRemAddress, op, newname + TCP_REMADDR_OFF);
      newname[TCP_REMPORT_OFF] = entry.tcpConnRemPort;

      if (exact){
	if (snmp_oid_compare(newname, TCP_CONN_LENGTH, name, *length) == 0){
	  memcpy( (char *)lowest,(char *)newname, TCP_CONN_LENGTH * sizeof(oid));
	  Lowentry = entry;
	  Found++;
	  break;  /* no need to search further */
	}
      } else {
	if ((snmp_oid_compare(newname, TCP_CONN_LENGTH, name, *length) > 0) &&
	    ((Nextentry.tcpConnLocalAddress == (u_long)-1)
	     || (snmp_oid_compare(newname, TCP_CONN_LENGTH, lowest, TCP_CONN_LENGTH) < 0)
	     || (snmp_oid_compare(name, *length, lowest, TCP_CONN_LENGTH) == 0))){

	  /* if new one is greater than input and closer to input than
	   * previous lowest, and is not equal to it, save this one as the "next" one.
	   */
	  memcpy( (char *)lowest,(char *)newname, TCP_CONN_LENGTH * sizeof(oid));
	  Lowentry = entry;
	  Found++;
	}
      }
    }
    if (Found == 0)
      return(NULL);
    memcpy((char *)name, (char *)lowest,
	  ((int)vp->namelen + TCP_CONN_LENGTH - TCP_LOCADDR_OFF) * sizeof(oid));
    *length = vp->namelen + TCP_CONN_LENGTH - TCP_LOCADDR_OFF;
    *write_method = 0;
    *var_len = sizeof(long);
    switch (vp->magic) {
    case TCPCONNSTATE:
      long_return = Lowentry.tcpConnState;
      return(u_char *) &long_return;
    case TCPCONNLOCALADDRESS:
      long_return = Lowentry.tcpConnLocalAddress;
      return(u_char *) &long_return;
    case TCPCONNLOCALPORT:
      long_return = Lowentry.tcpConnLocalPort;
      return(u_char *) &long_return;
    case TCPCONNREMADDRESS:
      long_return = Lowentry.tcpConnRemAddress;
      return(u_char *) &long_return;
    case TCPCONNREMPORT:
      long_return = Lowentry.tcpConnRemPort;
      return(u_char *) &long_return;
    default:
      ERROR_MSG("");
      return (NULL);
    }
}

#endif /* solaris2 - tcp */


	/*********************
	 *
	 *  Internal implementation functions
	 *
	 *********************/


#ifdef linux
/*
 * lucky days. since 1.1.16 the tcp statistics are avail by the proc
 * file-system.
 */

static void
linux_read_tcp_stat (struct tcp_mib *tcpstat)
{
  FILE *in = fopen ("/proc/net/snmp", "r");
  char line [1024];

  memset ((char *) tcpstat, (0), sizeof (*tcpstat));

  if (! in)
    return;

  while (line == fgets (line, sizeof(line), in))
    {
      if (12 == sscanf (line, "Tcp: %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
	&tcpstat->TcpRtoAlgorithm, &tcpstat->TcpRtoMin, &tcpstat->TcpRtoMax, 
	&tcpstat->TcpMaxConn, &tcpstat->TcpActiveOpens, &tcpstat->TcpPassiveOpens,
	&tcpstat->TcpAttemptFails, &tcpstat->TcpEstabResets, &tcpstat->TcpCurrEstab, 
	&tcpstat->TcpInSegs, &tcpstat->TcpOutSegs, &tcpstat->TcpRetransSegs))
	break;
    }
  fclose (in);
}

#endif /* linux */

#ifndef solaris2
#ifndef linux
/*
 *	Print INTERNET connections
 */

int TCP_Count_Connections (void)
{
	int Established;
	struct inpcb cb;
	register struct inpcb *next;
#if !(defined(freebsd2) || defined(netbsd2) || defined(openbsd2))
	register struct inpcb *prev;
#endif
	struct inpcb inpcb;
	struct tcpcb tcpcb;

Again:	/*
	 *	Prepare to scan the control blocks
	 */
	Established = 0;

	auto_nlist(TCP_SYMBOL, (char *)&cb, sizeof(struct inpcb));
	inpcb = cb;
#if !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2))
	prev = (struct inpcb *) auto_nlist_value(TCP_SYMBOL);
#endif /*  !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2)) */
	/*
	 *	Scan the control blocks
	 */
#if defined(freebsd2) || defined(netbsd1) || defined(openbsd2)
	while ((inpcb.INP_NEXT_SYMBOL != NULL) && (inpcb.INP_NEXT_SYMBOL != (struct inpcb *) auto_nlist_value(TCP_SYMBOL))) {
#else /*  defined(freebsd2) || defined(netbsd1) || defined(openbsd2) */
	while (inpcb.INP_NEXT_SYMBOL != (struct inpcb *) auto_nlist_value(TCP_SYMBOL)) {
#endif /*  defined(freebsd2) || defined(netbsd1) */
		next = inpcb.INP_NEXT_SYMBOL;

		if((klookup((unsigned long)next, (char *)&inpcb, sizeof (inpcb)) == 0)) {
		    perror("TCP_Count_Connections - inpcb");
		    break;
		}
#if !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2))
		if (inpcb.INP_PREV_SYMBOL != prev) {	    /* ??? */
			sleep(1);
			goto Again;
		}
#endif /*  !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2)) */
		if (inet_lnaof(inpcb.inp_laddr) == INADDR_ANY) {
#if !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2))
			prev = next;
#endif /*  !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2)) */
			continue;
		}
		if(klookup((unsigned long)inpcb.inp_ppcb, (char *)&tcpcb, sizeof (tcpcb)) == 0) {
		    perror("TCP_Count_Connections - tcpcb");
		    break;
		}

		if ((tcpcb.t_state == TCPS_ESTABLISHED) ||
		    (tcpcb.t_state == TCPS_CLOSE_WAIT))
		    Established++;
#if !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2))
		prev = next;
#endif /*  !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2)) */
	}
	return(Established);
}
#endif

static struct inpcb tcp_inpcb, *tcp_prev;
#ifdef PCB_TABLE
static struct inpcb *tcp_next, *tcp_head;
#endif
#ifdef linux
static struct inpcb *inpcb_list;
#endif

#if defined(CAN_USE_SYSCTL) && defined(TCPCTL_PCBLIST)
static char *tcpcb_buf = NULL;
static struct xinpgen *xig = NULL;
#endif /* !defined(CAN_USE_SYSCTL) || !define(TCPCTL_PCBLIST) */

void TCP_Scan_Init (void)
{
#if  !defined(CAN_USE_SYSCTL) || !defined(TCPCTL_PCBLIST)
#ifdef PCB_TABLE
    struct inpcbtable table;
#endif
#ifndef linux
#ifdef PCB_TABLE
    auto_nlist(TCP_SYMBOL, (char *)&table, sizeof(table));
    tcp_head = tcp_prev = (struct inpcb *)&((struct inpcbtable *)auto_nlist_value(TCP_SYMBOL))->inpt_queue.cqh_first;
    tcp_next = table.inpt_queue.cqh_first;
#else	/* PCB_TABLE */
    auto_nlist(TCP_SYMBOL, (char *)&tcp_inpcb, sizeof(tcp_inpcb));
#if !(defined(freebsd2) || defined(netbsd1) || defined(openbsd2))
    tcp_prev = (struct inpcb *) auto_nlist_value(TCP_SYMBOL);
#endif
#endif	/* PCB_TABLE */
#else	/* linux */
    FILE *in;
    char line [256];
    struct inpcb **pp;
    struct timeval now;
    static unsigned long Time_Of_Last_Reload = 0;

    /*
     * save some cpu-cycles, and reload after 5 secs...
     */
    gettimeofday (&now, (struct timezone *) 0);
    if (Time_Of_Last_Reload + 5 > now.tv_sec)
      {
	tcp_prev = inpcb_list;
	return;
      }
    Time_Of_Last_Reload = now.tv_sec;


    if (! (in = fopen ("/proc/net/tcp", "r")))
      {
	fprintf (stderr, "snmpd: cannot open /proc/net/tcp ...\n");
	tcp_prev = NULL;
	return;
      }

    /* free old chain: */
    while (inpcb_list)
      {
	struct inpcb *p = inpcb_list;
	inpcb_list = inpcb_list->INP_NEXT_SYMBOL;
	free (p);
      }

    /* scan proc-file and append: */

    pp = &inpcb_list;
    
    while (line == fgets (line, sizeof(line), in))
      {
	struct inpcb pcb, *nnew;
	static int linux_states [12] = { 0, 4, 2, 3, 6, 9, 10, 0, 5, 8, 1, 7 };
	int state, lp, fp, uid;

	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] : 1;
	pcb.uid = uid;
    
	nnew = (struct inpcb *) malloc (sizeof (struct inpcb));
	*nnew = pcb;
	nnew->INP_NEXT_SYMBOL = 0;

	*pp = nnew;
	pp = & nnew->INP_NEXT_SYMBOL;
      }

    fclose (in);

    /* first entry to go: */
    tcp_prev = inpcb_list;

#endif /* linux */
#else /*  !defined(CAN_USE_SYSCTL) || !defined(TCPCTL_PCBLIST) */
    {
	size_t len;
	int sname[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_PCBLIST };

	if (tcpcb_buf) {
	    free(tcpcb_buf);
	    tcpcb_buf = NULL;
	}
	xig = NULL;

	len = 0;
	if (sysctl(sname, 4, 0, &len, 0, 0) < 0) {
	    return;
	}
	if ((tcpcb_buf = malloc(len)) == NULL) {
	    return;
	}
	if (sysctl(sname, 4, tcpcb_buf, &len, 0, 0) < 0) {
	    free(tcpcb_buf);
	    tcpcb_buf = NULL;
	    return;
	}

	xig = (struct xinpgen *)tcpcb_buf;
	xig = (struct xinpgen *)((char *)xig + xig->xig_len);
	return;
    }
#endif /*  !defined(CAN_USE_SYSCTL) || !defined(TCPCTL_PCBLIST) */
}

int TCP_Scan_Next(int *State,
		  struct inpcb *RetInPcb)
{
#if !defined(CAN_USE_SYSCTL) || !defined(TCPCTL_PCBLIST)
	register struct inpcb *next;
#ifndef linux
	struct tcpcb tcpcb;

#ifdef PCB_TABLE
	if (tcp_next == tcp_head) {
#elif defined(freebsd2) || defined(netbsd1) || defined(openbsd2)
	if (tcp_inpcb.INP_NEXT_SYMBOL == NULL ||
	    tcp_inpcb.INP_NEXT_SYMBOL == (struct inpcb *) auto_nlist_value(TCP_SYMBOL)) {
#else
	if (tcp_inpcb.INP_NEXT_SYMBOL == (struct inpcb *) auto_nlist_value(TCP_SYMBOL)) {
#endif
	    return(0);	    /* "EOF" */
	}

#ifdef PCB_TABLE
	klookup((unsigned long)tcp_next, (char *)&tcp_inpcb, sizeof(tcp_inpcb));
	tcp_next = tcp_inpcb.inp_queue.cqe_next;
#else
	next = tcp_inpcb.INP_NEXT_SYMBOL;
	klookup((unsigned long)next, (char *)&tcp_inpcb, sizeof (tcp_inpcb));
#if !(defined(netbsd1) || defined(freebsd2)) || defined(openbsd2)
	if (tcp_inpcb.INP_PREV_SYMBOL != tcp_prev)	   /* ??? */
          return(-1); /* "FAILURE" */
#endif /*  !(defined(netbsd1) || defined(freebsd2) || defined(openbsd2)) */
#endif	/* PCB_TABLE */
	klookup ( (int)tcp_inpcb.inp_ppcb, (char *)&tcpcb, sizeof (tcpcb));
	*State = tcpcb.t_state;
#else /* linux */
	if (! tcp_prev)
	  return 0;

	tcp_inpcb = *tcp_prev;
	*State = tcp_inpcb.inp_state;
	next = tcp_inpcb.INP_NEXT_SYMBOL;
#endif

	*RetInPcb = tcp_inpcb;
#if !(defined(netbsd1) || defined(freebsd2) || defined(openbsd2))
	tcp_prev = next;
#endif
#else /*  !defined(CAN_USE_SYSCTL) || !defined(TCPCTL_PCBLIST) */
	/* Are we done? */
	if ((xig == NULL) ||
	    (xig->xig_len <= sizeof(struct xinpgen)))
	    return(0);  
	
	*State = ((struct xtcpcb *)xig)->xt_tp.t_state;
	*RetInPcb = ((struct xtcpcb *)xig)->xt_inp;
	
	/* Prepare for Next read */
	xig = (struct xinpgen *)((char *)xig + xig->xig_len);
#endif /*  !defined(CAN_USE_SYSCTL) || !defined(TCPCTL_PCBLIST) */
	return(1);	/* "OK" */
}
#endif /* solaris2 */
