/* Copyright (C) 2003 Juan Lang
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * This file implements statistics getting using the /proc filesystem exported
 * by Linux, and maybe other OSes.
 */

#include "config.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_NET_IF_ARP_H
#include <net/if_arp.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifdef HAVE_NETINET_TCP_FSM_H
#include <netinet/tcp_fsm.h>
#endif

#include "windef.h"
#include "winbase.h"
#include "iprtrmib.h"
#include "ifenum.h"
#include "ipstats.h"

#ifdef linux
#define TCPS_ESTABLISHED  1
#define TCPS_SYN_SENT     2
#define TCPS_SYN_RECEIVED 3
#define TCPS_FIN_WAIT_1   4
#define TCPS_FIN_WAIT_2   5
#define TCPS_TIME_WAIT    6
#define TCPS_CLOSED       7
#define TCPS_CLOSE_WAIT   8
#define TCPS_LAST_ACK     9
#define TCPS_LISTEN      10
#define TCPS_CLOSING     11
#endif

DWORD getInterfaceStatsByName(const char *name, PMIB_IFROW entry)
{
  FILE *fp;

  if (!name)
    return ERROR_INVALID_PARAMETER;
  if (!entry)
    return ERROR_INVALID_PARAMETER;

  /* get interface stats from /proc/net/dev, no error if can't
     no inUnknownProtos, outNUcastPkts, outQLen */
  fp = fopen("/proc/net/dev", "r");
  if (fp) {
    char buf[512] = { 0 }, *ptr;
    int nameLen = strlen(name), nameFound = 0;


    ptr = fgets(buf, sizeof(buf), fp);
    while (ptr && !nameFound) {
      while (*ptr && isspace(*ptr))
        ptr++;
      if (strncasecmp(ptr, name, nameLen) == 0 && *(ptr + nameLen) == ':')
        nameFound = 1;
      else
        ptr = fgets(buf, sizeof(buf), fp);
    }
    if (nameFound) {
      char *endPtr;

      ptr += nameLen + 1;
      if (ptr && *ptr) {
        entry->dwInOctets = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwInUcastPkts = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwInErrors = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwInDiscards = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        strtoul(ptr, &endPtr, 10); /* skip */
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        strtoul(ptr, &endPtr, 10); /* skip */
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        strtoul(ptr, &endPtr, 10); /* skip */
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwInNUcastPkts = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwOutOctets = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwOutUcastPkts = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwOutErrors = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
      if (ptr && *ptr) {
        entry->dwOutDiscards = strtoul(ptr, &endPtr, 10);
        ptr = endPtr;
      }
    }
    fclose(fp);
  }
  return NO_ERROR;
}

DWORD getICMPStats(MIB_ICMP *stats)
{
  FILE *fp;

  if (!stats)
    return ERROR_INVALID_PARAMETER;

  memset(stats, 0, sizeof(MIB_ICMP));
  /* get most of these stats from /proc/net/snmp, no error if can't */
  fp = fopen("/proc/net/snmp", "r");
  if (fp) {
    static const char hdr[] = "Icmp:";
    char buf[512] = { 0 }, *ptr;

    do {
      ptr = fgets(buf, sizeof(buf), fp);
    } while (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1));
    if (ptr) {
      /* last line was a header, get another */
      ptr = fgets(buf, sizeof(buf), fp);
      if (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1) == 0) {
        char *endPtr;

        ptr += sizeof(hdr);
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwMsgs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwErrors = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwDestUnreachs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwTimeExcds = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwParmProbs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwSrcQuenchs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwRedirects = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwEchoReps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwTimestamps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwTimestampReps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwAddrMasks = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpInStats.dwAddrMaskReps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwMsgs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwErrors = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwDestUnreachs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwTimeExcds = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwParmProbs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwSrcQuenchs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwRedirects = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwEchoReps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwTimestamps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwTimestampReps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwAddrMasks = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->stats.icmpOutStats.dwAddrMaskReps = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
      }
    }
    fclose(fp);
  }
  return NO_ERROR;
}

DWORD getIPStats(PMIB_IPSTATS stats)
{
  FILE *fp;

  if (!stats)
    return ERROR_INVALID_PARAMETER;

  memset(stats, 0, sizeof(MIB_IPSTATS));
  stats->dwNumIf = stats->dwNumAddr = getNumInterfaces();
  stats->dwNumRoutes = getNumRoutes();

  /* get most of these stats from /proc/net/snmp, no error if can't */
  fp = fopen("/proc/net/snmp", "r");
  if (fp) {
    static const char hdr[] = "Ip:";
    char buf[512] = { 0 }, *ptr;

    do {
      ptr = fgets(buf, sizeof(buf), fp);
    } while (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1));
    if (ptr) {
      /* last line was a header, get another */
      ptr = fgets(buf, sizeof(buf), fp);
      if (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1) == 0) {
        char *endPtr;

        ptr += sizeof(hdr);
        if (ptr && *ptr) {
          stats->dwForwarding = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwDefaultTTL = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInReceives = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInHdrErrors = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInAddrErrors = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwForwDatagrams = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInUnknownProtos = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInDiscards = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInDelivers = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwOutRequests = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwOutDiscards = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwOutNoRoutes = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwReasmTimeout = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwReasmReqds = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwReasmOks = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwReasmFails = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwFragOks = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwFragFails = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwFragCreates = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        /* hmm, no routingDiscards */
      }
    }
    fclose(fp);
  }
  return NO_ERROR;
}

DWORD getTCPStats(MIB_TCPSTATS *stats)
{
  FILE *fp;

  if (!stats)
    return ERROR_INVALID_PARAMETER;

  memset(stats, 0, sizeof(MIB_TCPSTATS));

  /* get from /proc/net/snmp, no error if can't */
  fp = fopen("/proc/net/snmp", "r");
  if (fp) {
    static const char hdr[] = "Tcp:";
    char buf[512] = { 0 }, *ptr;


    do {
      ptr = fgets(buf, sizeof(buf), fp);
    } while (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1));
    if (ptr) {
      /* last line was a header, get another */
      ptr = fgets(buf, sizeof(buf), fp);
      if (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1) == 0) {
        char *endPtr;

        ptr += sizeof(hdr);
        if (ptr && *ptr) {
          stats->dwRtoAlgorithm = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwRtoMin = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwRtoMin = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwMaxConn = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwActiveOpens = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwPassiveOpens = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwAttemptFails = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwEstabResets = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwCurrEstab = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInSegs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwOutSegs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwRetransSegs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInErrs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwOutRsts = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        stats->dwNumConns = getNumTcpEntries();
      }
    }
    fclose(fp);
  }
  return NO_ERROR;
}

DWORD getUDPStats(MIB_UDPSTATS *stats)
{
  FILE *fp;

  if (!stats)
    return ERROR_INVALID_PARAMETER;

  memset(stats, 0, sizeof(MIB_UDPSTATS));

  /* get from /proc/net/snmp, no error if can't */
  fp = fopen("/proc/net/snmp", "r");
  if (fp) {
    static const char hdr[] = "Udp:";
    char buf[512] = { 0 }, *ptr;


    do {
      ptr = fgets(buf, sizeof(buf), fp);
    } while (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1));
    if (ptr) {
      /* last line was a header, get another */
      ptr = fgets(buf, sizeof(buf), fp);
      if (ptr && strncasecmp(buf, hdr, sizeof(hdr) - 1) == 0) {
        char *endPtr;

        ptr += sizeof(hdr);
        if (ptr && *ptr) {
          stats->dwInDatagrams = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwNoPorts = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwInErrors = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwOutDatagrams = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
        if (ptr && *ptr) {
          stats->dwNumAddrs = strtoul(ptr, &endPtr, 10);
          ptr = endPtr;
        }
      }
    }
    fclose(fp);
  }
  return NO_ERROR;
}

static DWORD getNumWithOneHeader(const char *filename)
{
  FILE *fp;
  int ret = 0;

  fp = fopen(filename, "r");
  if (fp) {
    char buf[512] = { 0 }, *ptr;


    ptr = fgets(buf, sizeof(buf), fp);
    if (ptr) {
      do {
        ptr = fgets(buf, sizeof(buf), fp);
        if (ptr)
          ret++;
      } while (ptr);
    }
    fclose(fp);
  }
  return ret;
}

DWORD getNumRoutes(void)
{
  return getNumWithOneHeader("/proc/net/route");
}

RouteTable *getRouteTable(void)
{
  DWORD numRoutes = getNumRoutes();
  RouteTable *ret;

  ret = (RouteTable *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
   sizeof(RouteTable) + (numRoutes - 1) * sizeof(RouteEntry));
  if (ret) {
    FILE *fp;

    /* get from /proc/net/route, no error if can't */
    fp = fopen("/proc/net/route", "r");
    if (fp) {
      char buf[512] = { 0 }, *ptr;

      /* skip header line */
      ptr = fgets(buf, sizeof(buf), fp);
      while (ptr && ret->numRoutes < numRoutes) {
        ptr = fgets(buf, sizeof(buf), fp);
        if (ptr) {
          DWORD index;

          while (!isspace(*ptr))
            ptr++;
          *ptr = '\0';
          ptr++;
          if (getInterfaceIndexByName(buf, &index) == NO_ERROR) {
            char *endPtr;

            ret->routes[ret->numRoutes].ifIndex = index;
            if (*ptr) {
              ret->routes[ret->numRoutes].dest = strtoul(ptr, &endPtr, 16);
              ptr = endPtr;
            }
            if (ptr && *ptr) {
              ret->routes[ret->numRoutes].gateway = strtoul(ptr, &endPtr, 16);
              ptr = endPtr;
            }
            if (ptr && *ptr) {
              strtoul(ptr, &endPtr, 16); /* flags, skip */
              ptr = endPtr;
            }
            if (ptr && *ptr) {
              strtoul(ptr, &endPtr, 16); /* refcount, skip */
              ptr = endPtr;
            }
            if (ptr && *ptr) {
              strtoul(ptr, &endPtr, 16); /* use, skip */
              ptr = endPtr;
            }
            if (ptr && *ptr) {
              ret->routes[ret->numRoutes].metric = strtoul(ptr, &endPtr, 16);
              ptr = endPtr;
            }
            if (ptr && *ptr) {
              ret->routes[ret->numRoutes].mask = strtoul(ptr, &endPtr, 16);
              ptr = endPtr;
            }
            ret->numRoutes++;
          }
        }
      }
      fclose(fp);
    }
  }
  return ret;
}

DWORD getNumArpEntries(void)
{
  return getNumWithOneHeader("/proc/net/arp");
}

PMIB_IPNETTABLE getArpTable(void)
{
  DWORD numEntries = getNumArpEntries();
  PMIB_IPNETTABLE ret;

  ret = (PMIB_IPNETTABLE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
   sizeof(MIB_IPNETTABLE) + (numEntries - 1) * sizeof(MIB_IPNETROW));
  if (ret) {
    FILE *fp;

    /* get from /proc/net/arp, no error if can't */
    fp = fopen("/proc/net/arp", "r");
    if (fp) {
      char buf[512] = { 0 }, *ptr;

      /* skip header line */
      ptr = fgets(buf, sizeof(buf), fp);
      while (ptr && ret->dwNumEntries < numEntries) {
        ptr = fgets(buf, sizeof(buf), fp);
        if (ptr) {
          char *endPtr;

          ret->table[ret->dwNumEntries].dwAddr = inet_addr(ptr);
          while (ptr && *ptr && !isspace(*ptr))
            ptr++;

          if (ptr && *ptr) {
            strtoul(ptr, &endPtr, 16); /* hw type (skip) */
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            DWORD flags = strtoul(ptr, &endPtr, 16);

#ifdef ATF_COM
            if (flags & ATF_COM)
              ret->table[ret->dwNumEntries].dwType = MIB_IPNET_TYPE_DYNAMIC;
            else
#endif
#ifdef ATF_PERM
            if (flags & ATF_PERM)
              ret->table[ret->dwNumEntries].dwType = MIB_IPNET_TYPE_STATIC;
            else
#endif
              ret->table[ret->dwNumEntries].dwType = MIB_IPNET_TYPE_OTHER;

            ptr = endPtr;
          }
          while (ptr && *ptr && isspace(*ptr))
            ptr++;
          while (ptr && *ptr && !isspace(*ptr)) {
            DWORD byte = strtoul(ptr, &endPtr, 16);

            if (endPtr && *endPtr) {
              endPtr++;
              ret->table[ret->dwNumEntries].bPhysAddr[
               ret->table[ret->dwNumEntries].dwPhysAddrLen++] = byte & 0x0ff;
            }
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            strtoul(ptr, &endPtr, 16); /* mask (skip) */
            ptr = endPtr;
          }
          getInterfaceIndexByName(ptr, &ret->table[ret->dwNumEntries].dwIndex);
          ret->dwNumEntries++;
        }
      }
      fclose(fp);
    }
  }
  return ret;
}

DWORD getNumUdpEntries(void)
{
  return getNumWithOneHeader("/proc/net/udp");
}

PMIB_UDPTABLE getUdpTable(void)
{
  DWORD numEntries = getNumUdpEntries();
  PMIB_UDPTABLE ret;

  ret = (PMIB_UDPTABLE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
   sizeof(MIB_UDPTABLE) + (numEntries - 1) * sizeof(MIB_UDPROW));
  if (ret) {
    FILE *fp;

    /* get from /proc/net/udp, no error if can't */
    fp = fopen("/proc/net/udp", "r");
    if (fp) {
      char buf[512] = { 0 }, *ptr;

      /* skip header line */
      ptr = fgets(buf, sizeof(buf), fp);
      while (ptr && ret->dwNumEntries < numEntries) {
        ptr = fgets(buf, sizeof(buf), fp);
        if (ptr) {
          char *endPtr;

          if (ptr && *ptr) {
            strtoul(ptr, &endPtr, 16); /* skip */
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            ptr++;
            ret->table[ret->dwNumEntries].dwLocalAddr = strtoul(ptr, &endPtr,
             16);
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            ptr++;
            ret->table[ret->dwNumEntries].dwLocalPort = strtoul(ptr, &endPtr,
             16);
            ptr = endPtr;
          }
          ret->dwNumEntries++;
        }
      }
      fclose(fp);
    }
  }
  return ret;
}

DWORD getNumTcpEntries(void)
{
  return getNumWithOneHeader("/proc/net/tcp");
}

PMIB_TCPTABLE getTcpTable(void)
{
  DWORD numEntries = getNumTcpEntries();
  PMIB_TCPTABLE ret;

  ret = (PMIB_TCPTABLE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
   sizeof(MIB_TCPTABLE) + (numEntries - 1) * sizeof(MIB_TCPROW));
  if (ret) {
    FILE *fp;

    /* get from /proc/net/tcp, no error if can't */
    fp = fopen("/proc/net/tcp", "r");
    if (fp) {
      char buf[512] = { 0 }, *ptr;

      /* skip header line */
      ptr = fgets(buf, sizeof(buf), fp);
      while (ptr && ret->dwNumEntries < numEntries) {
        ptr = fgets(buf, sizeof(buf), fp);
        if (ptr) {
          char *endPtr;

          while (ptr && *ptr && *ptr != ':')
            ptr++;
          if (ptr && *ptr)
            ptr++;
          if (ptr && *ptr) {
            ret->table[ret->dwNumEntries].dwLocalAddr = strtoul(ptr, &endPtr,
             16);
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            ptr++;
            ret->table[ret->dwNumEntries].dwLocalPort = strtoul(ptr, &endPtr,
             16);
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            ret->table[ret->dwNumEntries].dwRemoteAddr = strtoul(ptr, &endPtr,
             16);
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            ptr++;
            ret->table[ret->dwNumEntries].dwRemotePort = strtoul(ptr, &endPtr,
             16);
            ptr = endPtr;
          }
          if (ptr && *ptr) {
            DWORD state = strtoul(ptr, &endPtr, 16);

            switch (state)
            {
              case TCPS_ESTABLISHED:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_ESTAB;
                break;
              case TCPS_SYN_SENT:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_SYN_SENT;
                break;
              case TCPS_SYN_RECEIVED:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_SYN_RCVD;
                break;
              case TCPS_FIN_WAIT_1:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_FIN_WAIT1;
                break;
              case TCPS_FIN_WAIT_2:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_FIN_WAIT2;
                break;
              case TCPS_TIME_WAIT:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_TIME_WAIT;
                break;
              case TCPS_CLOSED:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_CLOSED;
                break;
              case TCPS_CLOSE_WAIT:
                ret->table[ret->dwNumEntries].dwState =
                 MIB_TCP_STATE_CLOSE_WAIT;
                break;
              case TCPS_LAST_ACK:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_LAST_ACK;
                break;
              case TCPS_LISTEN:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_LISTEN;
                break;
              case TCPS_CLOSING:
                ret->table[ret->dwNumEntries].dwState = MIB_TCP_STATE_CLOSING;
                break;
            }
            ptr = endPtr;
          }
          ret->dwNumEntries++;
        }
      }
      fclose(fp);
    }
  }
  return ret;
}
