Logo Search packages:      
Sourcecode: wine version File versions  Download package

ipstats.c

/*
 * Copyright (C) 2003,2006 Juan Lang
 * Copyright (C) 2007 TransGaming Technologies Inc.
 * Copyright (C) 2009 Alexandre Julliard
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#ifdef HAVE_ALIAS_H
#include <alias.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_SYS_SOCKETVAR_H
#include <sys/socketvar.h>
#endif
#ifdef HAVE_SYS_TIMEOUT_H
#include <sys/timeout.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.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_DL_H
#include <net/if_dl.h>
#endif
#ifdef HAVE_NET_IF_TYPES_H
#include <net/if_types.h>
#endif
#ifdef HAVE_NET_ROUTE_H
#include <net/route.h>
#endif
#ifdef HAVE_NET_IF_ARP_H
#include <net/if_arp.h>
#endif
#ifdef HAVE_NETINET_IF_ETHER_H
#include <netinet/if_ether.h>
#endif
#ifdef HAVE_NETINET_IF_INARP_H
#include <netinet/if_inarp.h>
#endif
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#ifdef HAVE_NETINET_IP_VAR_H
#include <netinet/ip_var.h>
#endif
#ifdef HAVE_NETINET_TCP_FSM_H
#include <netinet/tcp_fsm.h>
#endif
#ifdef HAVE_NETINET_IN_PCB_H
#include <netinet/in_pcb.h>
#endif
#ifdef HAVE_NETINET_TCP_TIMER_H
#include <netinet/tcp_timer.h>
#endif
#ifdef HAVE_NETINET_TCP_VAR_H
#include <netinet/tcp_var.h>
#endif
#ifdef HAVE_NETINET_IP_ICMP_H
#include <netinet/ip_icmp.h>
#endif
#ifdef HAVE_NETINET_ICMP_VAR_H
#include <netinet/icmp_var.h>
#endif
#ifdef HAVE_NETINET_UDP_H
#include <netinet/udp.h>
#endif
#ifdef HAVE_NETINET_UDP_VAR_H
#include <netinet/udp_var.h>
#endif
#ifdef HAVE_SYS_PROTOSW_H
#include <sys/protosw.h>
#endif
#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif
#ifdef HAVE_KSTAT_H
#include <kstat.h>
#endif
#ifdef HAVE_INET_MIB2_H
#include <inet/mib2.h>
#endif
#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif
#ifdef HAVE_SYS_TIHDR_H
#include <sys/tihdr.h>
#endif

#ifndef ROUNDUP
#define ROUNDUP(a) \
      ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#endif
#ifndef ADVANCE
#define ADVANCE(x, n) (x += ROUNDUP(((struct sockaddr *)n)->sa_len))
#endif

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

#include "wine/debug.h"

#ifndef HAVE_NETINET_TCP_FSM_H
#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

#ifndef RTF_MULTICAST
#define RTF_MULTICAST 0 /* Not available on NetBSD/OpenBSD */
#endif

#ifndef RTF_LLINFO
#define RTF_LLINFO 0 /* Not available on FreeBSD 8 and above */
#endif

WINE_DEFAULT_DEBUG_CHANNEL(iphlpapi);

#ifdef HAVE_LIBKSTAT
static DWORD kstat_get_ui32( kstat_t *ksp, const char *name )
{
    unsigned int i;
    kstat_named_t *data = ksp->ks_data;

    for (i = 0; i < ksp->ks_ndata; i++)
        if (!strcmp( data[i].name, name )) return data[i].value.ui32;
    return 0;
}

static ULONGLONG kstat_get_ui64( kstat_t *ksp, const char *name )
{
    unsigned int i;
    kstat_named_t *data = ksp->ks_data;

    for (i = 0; i < ksp->ks_ndata; i++)
        if (!strcmp( data[i].name, name )) return data[i].value.ui64;
    return 0;
}
#endif

#if defined(HAVE_SYS_TIHDR_H) && defined(T_OPTMGMT_ACK)
static int open_streams_mib( const char *proto )
{
    int fd;
    struct strbuf buf;
    struct request
    {
        struct T_optmgmt_req req_header;
        struct opthdr        opt_header;
    } request;

    if ((fd = open( "/dev/arp", O_RDWR )) == -1)
    {
        WARN( "could not open /dev/arp: %s\n", strerror(errno) );
        return -1;
    }
    if (proto) ioctl( fd, I_PUSH, proto );

    request.req_header.PRIM_type  = T_SVR4_OPTMGMT_REQ;
    request.req_header.OPT_length = sizeof(request.opt_header);
    request.req_header.OPT_offset = FIELD_OFFSET( struct request, opt_header );
    request.req_header.MGMT_flags = T_CURRENT;
    request.opt_header.level      = MIB2_IP;
    request.opt_header.name       = 0;
    request.opt_header.len        = 0;

    buf.len = sizeof(request);
    buf.buf = (caddr_t)&request;
    if (putmsg( fd, &buf, NULL, 0 ) == -1)
    {
        WARN( "putmsg: %s\n", strerror(errno) );
        close( fd );
        fd = -1;
    }
    return fd;
}

static void *read_mib_entry( int fd, int level, int name, int *len )
{
    struct strbuf buf;
    void *data;
    int ret, flags = 0;

    struct reply
    {
        struct T_optmgmt_ack ack_header;
        struct opthdr        opt_header;
    } reply;

    for (;;)
    {
        buf.maxlen = sizeof(reply);
        buf.buf = (caddr_t)&reply;
        if ((ret = getmsg( fd, &buf, NULL, &flags )) < 0) return NULL;
        if (!(ret & MOREDATA)) return NULL;
        if (reply.ack_header.PRIM_type != T_OPTMGMT_ACK) return NULL;
        if (buf.len < sizeof(reply.ack_header)) return NULL;
        if (reply.ack_header.OPT_length < sizeof(reply.opt_header)) return NULL;

        if (!(data = HeapAlloc( GetProcessHeap(), 0, reply.opt_header.len ))) return NULL;
        buf.maxlen = reply.opt_header.len;
        buf.buf = (caddr_t)data;
        flags = 0;
        if (getmsg( fd, NULL, &buf, &flags ) >= 0 &&
            reply.opt_header.level == level &&
            reply.opt_header.name == name)
        {
            *len = buf.len;
            return data;
        }
        HeapFree( GetProcessHeap(), 0, data );
    }
}
#endif /* HAVE_SYS_TIHDR_H && T_OPTMGMT_ACK */

DWORD getInterfaceStatsByName(const char *name, PMIB_IFROW entry)
{
    DWORD ret = ERROR_NOT_SUPPORTED;

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

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/dev", "r")))
        {
            DWORD skip;
            char buf[512], *ptr;
            int nameLen = strlen(name);

            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                while (*ptr && isspace(*ptr)) ptr++;
                if (strncasecmp(ptr, name, nameLen) == 0 && *(ptr + nameLen) == ':')
                {
                    ptr += nameLen + 1;
                    sscanf( ptr, "%u %u %u %u %u %u %u %u %u %u %u %u",
                            &entry->dwInOctets, &entry->dwInUcastPkts,
                            &entry->dwInErrors, &entry->dwInDiscards,
                            &skip, &skip, &skip,
                            &entry->dwInNUcastPkts, &entry->dwOutOctets,
                            &entry->dwOutUcastPkts, &entry->dwOutErrors,
                            &entry->dwOutDiscards );
                    break;
                }
            }
            fclose(fp);
            ret = NO_ERROR;
        }
    }
#elif defined(HAVE_LIBKSTAT)
    {
        kstat_ctl_t *kc;
        kstat_t *ksp;

        if ((kc = kstat_open()) &&
            (ksp = kstat_lookup( kc, NULL, -1, (char *)name )) &&
            kstat_read( kc, ksp, NULL ) != -1 &&
            ksp->ks_type == KSTAT_TYPE_NAMED)
        {
            entry->dwMtu             = 1500;  /* FIXME */
            entry->dwSpeed           = min( kstat_get_ui64( ksp, "ifspeed" ), ~0u );
            entry->dwInOctets        = kstat_get_ui32( ksp, "rbytes" );
            entry->dwInNUcastPkts    = kstat_get_ui32( ksp, "multircv" );
            entry->dwInNUcastPkts   += kstat_get_ui32( ksp, "brdcstrcv" );
            entry->dwInUcastPkts     = kstat_get_ui32( ksp, "ipackets" ) - entry->dwInNUcastPkts;
            entry->dwInDiscards      = kstat_get_ui32( ksp, "norcvbuf" );
            entry->dwInErrors        = kstat_get_ui32( ksp, "ierrors" );
            entry->dwInUnknownProtos = kstat_get_ui32( ksp, "unknowns" );
            entry->dwOutOctets       = kstat_get_ui32( ksp, "obytes" );
            entry->dwOutNUcastPkts   = kstat_get_ui32( ksp, "multixmt" );
            entry->dwOutNUcastPkts  += kstat_get_ui32( ksp, "brdcstxmt" );
            entry->dwOutUcastPkts    = kstat_get_ui32( ksp, "opackets" ) - entry->dwOutNUcastPkts;
            entry->dwOutDiscards     = 0;  /* FIXME */
            entry->dwOutErrors       = kstat_get_ui32( ksp, "oerrors" );
            entry->dwOutQLen         = kstat_get_ui32( ksp, "noxmtbuf" );
            ret = NO_ERROR;
        }
        if (kc) kstat_close( kc );
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(NET_RT_IFLIST)
    {
        int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_IFLIST, if_nametoindex(name)};
#define MIB_LEN (sizeof(mib) / sizeof(mib[0]))

        size_t needed;
        char *buf = NULL, *end;
        struct if_msghdr *ifm;
        struct if_data ifdata;

        if(sysctl(mib, MIB_LEN, NULL, &needed, NULL, 0) == -1)
        {
            ERR ("failed to get size of iflist\n");
            goto done;
        }
        buf = HeapAlloc (GetProcessHeap (), 0, needed);
        if (!buf)
        {
            ret = ERROR_OUTOFMEMORY;
            goto done;
        }
        if(sysctl(mib, MIB_LEN, buf, &needed, NULL, 0) == -1)
        {
            ERR ("failed to get iflist\n");
            goto done;
        }
        for ( end = buf + needed; buf < end; buf += ifm->ifm_msglen)
        {
            ifm = (struct if_msghdr *) buf;
            if(ifm->ifm_type == RTM_IFINFO && ifm->ifm_data.ifi_type == IFT_ETHER)
            {
                ifdata = ifm->ifm_data;
                entry->dwMtu = ifdata.ifi_mtu;
                entry->dwSpeed = ifdata.ifi_baudrate;
                entry->dwInOctets = ifdata.ifi_ibytes;
                entry->dwInErrors = ifdata.ifi_ierrors;
                entry->dwInDiscards = ifdata.ifi_iqdrops;
                entry->dwInUcastPkts = ifdata.ifi_ipackets;
                entry->dwInNUcastPkts = ifdata.ifi_imcasts;
                entry->dwOutOctets = ifdata.ifi_obytes;
                entry->dwOutUcastPkts = ifdata.ifi_opackets;
                entry->dwOutErrors = ifdata.ifi_oerrors;
                ret = NO_ERROR;
                break;
            }
        }
    done:
        HeapFree (GetProcessHeap (), 0, buf);
    }
#else
    FIXME( "unimplemented\n" );
#endif
    return ret;
}


/******************************************************************
 *    GetIcmpStatistics (IPHLPAPI.@)
 *
 * Get the ICMP statistics for the local computer.
 *
 * PARAMS
 *  stats [Out] buffer for ICMP statistics
 *
 * RETURNS
 *  Success: NO_ERROR
 *  Failure: error code from winerror.h
 */
DWORD WINAPI GetIcmpStatistics(PMIB_ICMP stats)
{
    DWORD ret = ERROR_NOT_SUPPORTED;

    if (!stats) return ERROR_INVALID_PARAMETER;
    memset( stats, 0, sizeof(MIB_ICMP) );

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/snmp", "r")))
        {
            static const char hdr[] = "Icmp:";
            char buf[512], *ptr;

            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                if (strncasecmp(buf, hdr, sizeof(hdr) - 1)) continue;
                /* last line was a header, get another */
                if (!(ptr = fgets(buf, sizeof(buf), fp))) break;
                if (!strncasecmp(buf, hdr, sizeof(hdr) - 1))
                {
                    ptr += sizeof(hdr);
                    sscanf( ptr, "%u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
                            &stats->stats.icmpInStats.dwMsgs,
                            &stats->stats.icmpInStats.dwErrors,
                            &stats->stats.icmpInStats.dwDestUnreachs,
                            &stats->stats.icmpInStats.dwTimeExcds,
                            &stats->stats.icmpInStats.dwParmProbs,
                            &stats->stats.icmpInStats.dwSrcQuenchs,
                            &stats->stats.icmpInStats.dwRedirects,
                            &stats->stats.icmpInStats.dwEchoReps,
                            &stats->stats.icmpInStats.dwTimestamps,
                            &stats->stats.icmpInStats.dwTimestampReps,
                            &stats->stats.icmpInStats.dwAddrMasks,
                            &stats->stats.icmpInStats.dwAddrMaskReps,
                            &stats->stats.icmpOutStats.dwMsgs,
                            &stats->stats.icmpOutStats.dwErrors,
                            &stats->stats.icmpOutStats.dwDestUnreachs,
                            &stats->stats.icmpOutStats.dwTimeExcds,
                            &stats->stats.icmpOutStats.dwParmProbs,
                            &stats->stats.icmpOutStats.dwSrcQuenchs,
                            &stats->stats.icmpOutStats.dwRedirects,
                            &stats->stats.icmpOutStats.dwEchoReps,
                            &stats->stats.icmpOutStats.dwTimestamps,
                            &stats->stats.icmpOutStats.dwTimestampReps,
                            &stats->stats.icmpOutStats.dwAddrMasks,
                            &stats->stats.icmpOutStats.dwAddrMaskReps );
                    break;
                }
            }
            fclose(fp);
            ret = NO_ERROR;
        }
    }
#elif defined(HAVE_LIBKSTAT)
    {
        static char ip[] = "ip", icmp[] = "icmp";
        kstat_ctl_t *kc;
        kstat_t *ksp;

        if ((kc = kstat_open()) &&
            (ksp = kstat_lookup( kc, ip, 0, icmp )) &&
            kstat_read( kc, ksp, NULL ) != -1 &&
            ksp->ks_type == KSTAT_TYPE_NAMED)
        {
            stats->stats.icmpInStats.dwMsgs           = kstat_get_ui32( ksp, "inMsgs" );
            stats->stats.icmpInStats.dwErrors         = kstat_get_ui32( ksp, "inErrors" );
            stats->stats.icmpInStats.dwDestUnreachs   = kstat_get_ui32( ksp, "inDestUnreachs" );
            stats->stats.icmpInStats.dwTimeExcds      = kstat_get_ui32( ksp, "inTimeExcds" );
            stats->stats.icmpInStats.dwParmProbs      = kstat_get_ui32( ksp, "inParmProbs" );
            stats->stats.icmpInStats.dwSrcQuenchs     = kstat_get_ui32( ksp, "inSrcQuenchs" );
            stats->stats.icmpInStats.dwRedirects      = kstat_get_ui32( ksp, "inRedirects" );
            stats->stats.icmpInStats.dwEchos          = kstat_get_ui32( ksp, "inEchos" );
            stats->stats.icmpInStats.dwEchoReps       = kstat_get_ui32( ksp, "inEchoReps" );
            stats->stats.icmpInStats.dwTimestamps     = kstat_get_ui32( ksp, "inTimestamps" );
            stats->stats.icmpInStats.dwTimestampReps  = kstat_get_ui32( ksp, "inTimestampReps" );
            stats->stats.icmpInStats.dwAddrMasks      = kstat_get_ui32( ksp, "inAddrMasks" );
            stats->stats.icmpInStats.dwAddrMaskReps   = kstat_get_ui32( ksp, "inAddrMaskReps" );
            stats->stats.icmpOutStats.dwMsgs          = kstat_get_ui32( ksp, "outMsgs" );
            stats->stats.icmpOutStats.dwErrors        = kstat_get_ui32( ksp, "outErrors" );
            stats->stats.icmpOutStats.dwDestUnreachs  = kstat_get_ui32( ksp, "outDestUnreachs" );
            stats->stats.icmpOutStats.dwTimeExcds     = kstat_get_ui32( ksp, "outTimeExcds" );
            stats->stats.icmpOutStats.dwParmProbs     = kstat_get_ui32( ksp, "outParmProbs" );
            stats->stats.icmpOutStats.dwSrcQuenchs    = kstat_get_ui32( ksp, "outSrcQuenchs" );
            stats->stats.icmpOutStats.dwRedirects     = kstat_get_ui32( ksp, "outRedirects" );
            stats->stats.icmpOutStats.dwEchos         = kstat_get_ui32( ksp, "outEchos" );
            stats->stats.icmpOutStats.dwEchoReps      = kstat_get_ui32( ksp, "outEchoReps" );
            stats->stats.icmpOutStats.dwTimestamps    = kstat_get_ui32( ksp, "outTimestamps" );
            stats->stats.icmpOutStats.dwTimestampReps = kstat_get_ui32( ksp, "outTimestampReps" );
            stats->stats.icmpOutStats.dwAddrMasks     = kstat_get_ui32( ksp, "outAddrMasks" );
            stats->stats.icmpOutStats.dwAddrMaskReps  = kstat_get_ui32( ksp, "outAddrMaskReps" );
            ret = NO_ERROR;
        }
        if (kc) kstat_close( kc );
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(ICMPCTL_STATS)
    {
        int mib[] = {CTL_NET, PF_INET, IPPROTO_ICMP, ICMPCTL_STATS};
#define MIB_LEN (sizeof(mib) / sizeof(mib[0]))
        struct icmpstat icmp_stat;
        size_t needed  = sizeof(icmp_stat);
        int i;

        if(sysctl(mib, MIB_LEN, &icmp_stat, &needed, NULL, 0) != -1)
        {
            /*in stats */
            stats->stats.icmpInStats.dwMsgs = icmp_stat.icps_badcode + icmp_stat.icps_checksum + icmp_stat.icps_tooshort + icmp_stat.icps_badlen;
            for(i = 0; i <= ICMP_MAXTYPE; i++)
                stats->stats.icmpInStats.dwMsgs += icmp_stat.icps_inhist[i];

            stats->stats.icmpInStats.dwErrors = icmp_stat.icps_badcode + icmp_stat.icps_tooshort + icmp_stat.icps_checksum + icmp_stat.icps_badlen;

            stats->stats.icmpInStats.dwDestUnreachs = icmp_stat.icps_inhist[ICMP_UNREACH];
            stats->stats.icmpInStats.dwTimeExcds = icmp_stat.icps_inhist[ICMP_TIMXCEED];
            stats->stats.icmpInStats.dwParmProbs = icmp_stat.icps_inhist[ICMP_PARAMPROB];
            stats->stats.icmpInStats.dwSrcQuenchs = icmp_stat.icps_inhist[ICMP_SOURCEQUENCH];
            stats->stats.icmpInStats.dwRedirects = icmp_stat.icps_inhist[ICMP_REDIRECT];
            stats->stats.icmpInStats.dwEchos = icmp_stat.icps_inhist[ICMP_ECHO];
            stats->stats.icmpInStats.dwEchoReps = icmp_stat.icps_inhist[ICMP_ECHOREPLY];
            stats->stats.icmpInStats.dwTimestamps = icmp_stat.icps_inhist[ICMP_TSTAMP];
            stats->stats.icmpInStats.dwTimestampReps = icmp_stat.icps_inhist[ICMP_TSTAMPREPLY];
            stats->stats.icmpInStats.dwAddrMasks = icmp_stat.icps_inhist[ICMP_MASKREQ];
            stats->stats.icmpInStats.dwAddrMaskReps = icmp_stat.icps_inhist[ICMP_MASKREPLY];

#ifdef HAVE_ICPS_OUTHIST
            /* out stats */
            stats->stats.icmpOutStats.dwMsgs = icmp_stat.icps_oldshort + icmp_stat.icps_oldicmp;
            for(i = 0; i <= ICMP_MAXTYPE; i++)
                stats->stats.icmpOutStats.dwMsgs += icmp_stat.icps_outhist[i];

            stats->stats.icmpOutStats.dwErrors = icmp_stat.icps_oldshort + icmp_stat.icps_oldicmp;

            stats->stats.icmpOutStats.dwDestUnreachs = icmp_stat.icps_outhist[ICMP_UNREACH];
            stats->stats.icmpOutStats.dwTimeExcds = icmp_stat.icps_outhist[ICMP_TIMXCEED];
            stats->stats.icmpOutStats.dwParmProbs = icmp_stat.icps_outhist[ICMP_PARAMPROB];
            stats->stats.icmpOutStats.dwSrcQuenchs = icmp_stat.icps_outhist[ICMP_SOURCEQUENCH];
            stats->stats.icmpOutStats.dwRedirects = icmp_stat.icps_outhist[ICMP_REDIRECT];
            stats->stats.icmpOutStats.dwEchos = icmp_stat.icps_outhist[ICMP_ECHO];
            stats->stats.icmpOutStats.dwEchoReps = icmp_stat.icps_outhist[ICMP_ECHOREPLY];
            stats->stats.icmpOutStats.dwTimestamps = icmp_stat.icps_outhist[ICMP_TSTAMP];
            stats->stats.icmpOutStats.dwTimestampReps = icmp_stat.icps_outhist[ICMP_TSTAMPREPLY];
            stats->stats.icmpOutStats.dwAddrMasks = icmp_stat.icps_outhist[ICMP_MASKREQ];
            stats->stats.icmpOutStats.dwAddrMaskReps = icmp_stat.icps_outhist[ICMP_MASKREPLY];
#endif /* ICPS_OUTHIST */
            ret = NO_ERROR;
        }
    }
#else /* ICMPCTL_STATS */
    FIXME( "unimplemented\n" );
#endif
    return ret;
}


/******************************************************************
 *    GetIpStatistics (IPHLPAPI.@)
 *
 * Get the IP statistics for the local computer.
 *
 * PARAMS
 *  stats [Out] buffer for IP statistics
 *
 * RETURNS
 *  Success: NO_ERROR
 *  Failure: error code from winerror.h
 */
DWORD WINAPI GetIpStatistics(PMIB_IPSTATS stats)
{
    DWORD ret = ERROR_NOT_SUPPORTED;
    MIB_IPFORWARDTABLE *fwd_table;

    if (!stats) return ERROR_INVALID_PARAMETER;
    memset( stats, 0, sizeof(*stats) );

    stats->dwNumIf = stats->dwNumAddr = getNumInterfaces();
    if (!AllocateAndGetIpForwardTableFromStack( &fwd_table, FALSE, GetProcessHeap(), 0 ))
    {
        stats->dwNumRoutes = fwd_table->dwNumEntries;
        HeapFree( GetProcessHeap(), 0, fwd_table );
    }

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/snmp", "r")))
        {
            static const char hdr[] = "Ip:";
            char buf[512], *ptr;

            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                if (strncasecmp(buf, hdr, sizeof(hdr) - 1)) continue;
                /* last line was a header, get another */
                if (!(ptr = fgets(buf, sizeof(buf), fp))) break;
                if (!strncasecmp(buf, hdr, sizeof(hdr) - 1))
                {
                    ptr += sizeof(hdr);
                    sscanf( ptr, "%u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
                            &stats->dwForwarding,
                            &stats->dwDefaultTTL,
                            &stats->dwInReceives,
                            &stats->dwInHdrErrors,
                            &stats->dwInAddrErrors,
                            &stats->dwForwDatagrams,
                            &stats->dwInUnknownProtos,
                            &stats->dwInDiscards,
                            &stats->dwInDelivers,
                            &stats->dwOutRequests,
                            &stats->dwOutDiscards,
                            &stats->dwOutNoRoutes,
                            &stats->dwReasmTimeout,
                            &stats->dwReasmReqds,
                            &stats->dwReasmOks,
                            &stats->dwReasmFails,
                            &stats->dwFragOks,
                            &stats->dwFragFails,
                            &stats->dwFragCreates );
                    /* hmm, no routingDiscards */
                    break;
                }
            }
            fclose(fp);
            ret = NO_ERROR;
        }
    }
#elif defined(HAVE_LIBKSTAT)
    {
        static char ip[] = "ip";
        kstat_ctl_t *kc;
        kstat_t *ksp;

        if ((kc = kstat_open()) &&
            (ksp = kstat_lookup( kc, ip, 0, ip )) &&
            kstat_read( kc, ksp, NULL ) != -1 &&
            ksp->ks_type == KSTAT_TYPE_NAMED)
        {
            stats->dwForwarding      = kstat_get_ui32( ksp, "forwarding" );
            stats->dwDefaultTTL      = kstat_get_ui32( ksp, "defaultTTL" );
            stats->dwInReceives      = kstat_get_ui32( ksp, "inReceives" );
            stats->dwInHdrErrors     = kstat_get_ui32( ksp, "inHdrErrors" );
            stats->dwInAddrErrors    = kstat_get_ui32( ksp, "inAddrErrors" );
            stats->dwForwDatagrams   = kstat_get_ui32( ksp, "forwDatagrams" );
            stats->dwInUnknownProtos = kstat_get_ui32( ksp, "inUnknownProtos" );
            stats->dwInDiscards      = kstat_get_ui32( ksp, "inDiscards" );
            stats->dwInDelivers      = kstat_get_ui32( ksp, "inDelivers" );
            stats->dwOutRequests     = kstat_get_ui32( ksp, "outRequests" );
            stats->dwRoutingDiscards = kstat_get_ui32( ksp, "routingDiscards" );
            stats->dwOutDiscards     = kstat_get_ui32( ksp, "outDiscards" );
            stats->dwOutNoRoutes     = kstat_get_ui32( ksp, "outNoRoutes" );
            stats->dwReasmTimeout    = kstat_get_ui32( ksp, "reasmTimeout" );
            stats->dwReasmReqds      = kstat_get_ui32( ksp, "reasmReqds" );
            stats->dwReasmOks        = kstat_get_ui32( ksp, "reasmOKs" );
            stats->dwReasmFails      = kstat_get_ui32( ksp, "reasmFails" );
            stats->dwFragOks         = kstat_get_ui32( ksp, "fragOKs" );
            stats->dwFragFails       = kstat_get_ui32( ksp, "fragFails" );
            stats->dwFragCreates     = kstat_get_ui32( ksp, "fragCreates" );
            ret = NO_ERROR;
        }
        if (kc) kstat_close( kc );
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(IPCTL_STATS)
    {
        int mib[] = {CTL_NET, PF_INET, IPPROTO_IP, IPCTL_STATS};
#define MIB_LEN (sizeof(mib) / sizeof(mib[0]))
        int ip_ttl, ip_forwarding;
        struct ipstat ip_stat;
        size_t needed;

        needed = sizeof(ip_stat);
        if(sysctl(mib, MIB_LEN, &ip_stat, &needed, NULL, 0) == -1)
        {
            ERR ("failed to get ipstat\n");
            return ERROR_NOT_SUPPORTED;
        }

        needed = sizeof(ip_ttl);
        if (sysctlbyname ("net.inet.ip.ttl", &ip_ttl, &needed, NULL, 0) == -1)
        {
            ERR ("failed to get ip Default TTL\n");
            return ERROR_NOT_SUPPORTED;
        }

        needed = sizeof(ip_forwarding);
        if (sysctlbyname ("net.inet.ip.forwarding", &ip_forwarding, &needed, NULL, 0) == -1)
        {
            ERR ("failed to get ip forwarding\n");
            return ERROR_NOT_SUPPORTED;
        }

        stats->dwForwarding = ip_forwarding;
        stats->dwDefaultTTL = ip_ttl;
        stats->dwInDelivers = ip_stat.ips_delivered;
        stats->dwInHdrErrors = ip_stat.ips_badhlen + ip_stat.ips_badsum + ip_stat.ips_tooshort + ip_stat.ips_badlen;
        stats->dwInAddrErrors = ip_stat.ips_cantforward;
        stats->dwInReceives = ip_stat.ips_total;
        stats->dwForwDatagrams = ip_stat.ips_forward;
        stats->dwInUnknownProtos = ip_stat.ips_noproto;
        stats->dwInDiscards = ip_stat.ips_fragdropped;
        stats->dwOutDiscards = ip_stat.ips_odropped;
        stats->dwReasmOks = ip_stat.ips_reassembled;
        stats->dwFragOks = ip_stat.ips_fragmented;
        stats->dwFragFails = ip_stat.ips_cantfrag;
        stats->dwReasmTimeout = ip_stat.ips_fragtimeout;
        stats->dwOutNoRoutes = ip_stat.ips_noroute;
        stats->dwOutRequests = ip_stat.ips_localout;
        stats->dwReasmReqds = ip_stat.ips_fragments;
        ret = NO_ERROR;
    }
#else
    FIXME( "unimplemented\n" );
#endif
    return ret;
}


/******************************************************************
 *    GetTcpStatistics (IPHLPAPI.@)
 *
 * Get the TCP statistics for the local computer.
 *
 * PARAMS
 *  stats [Out] buffer for TCP statistics
 *
 * RETURNS
 *  Success: NO_ERROR
 *  Failure: error code from winerror.h
 */
DWORD WINAPI GetTcpStatistics(PMIB_TCPSTATS stats)
{
    DWORD ret = ERROR_NOT_SUPPORTED;

    if (!stats) return ERROR_INVALID_PARAMETER;
    memset( stats, 0, sizeof(*stats) );

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/snmp", "r")))
        {
            static const char hdr[] = "Tcp:";
            MIB_TCPTABLE *tcp_table;
            char buf[512], *ptr;

            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                if (strncasecmp(buf, hdr, sizeof(hdr) - 1)) continue;
                /* last line was a header, get another */
                if (!(ptr = fgets(buf, sizeof(buf), fp))) break;
                if (!strncasecmp(buf, hdr, sizeof(hdr) - 1))
                {
                    ptr += sizeof(hdr);
                    sscanf( ptr, "%u %u %u %u %u %u %u %u %u %u %u %u %u %u",
                            &stats->dwRtoAlgorithm,
                            &stats->dwRtoMin,
                            &stats->dwRtoMax,
                            &stats->dwMaxConn,
                            &stats->dwActiveOpens,
                            &stats->dwPassiveOpens,
                            &stats->dwAttemptFails,
                            &stats->dwEstabResets,
                            &stats->dwCurrEstab,
                            &stats->dwInSegs,
                            &stats->dwOutSegs,
                            &stats->dwRetransSegs,
                            &stats->dwInErrs,
                            &stats->dwOutRsts );
                    break;
                }
            }
            if (!AllocateAndGetTcpTableFromStack( &tcp_table, FALSE, GetProcessHeap(), 0 ))
            {
                stats->dwNumConns = tcp_table->dwNumEntries;
                HeapFree( GetProcessHeap(), 0, tcp_table );
            }
            fclose(fp);
            ret = NO_ERROR;
        }
    }
#elif defined(HAVE_LIBKSTAT)
    {
        static char tcp[] = "tcp";
        kstat_ctl_t *kc;
        kstat_t *ksp;

        if ((kc = kstat_open()) &&
            (ksp = kstat_lookup( kc, tcp, 0, tcp )) &&
            kstat_read( kc, ksp, NULL ) != -1 &&
            ksp->ks_type == KSTAT_TYPE_NAMED)
        {
            stats->dwRtoAlgorithm = kstat_get_ui32( ksp, "rtoAlgorithm" );
            stats->dwRtoMin       = kstat_get_ui32( ksp, "rtoMin" );
            stats->dwRtoMax       = kstat_get_ui32( ksp, "rtoMax" );
            stats->dwMaxConn      = kstat_get_ui32( ksp, "maxConn" );
            stats->dwActiveOpens  = kstat_get_ui32( ksp, "activeOpens" );
            stats->dwPassiveOpens = kstat_get_ui32( ksp, "passiveOpens" );
            stats->dwAttemptFails = kstat_get_ui32( ksp, "attemptFails" );
            stats->dwEstabResets  = kstat_get_ui32( ksp, "estabResets" );
            stats->dwCurrEstab    = kstat_get_ui32( ksp, "currEstab" );
            stats->dwInSegs       = kstat_get_ui32( ksp, "inSegs" );
            stats->dwOutSegs      = kstat_get_ui32( ksp, "outSegs" );
            stats->dwRetransSegs  = kstat_get_ui32( ksp, "retransSegs" );
            stats->dwInErrs       = kstat_get_ui32( ksp, "inErrs" );
            stats->dwOutRsts      = kstat_get_ui32( ksp, "outRsts" );
            stats->dwNumConns     = kstat_get_ui32( ksp, "connTableSize" );
            ret = NO_ERROR;
        }
        if (kc) kstat_close( kc );
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(UDPCTL_STATS)
    {
#ifndef TCPTV_MIN  /* got removed in Mac OS X for some reason */
#define TCPTV_MIN 2
#define TCPTV_REXMTMAX 128
#endif
        int mib[] = {CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_STATS};
#define MIB_LEN (sizeof(mib) / sizeof(mib[0]))
#define hz 1000
        struct tcpstat tcp_stat;
        size_t needed = sizeof(tcp_stat);

        if(sysctl(mib, MIB_LEN, &tcp_stat, &needed, NULL, 0) != -1)
        {
            stats->dwRtoAlgorithm = MIB_TCP_RTO_VANJ;
            stats->dwRtoMin = TCPTV_MIN;
            stats->dwRtoMax = TCPTV_REXMTMAX;
            stats->dwMaxConn = -1;
            stats->dwActiveOpens = tcp_stat.tcps_connattempt;
            stats->dwPassiveOpens = tcp_stat.tcps_accepts;
            stats->dwAttemptFails = tcp_stat.tcps_conndrops;
            stats->dwEstabResets = tcp_stat.tcps_drops;
            stats->dwCurrEstab = 0;
            stats->dwInSegs = tcp_stat.tcps_rcvtotal;
            stats->dwOutSegs = tcp_stat.tcps_sndtotal - tcp_stat.tcps_sndrexmitpack;
            stats->dwRetransSegs = tcp_stat.tcps_sndrexmitpack;
            stats->dwInErrs = tcp_stat.tcps_rcvbadsum + tcp_stat.tcps_rcvbadoff + tcp_stat.tcps_rcvmemdrop + tcp_stat.tcps_rcvshort;
            stats->dwOutRsts = tcp_stat.tcps_sndctrl - tcp_stat.tcps_closed;
            stats->dwNumConns = tcp_stat.tcps_connects;
            ret = NO_ERROR;
        }
        else ERR ("failed to get tcpstat\n");
    }
#else
    FIXME( "unimplemented\n" );
#endif
    return ret;
}


/******************************************************************
 *    GetUdpStatistics (IPHLPAPI.@)
 *
 * Get the UDP statistics for the local computer.
 *
 * PARAMS
 *  stats [Out] buffer for UDP statistics
 *
 * RETURNS
 *  Success: NO_ERROR
 *  Failure: error code from winerror.h
 */
DWORD WINAPI GetUdpStatistics(PMIB_UDPSTATS stats)
{
    DWORD ret = ERROR_NOT_SUPPORTED;

    if (!stats) return ERROR_INVALID_PARAMETER;
    memset( stats, 0, sizeof(*stats) );

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/snmp", "r")))
        {
            static const char hdr[] = "Udp:";
            char buf[512], *ptr;

            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                if (strncasecmp(buf, hdr, sizeof(hdr) - 1)) continue;
                /* last line was a header, get another */
                if (!(ptr = fgets(buf, sizeof(buf), fp))) break;
                if (!strncasecmp(buf, hdr, sizeof(hdr) - 1))
                {
                    ptr += sizeof(hdr);
                    sscanf( ptr, "%u %u %u %u %u",
                            &stats->dwInDatagrams, &stats->dwNoPorts,
                            &stats->dwInErrors, &stats->dwOutDatagrams, &stats->dwNumAddrs );
                    break;
                }
            }
            fclose(fp);
            ret = NO_ERROR;
        }
    }
#elif defined(HAVE_LIBKSTAT)
    {
        static char udp[] = "udp";
        kstat_ctl_t *kc;
        kstat_t *ksp;
        MIB_UDPTABLE *udp_table;

        if ((kc = kstat_open()) &&
            (ksp = kstat_lookup( kc, udp, 0, udp )) &&
            kstat_read( kc, ksp, NULL ) != -1 &&
            ksp->ks_type == KSTAT_TYPE_NAMED)
        {
            stats->dwInDatagrams  = kstat_get_ui32( ksp, "inDatagrams" );
            stats->dwNoPorts      = 0;  /* FIXME */
            stats->dwInErrors     = kstat_get_ui32( ksp, "inErrors" );
            stats->dwOutDatagrams = kstat_get_ui32( ksp, "outDatagrams" );
            if (!AllocateAndGetUdpTableFromStack( &udp_table, FALSE, GetProcessHeap(), 0 ))
            {
                stats->dwNumAddrs = udp_table->dwNumEntries;
                HeapFree( GetProcessHeap(), 0, udp_table );
            }
            ret = NO_ERROR;
        }
        if (kc) kstat_close( kc );
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(UDPCTL_STATS)
    {
        int mib[] = {CTL_NET, PF_INET, IPPROTO_UDP, UDPCTL_STATS};
#define MIB_LEN (sizeof(mib) / sizeof(mib[0]))
        struct udpstat udp_stat;
        MIB_UDPTABLE *udp_table;
        size_t needed = sizeof(udp_stat);

        if(sysctl(mib, MIB_LEN, &udp_stat, &needed, NULL, 0) != -1)
        {
            stats->dwInDatagrams = udp_stat.udps_ipackets;
            stats->dwOutDatagrams = udp_stat.udps_opackets;
            stats->dwNoPorts = udp_stat.udps_noport;
            stats->dwInErrors = udp_stat.udps_hdrops + udp_stat.udps_badsum + udp_stat.udps_fullsock + udp_stat.udps_badlen;
            if (!AllocateAndGetUdpTableFromStack( &udp_table, FALSE, GetProcessHeap(), 0 ))
            {
                stats->dwNumAddrs = udp_table->dwNumEntries;
                HeapFree( GetProcessHeap(), 0, udp_table );
            }
            ret = NO_ERROR;
        }
        else ERR ("failed to get udpstat\n");
    }
#else
    FIXME( "unimplemented\n" );
#endif
    return ret;
}


static MIB_IPFORWARDTABLE *append_ipforward_row( HANDLE heap, DWORD flags, MIB_IPFORWARDTABLE *table,
                                                 DWORD *count, const MIB_IPFORWARDROW *row )
{
    if (table->dwNumEntries >= *count)
    {
        MIB_IPFORWARDTABLE *new_table;
        DWORD new_count = table->dwNumEntries * 2;

        if (!(new_table = HeapReAlloc( heap, flags, table,
                                       FIELD_OFFSET(MIB_IPFORWARDTABLE, table[new_count] ))))
        {
            HeapFree( heap, 0, table );
            return NULL;
        }
        *count = new_count;
        table = new_table;
    }
    memcpy( &table->table[table->dwNumEntries++], row, sizeof(*row) );
    return table;
}

static int compare_ipforward_rows(const void *a, const void *b)
{
    const MIB_IPFORWARDROW *rowA = a;
    const MIB_IPFORWARDROW *rowB = b;
    int ret;

    if ((ret = rowA->dwForwardDest - rowB->dwForwardDest) != 0) return ret;
    if ((ret = rowA->dwForwardProto - rowB->dwForwardProto) != 0) return ret;
    if ((ret = rowA->dwForwardPolicy - rowB->dwForwardPolicy) != 0) return ret;
    return rowA->dwForwardNextHop - rowB->dwForwardNextHop;
}

/******************************************************************
 *    AllocateAndGetIpForwardTableFromStack (IPHLPAPI.@)
 *
 * Get the route table.
 * Like GetIpForwardTable(), but allocate the returned table from heap.
 *
 * PARAMS
 *  ppIpForwardTable [Out] pointer into which the MIB_IPFORWARDTABLE is
 *                         allocated and returned.
 *  bOrder           [In]  whether to sort the table
 *  heap             [In]  heap from which the table is allocated
 *  flags            [In]  flags to HeapAlloc
 *
 * RETURNS
 *  ERROR_INVALID_PARAMETER if ppIfTable is NULL, other error codes
 *  on failure, NO_ERROR on success.
 */
DWORD WINAPI AllocateAndGetIpForwardTableFromStack(PMIB_IPFORWARDTABLE *ppIpForwardTable, BOOL bOrder,
                                                   HANDLE heap, DWORD flags)
{
    MIB_IPFORWARDTABLE *table;
    MIB_IPFORWARDROW row;
    DWORD ret = NO_ERROR, count = 16;

    TRACE("table %p, bOrder %d, heap %p, flags 0x%08x\n", ppIpForwardTable, bOrder, heap, flags);

    if (!ppIpForwardTable) return ERROR_INVALID_PARAMETER;

    if (!(table = HeapAlloc( heap, flags, FIELD_OFFSET(MIB_IPFORWARDTABLE, table[count] ))))
        return ERROR_OUTOFMEMORY;

    table->dwNumEntries = 0;

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/route", "r")))
        {
            char buf[512], *ptr;
            DWORD flags;

            /* skip header line */
            ptr = fgets(buf, sizeof(buf), fp);
            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                memset( &row, 0, sizeof(row) );

                while (!isspace(*ptr)) ptr++;
                *ptr++ = 0;
                if (getInterfaceIndexByName(buf, &row.dwForwardIfIndex) != NO_ERROR)
                    continue;

                row.dwForwardDest = strtoul(ptr, &ptr, 16);
                row.dwForwardNextHop = strtoul(ptr + 1, &ptr, 16);
                flags = strtoul(ptr + 1, &ptr, 16);

                if (!(flags & RTF_UP)) row.dwForwardType = MIB_IPROUTE_TYPE_INVALID;
                else if (flags & RTF_GATEWAY) row.dwForwardType = MIB_IPROUTE_TYPE_INDIRECT;
                else row.dwForwardType = MIB_IPROUTE_TYPE_DIRECT;

                strtoul(ptr + 1, &ptr, 16); /* refcount, skip */
                strtoul(ptr + 1, &ptr, 16); /* use, skip */
                row.dwForwardMetric1 = strtoul(ptr + 1, &ptr, 16);
                row.dwForwardMask = strtoul(ptr + 1, &ptr, 16);
                /* FIXME: other protos might be appropriate, e.g. the default
                 * route is typically set with MIB_IPPROTO_NETMGMT instead */
                row.dwForwardProto = MIB_IPPROTO_LOCAL;

                if (!(table = append_ipforward_row( heap, flags, table, &count, &row )))
                    break;
            }
            fclose(fp);
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#elif defined(HAVE_SYS_TIHDR_H) && defined(T_OPTMGMT_ACK)
    {
        void *data;
        int fd, len, namelen;
        mib2_ipRouteEntry_t *entry;
        char name[64];

        if ((fd = open_streams_mib( NULL )) != -1)
        {
            if ((data = read_mib_entry( fd, MIB2_IP, MIB2_IP_ROUTE, &len )))
            {
                for (entry = data; (char *)(entry + 1) <= (char *)data + len; entry++)
                {
                    row.dwForwardDest      = entry->ipRouteDest;
                    row.dwForwardMask      = entry->ipRouteMask;
                    row.dwForwardPolicy    = 0;
                    row.dwForwardNextHop   = entry->ipRouteNextHop;
                    row.dwForwardType      = entry->ipRouteType;
                    row.dwForwardProto     = entry->ipRouteProto;
                    row.dwForwardAge       = entry->ipRouteAge;
                    row.dwForwardNextHopAS = 0;
                    row.dwForwardMetric1   = entry->ipRouteMetric1;
                    row.dwForwardMetric2   = entry->ipRouteMetric2;
                    row.dwForwardMetric3   = entry->ipRouteMetric3;
                    row.dwForwardMetric4   = entry->ipRouteMetric4;
                    row.dwForwardMetric5   = entry->ipRouteMetric5;
                    namelen = min( sizeof(name) - 1, entry->ipRouteIfIndex.o_length );
                    memcpy( name, entry->ipRouteIfIndex.o_bytes, namelen );
                    name[namelen] = 0;
                    getInterfaceIndexByName( name, &row.dwForwardIfIndex );
                    if (!(table = append_ipforward_row( heap, flags, table, &count, &row ))) break;
                }
                HeapFree( GetProcessHeap(), 0, data );
            }
            close( fd );
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(NET_RT_DUMP)
    {
       int mib[6] = {CTL_NET, PF_ROUTE, 0, PF_INET, NET_RT_DUMP, 0};
       size_t needed;
       char *buf = NULL, *lim, *next, *addrPtr;
       struct rt_msghdr *rtm;

       if (sysctl (mib, 6, NULL, &needed, NULL, 0) < 0)
       {
          ERR ("sysctl 1 failed!\n");
          ret = ERROR_NOT_SUPPORTED;
          goto done;
       }

       buf = HeapAlloc (GetProcessHeap (), 0, needed);
       if (!buf)
       {
          ret = ERROR_OUTOFMEMORY;
          goto done;
       }

       if (sysctl (mib, 6, buf, &needed, NULL, 0) < 0)
       {
          ret = ERROR_NOT_SUPPORTED;
          goto done;
       }

       lim = buf + needed;
       for (next = buf; next < lim; next += rtm->rtm_msglen)
       {
          int i;

          rtm = (struct rt_msghdr *)next;

          if (rtm->rtm_type != RTM_GET)
          {
             WARN ("Got unexpected message type 0x%x!\n",
                   rtm->rtm_type);
             continue;
          }

          /* Ignore all entries except for gateway routes which aren't
             multicast */
          if (!(rtm->rtm_flags & RTF_GATEWAY) ||
              (rtm->rtm_flags & RTF_MULTICAST))
             continue;

          memset( &row, 0, sizeof(row) );
          row.dwForwardIfIndex = rtm->rtm_index;
          row.dwForwardType = MIB_IPROUTE_TYPE_INDIRECT;
          row.dwForwardMetric1 = rtm->rtm_rmx.rmx_hopcount;
          row.dwForwardProto = MIB_IPPROTO_LOCAL;

          addrPtr = (char *)(rtm + 1);

          for (i = 1; i; i <<= 1)
          {
             struct sockaddr *sa;
             DWORD addr;

             if (!(i & rtm->rtm_addrs))
                continue;

             sa = (struct sockaddr *)addrPtr;
             ADVANCE (addrPtr, sa);

             /* default routes are encoded by length-zero sockaddr */
             if (sa->sa_len == 0)
                addr = 0;
             else if (sa->sa_family != AF_INET)
             {
                WARN ("Received unsupported sockaddr family 0x%x\n",
                     sa->sa_family);
                addr = 0;
             }
             else
             {
                struct sockaddr_in *sin = (struct sockaddr_in *)sa;

                addr = sin->sin_addr.s_addr;
             }

             switch (i)
             {
                case RTA_DST:     row.dwForwardDest = addr; break;
                case RTA_GATEWAY: row.dwForwardNextHop = addr; break;
                case RTA_NETMASK: row.dwForwardMask = addr; break;
                default:
                   WARN ("Unexpected address type 0x%x\n", i);
             }
          }

          if (!(table = append_ipforward_row( heap, flags, table, &count, &row )))
              break;
       }
done:
      HeapFree( GetProcessHeap (), 0, buf );
    }
#else
    FIXME( "not implemented\n" );
    ret = ERROR_NOT_SUPPORTED;
#endif

    if (!table) return ERROR_OUTOFMEMORY;
    if (!ret)
    {
        if (bOrder && table->dwNumEntries)
            qsort( table->table, table->dwNumEntries, sizeof(row), compare_ipforward_rows );
        *ppIpForwardTable = table;
    }
    else HeapFree( heap, flags, table );
    TRACE( "returning ret %u table %p\n", ret, table );
    return ret;
}

static MIB_IPNETTABLE *append_ipnet_row( HANDLE heap, DWORD flags, MIB_IPNETTABLE *table,
                                         DWORD *count, const MIB_IPNETROW *row )
{
    if (table->dwNumEntries >= *count)
    {
        MIB_IPNETTABLE *new_table;
        DWORD new_count = table->dwNumEntries * 2;

        if (!(new_table = HeapReAlloc( heap, flags, table,
                                       FIELD_OFFSET(MIB_IPNETTABLE, table[new_count] ))))
        {
            HeapFree( heap, 0, table );
            return NULL;
        }
        *count = new_count;
        table = new_table;
    }
    memcpy( &table->table[table->dwNumEntries++], row, sizeof(*row) );
    return table;
}

static int compare_ipnet_rows(const void *a, const void *b)
{
    const MIB_IPNETROW *rowA = a;
    const MIB_IPNETROW *rowB = b;

    return ntohl(rowA->dwAddr) - ntohl(rowB->dwAddr);
}


/******************************************************************
 *    AllocateAndGetIpNetTableFromStack (IPHLPAPI.@)
 *
 * Get the IP-to-physical address mapping table.
 * Like GetIpNetTable(), but allocate the returned table from heap.
 *
 * PARAMS
 *  ppIpNetTable [Out] pointer into which the MIB_IPNETTABLE is
 *                     allocated and returned.
 *  bOrder       [In]  whether to sort the table
 *  heap         [In]  heap from which the table is allocated
 *  flags        [In]  flags to HeapAlloc
 *
 * RETURNS
 *  ERROR_INVALID_PARAMETER if ppIpNetTable is NULL, other error codes
 *  on failure, NO_ERROR on success.
 */
DWORD WINAPI AllocateAndGetIpNetTableFromStack(PMIB_IPNETTABLE *ppIpNetTable, BOOL bOrder,
                                               HANDLE heap, DWORD flags)
{
    MIB_IPNETTABLE *table;
    MIB_IPNETROW row;
    DWORD ret = NO_ERROR, count = 16;

    TRACE("table %p, bOrder %d, heap %p, flags 0x%08x\n", ppIpNetTable, bOrder, heap, flags);

    if (!ppIpNetTable) return ERROR_INVALID_PARAMETER;

    if (!(table = HeapAlloc( heap, flags, FIELD_OFFSET(MIB_IPNETTABLE, table[count] ))))
        return ERROR_OUTOFMEMORY;

    table->dwNumEntries = 0;

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/arp", "r")))
        {
            char buf[512], *ptr;
            DWORD flags;

            /* skip header line */
            ptr = fgets(buf, sizeof(buf), fp);
            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                memset( &row, 0, sizeof(row) );

                row.dwAddr = inet_addr(ptr);
                while (*ptr && !isspace(*ptr)) ptr++;
                strtoul(ptr + 1, &ptr, 16); /* hw type (skip) */
                flags = strtoul(ptr + 1, &ptr, 16);

#ifdef ATF_COM
                if (flags & ATF_COM) row.dwType = MIB_IPNET_TYPE_DYNAMIC;
                else
#endif
#ifdef ATF_PERM
                if (flags & ATF_PERM) row.dwType = MIB_IPNET_TYPE_STATIC;
                else
#endif
                    row.dwType = MIB_IPNET_TYPE_OTHER;

                while (*ptr && isspace(*ptr)) ptr++;
                while (*ptr && !isspace(*ptr))
                {
                    row.bPhysAddr[row.dwPhysAddrLen++] = strtoul(ptr, &ptr, 16);
                    if (*ptr) ptr++;
                }
                while (*ptr && isspace(*ptr)) ptr++;
                while (*ptr && !isspace(*ptr)) ptr++;   /* mask (skip) */
                while (*ptr && isspace(*ptr)) ptr++;
                getInterfaceIndexByName(ptr, &row.dwIndex);

                if (!(table = append_ipnet_row( heap, flags, table, &count, &row )))
                    break;
            }
            fclose(fp);
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#elif defined(HAVE_SYS_TIHDR_H) && defined(T_OPTMGMT_ACK)
    {
        void *data;
        int fd, len, namelen;
        mib2_ipNetToMediaEntry_t *entry;
        char name[64];

        if ((fd = open_streams_mib( NULL )) != -1)
        {
            if ((data = read_mib_entry( fd, MIB2_IP, MIB2_IP_MEDIA, &len )))
            {
                for (entry = data; (char *)(entry + 1) <= (char *)data + len; entry++)
                {
                    row.dwPhysAddrLen = min( entry->ipNetToMediaPhysAddress.o_length, MAXLEN_PHYSADDR );
                    memcpy( row.bPhysAddr, entry->ipNetToMediaPhysAddress.o_bytes, row.dwPhysAddrLen );
                    row.dwAddr = entry->ipNetToMediaNetAddress;
                    row.dwType = entry->ipNetToMediaType;
                    namelen = min( sizeof(name) - 1, entry->ipNetToMediaIfIndex.o_length );
                    memcpy( name, entry->ipNetToMediaIfIndex.o_bytes, namelen );
                    name[namelen] = 0;
                    getInterfaceIndexByName( name, &row.dwIndex );
                    if (!(table = append_ipnet_row( heap, flags, table, &count, &row ))) break;
                }
                HeapFree( GetProcessHeap(), 0, data );
            }
            close( fd );
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(NET_RT_DUMP)
    {
      int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_LLINFO};
#define MIB_LEN (sizeof(mib) / sizeof(mib[0]))
      size_t needed;
      char *buf = NULL, *lim, *next;
      struct rt_msghdr *rtm;
      struct sockaddr_inarp *sinarp;
      struct sockaddr_dl *sdl;

      if (sysctl (mib, MIB_LEN,  NULL, &needed, NULL, 0) == -1)
      {
         ERR ("failed to get arp table\n");
         ret = ERROR_NOT_SUPPORTED;
         goto done;
      }

      buf = HeapAlloc (GetProcessHeap (), 0, needed);
      if (!buf)
      {
          ret = ERROR_OUTOFMEMORY;
          goto done;
      }

      if (sysctl (mib, MIB_LEN, buf, &needed, NULL, 0) == -1)
      {
         ret = ERROR_NOT_SUPPORTED;
         goto done;
      }

      lim = buf + needed;
      next = buf;
      while(next < lim)
      {
          rtm = (struct rt_msghdr *)next;
          sinarp=(struct sockaddr_inarp *)(rtm + 1);
          sdl = (struct sockaddr_dl *)((char *)sinarp + ROUNDUP(sinarp->sin_len));
          if(sdl->sdl_alen) /* arp entry */
          {
              memset( &row, 0, sizeof(row) );
              row.dwAddr = sinarp->sin_addr.s_addr;
              row.dwIndex = sdl->sdl_index;
              row.dwPhysAddrLen = min( 8, sdl->sdl_alen );
              memcpy( row.bPhysAddr, &sdl->sdl_data[sdl->sdl_nlen], row.dwPhysAddrLen );
              if(rtm->rtm_rmx.rmx_expire == 0) row.dwType = MIB_IPNET_TYPE_STATIC;
              else if(sinarp->sin_other & SIN_PROXY) row.dwType = MIB_IPNET_TYPE_OTHER;
              else if(rtm->rtm_rmx.rmx_expire != 0) row.dwType = MIB_IPNET_TYPE_DYNAMIC;
              else row.dwType = MIB_IPNET_TYPE_INVALID;

              if (!(table = append_ipnet_row( heap, flags, table, &count, &row )))
                  break;
          }
          next += rtm->rtm_msglen;
      }
done:
      HeapFree( GetProcessHeap (), 0, buf );
    }
#else
    FIXME( "not implemented\n" );
    ret = ERROR_NOT_SUPPORTED;
#endif

    if (!table) return ERROR_OUTOFMEMORY;
    if (!ret)
    {
        if (bOrder && table->dwNumEntries)
            qsort( table->table, table->dwNumEntries, sizeof(row), compare_ipnet_rows );
        *ppIpNetTable = table;
    }
    else HeapFree( heap, flags, table );
    TRACE( "returning ret %u table %p\n", ret, table );
    return ret;
}


static MIB_UDPTABLE *append_udp_row( HANDLE heap, DWORD flags, MIB_UDPTABLE *table,
                                     DWORD *count, const MIB_UDPROW *row )
{
    if (table->dwNumEntries >= *count)
    {
        MIB_UDPTABLE *new_table;
        DWORD new_count = table->dwNumEntries * 2;

        if (!(new_table = HeapReAlloc( heap, flags, table, FIELD_OFFSET(MIB_UDPTABLE, table[new_count] ))))
        {
            HeapFree( heap, 0, table );
            return NULL;
        }
        *count = new_count;
        table = new_table;
    }
    memcpy( &table->table[table->dwNumEntries++], row, sizeof(*row) );
    return table;
}

static int compare_udp_rows(const void *a, const void *b)
{
    const MIB_UDPROW *rowA = a;
    const MIB_UDPROW *rowB = b;
    int ret;

    if ((ret = rowA->dwLocalAddr - rowB->dwLocalAddr) != 0) return ret;
    return rowA->dwLocalPort - rowB->dwLocalPort;
}


/******************************************************************
 *    AllocateAndGetUdpTableFromStack (IPHLPAPI.@)
 *
 * Get the UDP listener table.
 * Like GetUdpTable(), but allocate the returned table from heap.
 *
 * PARAMS
 *  ppUdpTable [Out] pointer into which the MIB_UDPTABLE is
 *                   allocated and returned.
 *  bOrder     [In]  whether to sort the table
 *  heap       [In]  heap from which the table is allocated
 *  flags      [In]  flags to HeapAlloc
 *
 * RETURNS
 *  ERROR_INVALID_PARAMETER if ppUdpTable is NULL, whatever GetUdpTable()
 *  returns otherwise.
 */
DWORD WINAPI AllocateAndGetUdpTableFromStack(PMIB_UDPTABLE *ppUdpTable, BOOL bOrder,
                                             HANDLE heap, DWORD flags)
{
    MIB_UDPTABLE *table;
    MIB_UDPROW row;
    DWORD ret = NO_ERROR, count = 16;

    TRACE("table %p, bOrder %d, heap %p, flags 0x%08x\n", ppUdpTable, bOrder, heap, flags);

    if (!ppUdpTable) return ERROR_INVALID_PARAMETER;

    if (!(table = HeapAlloc( heap, flags, FIELD_OFFSET(MIB_UDPTABLE, table[count] ))))
        return ERROR_OUTOFMEMORY;

    table->dwNumEntries = 0;

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/udp", "r")))
        {
            char buf[512], *ptr;
            DWORD dummy;

            /* skip header line */
            ptr = fgets(buf, sizeof(buf), fp);
            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                if (sscanf( ptr, "%u: %x:%x", &dummy, &row.dwLocalAddr, &row.dwLocalPort ) != 3)
                    continue;
                row.dwLocalPort = htons( row.dwLocalPort );
                if (!(table = append_udp_row( heap, flags, table, &count, &row )))
                    break;
            }
            fclose(fp);
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#elif defined(HAVE_SYS_TIHDR_H) && defined(T_OPTMGMT_ACK)
    {
        void *data;
        int fd, len;
        mib2_udpEntry_t *entry;

        if ((fd = open_streams_mib( "udp" )) != -1)
        {
            if ((data = read_mib_entry( fd, MIB2_UDP, MIB2_UDP_ENTRY, &len )))
            {
                for (entry = data; (char *)(entry + 1) <= (char *)data + len; entry++)
                {
                    row.dwLocalAddr = entry->udpLocalAddress;
                    row.dwLocalPort = htons( entry->udpLocalPort );
                    if (!(table = append_udp_row( heap, flags, table, &count, &row ))) break;
                }
                HeapFree( GetProcessHeap(), 0, data );
            }
            close( fd );
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#else
    FIXME( "not implemented\n" );
    ret = ERROR_NOT_SUPPORTED;
#endif

    if (!table) return ERROR_OUTOFMEMORY;
    if (!ret)
    {
        if (bOrder && table->dwNumEntries)
            qsort( table->table, table->dwNumEntries, sizeof(row), compare_udp_rows );
        *ppUdpTable = table;
    }
    else HeapFree( heap, flags, table );
    TRACE( "returning ret %u table %p\n", ret, table );
    return ret;
}


static MIB_TCPTABLE *append_tcp_row( HANDLE heap, DWORD flags, MIB_TCPTABLE *table,
                                     DWORD *count, const MIB_TCPROW *row )
{
    if (table->dwNumEntries >= *count)
    {
        MIB_TCPTABLE *new_table;
        DWORD new_count = table->dwNumEntries * 2;

        if (!(new_table = HeapReAlloc( heap, flags, table, FIELD_OFFSET(MIB_TCPTABLE, table[new_count] ))))
        {
            HeapFree( heap, 0, table );
            return NULL;
        }
        *count = new_count;
        table = new_table;
    }
    memcpy( &table->table[table->dwNumEntries++], row, sizeof(*row) );
    return table;
}


/* Why not a lookup table? Because the TCPS_* constants are different
   on different platforms */
static inline DWORD TCPStateToMIBState (int state)
{
   switch (state)
   {
      case TCPS_ESTABLISHED: return MIB_TCP_STATE_ESTAB;
      case TCPS_SYN_SENT: return MIB_TCP_STATE_SYN_SENT;
      case TCPS_SYN_RECEIVED: return MIB_TCP_STATE_SYN_RCVD;
      case TCPS_FIN_WAIT_1: return MIB_TCP_STATE_FIN_WAIT1;
      case TCPS_FIN_WAIT_2: return MIB_TCP_STATE_FIN_WAIT2;
      case TCPS_TIME_WAIT: return MIB_TCP_STATE_TIME_WAIT;
      case TCPS_CLOSE_WAIT: return MIB_TCP_STATE_CLOSE_WAIT;
      case TCPS_LAST_ACK: return MIB_TCP_STATE_LAST_ACK;
      case TCPS_LISTEN: return MIB_TCP_STATE_LISTEN;
      case TCPS_CLOSING: return MIB_TCP_STATE_CLOSING;
      default:
      case TCPS_CLOSED: return MIB_TCP_STATE_CLOSED;
   }
}


static int compare_tcp_rows(const void *a, const void *b)
{
    const MIB_TCPROW *rowA = a;
    const MIB_TCPROW *rowB = b;
    int ret;

    if ((ret = ntohl (rowA->dwLocalAddr) - ntohl (rowB->dwLocalAddr)) != 0) return ret;
    if ((ret = ntohs ((unsigned short)rowA->dwLocalPort) -
               ntohs ((unsigned short)rowB->dwLocalPort)) != 0) return ret;
    if ((ret = ntohl (rowA->dwRemoteAddr) - ntohl (rowB->dwRemoteAddr)) != 0) return ret;
    return ntohs ((unsigned short)rowA->dwRemotePort) - ntohs ((unsigned short)rowB->dwRemotePort);
}


/******************************************************************
 *    AllocateAndGetTcpTableFromStack (IPHLPAPI.@)
 *
 * Get the TCP connection table.
 * Like GetTcpTable(), but allocate the returned table from heap.
 *
 * PARAMS
 *  ppTcpTable [Out] pointer into which the MIB_TCPTABLE is
 *                   allocated and returned.
 *  bOrder     [In]  whether to sort the table
 *  heap       [In]  heap from which the table is allocated
 *  flags      [In]  flags to HeapAlloc
 *
 * RETURNS
 *  ERROR_INVALID_PARAMETER if ppTcpTable is NULL, whatever GetTcpTable()
 *  returns otherwise.
 */
DWORD WINAPI AllocateAndGetTcpTableFromStack( PMIB_TCPTABLE *ppTcpTable, BOOL bOrder,
                                              HANDLE heap, DWORD flags)
{
    MIB_TCPTABLE *table;
    MIB_TCPROW row;
    DWORD ret = NO_ERROR, count = 16;

    TRACE("table %p, bOrder %d, heap %p, flags 0x%08x\n", ppTcpTable, bOrder, heap, flags);

    if (!ppTcpTable) return ERROR_INVALID_PARAMETER;

    if (!(table = HeapAlloc( heap, flags, FIELD_OFFSET(MIB_TCPTABLE, table[count] ))))
        return ERROR_OUTOFMEMORY;

    table->dwNumEntries = 0;

#ifdef __linux__
    {
        FILE *fp;

        if ((fp = fopen("/proc/net/tcp", "r")))
        {
            char buf[512], *ptr;
            DWORD dummy;

            /* skip header line */
            ptr = fgets(buf, sizeof(buf), fp);
            while ((ptr = fgets(buf, sizeof(buf), fp)))
            {
                if (sscanf( ptr, "%x: %x:%x %x:%x %x", &dummy, &row.dwLocalAddr, &row.dwLocalPort,
                            &row.dwRemoteAddr, &row.dwRemotePort, &row.dwState ) != 6)
                    continue;
                row.dwLocalPort = htons( row.dwLocalPort );
                row.dwRemotePort = htons( row.dwRemotePort );
                row.dwState = TCPStateToMIBState( row.dwState );
                if (!(table = append_tcp_row( heap, flags, table, &count, &row )))
                    break;
            }
            fclose( fp );
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#elif defined(HAVE_SYS_TIHDR_H) && defined(T_OPTMGMT_ACK)
    {
        void *data;
        int fd, len;
        mib2_tcpConnEntry_t *entry;

        if ((fd = open_streams_mib( "tcp" )) != -1)
        {
            if ((data = read_mib_entry( fd, MIB2_TCP, MIB2_TCP_CONN, &len )))
            {
                for (entry = data; (char *)(entry + 1) <= (char *)data + len; entry++)
                {
                    row.dwLocalAddr = entry->tcpConnLocalAddress;
                    row.dwLocalPort = htons( entry->tcpConnLocalPort );
                    row.dwRemoteAddr = entry->tcpConnRemAddress;
                    row.dwRemotePort = htons( entry->tcpConnRemPort );
                    row.dwState = entry->tcpConnState;
                    if (!(table = append_tcp_row( heap, flags, table, &count, &row ))) break;
                }
                HeapFree( GetProcessHeap(), 0, data );
            }
            close( fd );
        }
        else ret = ERROR_NOT_SUPPORTED;
    }
#elif defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_STRUCT_XINPGEN)
    {
        size_t Len = 0;
        char *Buf = NULL;
        struct xinpgen *pXIG, *pOrigXIG;

        if (sysctlbyname ("net.inet.tcp.pcblist", NULL, &Len, NULL, 0) < 0)
        {
            ERR ("Failure to read net.inet.tcp.pcblist via sysctlbyname!\n");
            ret = ERROR_NOT_SUPPORTED;
            goto done;
        }

        Buf = HeapAlloc (GetProcessHeap (), 0, Len);
        if (!Buf)
        {
            ret = ERROR_OUTOFMEMORY;
            goto done;
        }

        if (sysctlbyname ("net.inet.tcp.pcblist", Buf, &Len, NULL, 0) < 0)
        {
            ERR ("Failure to read net.inet.tcp.pcblist via sysctlbyname!\n");
            ret = ERROR_NOT_SUPPORTED;
            goto done;
        }

        /* Might be nothing here; first entry is just a header it seems */
        if (Len <= sizeof (struct xinpgen)) goto done;

        pOrigXIG = (struct xinpgen *)Buf;
        pXIG = pOrigXIG;

        for (pXIG = (struct xinpgen *)((char *)pXIG + pXIG->xig_len);
             pXIG->xig_len > sizeof (struct xinpgen);
             pXIG = (struct xinpgen *)((char *)pXIG + pXIG->xig_len))
        {
            struct tcpcb *pTCPData = NULL;
            struct inpcb *pINData;
            struct xsocket *pSockData;

            pTCPData = &((struct xtcpcb *)pXIG)->xt_tp;
            pINData = &((struct xtcpcb *)pXIG)->xt_inp;
            pSockData = &((struct xtcpcb *)pXIG)->xt_socket;

            /* Ignore sockets for other protocols */
            if (pSockData->xso_protocol != IPPROTO_TCP)
                continue;

            /* Ignore PCBs that were freed while generating the data */
            if (pINData->inp_gencnt > pOrigXIG->xig_gen)
                continue;

            /* we're only interested in IPv4 addresses */
            if (!(pINData->inp_vflag & INP_IPV4) ||
                (pINData->inp_vflag & INP_IPV6))
                continue;

            /* If all 0's, skip it */
            if (!pINData->inp_laddr.s_addr &&
                !pINData->inp_lport &&
                !pINData->inp_faddr.s_addr &&
                !pINData->inp_fport)
                continue;

            /* Fill in structure details */
            row.dwLocalAddr = pINData->inp_laddr.s_addr;
            row.dwLocalPort = pINData->inp_lport;
            row.dwRemoteAddr = pINData->inp_faddr.s_addr;
            row.dwRemotePort = pINData->inp_fport;
            row.dwState = TCPStateToMIBState (pTCPData->t_state);
            if (!(table = append_tcp_row( heap, flags, table, &count, &row ))) break;
        }

    done:
        HeapFree (GetProcessHeap (), 0, Buf);
    }
#else
    FIXME( "not implemented\n" );
    ret = ERROR_NOT_SUPPORTED;
#endif

    if (!table) return ERROR_OUTOFMEMORY;
    if (!ret)
    {
        if (bOrder && table->dwNumEntries)
            qsort( table->table, table->dwNumEntries, sizeof(row), compare_tcp_rows );
        *ppTcpTable = table;
    }
    else HeapFree( heap, flags, table );
    TRACE( "returning ret %u table %p\n", ret, table );
    return ret;
}

Generated by  Doxygen 1.6.0   Back to index