[PATCH 2/7] nsiproxy: Implement TCP connections enumerate_all.

Huw Davies huw at codeweavers.com
Tue Aug 17 03:27:12 CDT 2021


Signed-off-by: Huw Davies <huw at codeweavers.com>
---
 dlls/nsi/tests/nsi.c    | 101 ++++++++++++++
 dlls/nsiproxy.sys/tcp.c | 302 ++++++++++++++++++++++++++++++++++++++++
 include/wine/nsi.h      |  25 ++++
 3 files changed, 428 insertions(+)

diff --git a/dlls/nsi/tests/nsi.c b/dlls/nsi/tests/nsi.c
index 33b4e9e6d97..8e19df83684 100644
--- a/dlls/nsi/tests/nsi.c
+++ b/dlls/nsi/tests/nsi.c
@@ -848,6 +848,101 @@ static void test_tcp_stats( int family )
     winetest_pop_context();
 }
 
+static void test_tcp_tables( int family, int table_type )
+{
+    DWORD dyn_sizes[] = { FIELD_OFFSET(struct nsi_tcp_conn_dynamic, unk[2]), sizeof(struct nsi_tcp_conn_dynamic) };
+    DWORD i, err, count, table_num, dyn_size, size;
+    struct nsi_tcp_conn_key *keys;
+    struct nsi_tcp_conn_dynamic *dyn_tbl, *dyn;
+    struct nsi_tcp_conn_static *stat;
+    MIB_TCPTABLE_OWNER_MODULE *table;
+    MIB_TCP6TABLE_OWNER_MODULE *table6;
+    MIB_TCPROW_OWNER_MODULE *row;
+    MIB_TCP6ROW_OWNER_MODULE *row6;
+
+    winetest_push_context( "%s: %d", family == AF_INET ? "AF_INET" : "AF_INET6", table_type );
+
+    switch (table_type)
+    {
+    case TCP_TABLE_OWNER_MODULE_ALL: table_num = 3; break;
+    case TCP_TABLE_OWNER_MODULE_CONNECTIONS: table_num = 4; break;
+    case TCP_TABLE_OWNER_MODULE_LISTENER: table_num = 5; break;
+    default: return;
+    }
+
+    for (i = 0; i < ARRAY_SIZE(dyn_sizes); i++)
+    {
+        err = NsiAllocateAndGetTable( 1, &NPI_MS_TCP_MODULEID, table_num, (void **)&keys, sizeof(*keys), NULL, 0,
+                                      (void **)&dyn_tbl, dyn_sizes[i], (void **)&stat, sizeof(*stat), &count, 0 );
+        if (!err) break;
+    }
+    ok( !err, "got %d\n", err );
+    dyn_size = dyn_sizes[i];
+
+    size = 0;
+    err = GetExtendedTcpTable( NULL, &size, 0, family, table_type, 0 );
+    size *= 2;
+    table = malloc( size );
+    table6 = (MIB_TCP6TABLE_OWNER_MODULE *)table;
+    err = GetExtendedTcpTable( table, &size, 0, family, table_type, 0 );
+    ok( !err, "got %d\n", err );
+
+    row = table->table;
+    row6 = table6->table;
+
+    for (i = 0; i < count; i++)
+    {
+        dyn = (struct nsi_tcp_conn_dynamic *)((BYTE *)dyn_tbl + i * dyn_size);
+        if (keys[i].local.si_family != family) continue;
+
+        if (family == AF_INET)
+        {
+            ok( unstable( row->dwLocalAddr == keys[i].local.Ipv4.sin_addr.s_addr ), "%08x vs %08x\n",
+                row->dwLocalAddr, keys[i].local.Ipv4.sin_addr.s_addr );
+            ok( unstable( row->dwLocalPort == keys[i].local.Ipv4.sin_port ), "%d vs %d\n",
+                row->dwLocalPort, keys[i].local.Ipv4.sin_port );
+            ok( unstable( row->dwRemoteAddr == keys[i].remote.Ipv4.sin_addr.s_addr ), "%08x vs %08x\n",
+                row->dwRemoteAddr, keys[i].remote.Ipv4.sin_addr.s_addr );
+            ok( unstable( row->dwRemotePort == keys[i].remote.Ipv4.sin_port ), "%d vs %d\n",
+                row->dwRemotePort, keys[i].remote.Ipv4.sin_port );
+            ok( unstable( row->dwState == dyn->state ), "%x vs %x\n", row->dwState, dyn->state );
+todo_wine_if( !unstable(0) && row->dwOwningPid )
+            ok( unstable( row->dwOwningPid == stat[i].pid ), "%x vs %x\n", row->dwOwningPid, stat[i].pid );
+            ok( unstable( row->liCreateTimestamp.QuadPart == stat[i].create_time ), "mismatch\n" );
+            ok( unstable( row->OwningModuleInfo[0] == stat[i].mod_info ), "mismatch\n");
+            row++;
+        }
+        else if (family == AF_INET6)
+        {
+            ok( unstable( !memcmp( row6->ucLocalAddr, keys[i].local.Ipv6.sin6_addr.s6_addr, sizeof(IN6_ADDR) ) ),
+                "mismatch\n" );
+todo_wine_if( !unstable(0) && row6->dwLocalScopeId )
+            ok( unstable( row6->dwLocalScopeId == keys[i].local.Ipv6.sin6_scope_id ), "%x vs %x\n",
+                row6->dwLocalScopeId, keys[i].local.Ipv6.sin6_scope_id );
+            ok( unstable( row6->dwLocalPort == keys[i].local.Ipv6.sin6_port ), "%d vs %d\n",
+                row6->dwLocalPort, keys[i].local.Ipv6.sin6_port );
+            ok( unstable( !memcmp( row6->ucRemoteAddr, keys[i].remote.Ipv6.sin6_addr.s6_addr, sizeof(IN6_ADDR) ) ),
+                "mismatch\n" );
+todo_wine_if( !unstable(0) && row6->dwRemoteScopeId )
+            ok( unstable( row6->dwRemoteScopeId == keys[i].remote.Ipv6.sin6_scope_id ), "%x vs %x\n",
+                row6->dwRemoteScopeId, keys[i].remote.Ipv6.sin6_scope_id );
+            ok( unstable( row6->dwRemotePort == keys[i].remote.Ipv6.sin6_port ), "%d vs %d\n",
+                row6->dwRemotePort, keys[i].remote.Ipv6.sin6_port );
+            ok( unstable( row6->dwState == dyn->state ), "%x vs %x\n", row6->dwState, dyn->state );
+todo_wine_if( !unstable(0) && row6->dwOwningPid )
+            ok( unstable( row6->dwOwningPid == stat[i].pid ), "%x vs %x\n", row6->dwOwningPid, stat[i].pid );
+            ok( unstable( row6->liCreateTimestamp.QuadPart == stat[i].create_time ), "mismatch\n" );
+            ok( unstable( row6->OwningModuleInfo[0] == stat[i].mod_info ), "mismatch\n");
+            row6++;
+        }
+
+    }
+    free( table );
+    NsiFreeTable( keys, NULL, dyn_tbl, stat );
+    winetest_pop_context();
+}
+
+
 START_TEST( nsi )
 {
     test_nsi_api();
@@ -870,4 +965,10 @@ START_TEST( nsi )
 
     test_tcp_stats( AF_INET );
     test_tcp_stats( AF_INET6 );
+    test_tcp_tables( AF_INET, TCP_TABLE_OWNER_MODULE_ALL );
+    test_tcp_tables( AF_INET, TCP_TABLE_OWNER_MODULE_CONNECTIONS );
+    test_tcp_tables( AF_INET, TCP_TABLE_OWNER_MODULE_LISTENER );
+    test_tcp_tables( AF_INET6, TCP_TABLE_OWNER_MODULE_ALL );
+    test_tcp_tables( AF_INET6, TCP_TABLE_OWNER_MODULE_CONNECTIONS );
+    test_tcp_tables( AF_INET6, TCP_TABLE_OWNER_MODULE_LISTENER );
 }
diff --git a/dlls/nsiproxy.sys/tcp.c b/dlls/nsiproxy.sys/tcp.c
index 5fe7fcbd72e..710aed5d6c3 100644
--- a/dlls/nsiproxy.sys/tcp.c
+++ b/dlls/nsiproxy.sys/tcp.c
@@ -46,6 +46,10 @@
 #include <netinet/tcp_var.h>
 #endif
 
+#ifdef HAVE_NETINET_TCP_FSM_H
+#include <netinet/tcp_fsm.h>
+#endif
+
 #ifdef HAVE_SYS_SYSCTL_H
 #include <sys/sysctl.h>
 #endif
@@ -61,11 +65,26 @@
 #include "netiodef.h"
 #include "ws2ipdef.h"
 #include "tcpmib.h"
+#include "wine/heap.h"
 #include "wine/nsi.h"
 #include "wine/debug.h"
 
 #include "nsiproxy_private.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
+
 WINE_DEFAULT_DEBUG_CHANNEL(nsi);
 
 static NTSTATUS tcp_stats_get_all_parameters( const void *key, DWORD key_size, void *rw_data, DWORD rw_size,
@@ -171,6 +190,265 @@ static NTSTATUS tcp_stats_get_all_parameters( const void *key, DWORD key_size, v
 #endif
 }
 
+static inline MIB_TCP_STATE tcp_state_to_mib_state( 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 NTSTATUS tcp_conns_enumerate_all( DWORD filter, struct nsi_tcp_conn_key *key_data, DWORD key_size,
+                                         void *rw, DWORD rw_size,
+                                         struct nsi_tcp_conn_dynamic *dynamic_data, DWORD dynamic_size,
+                                         struct nsi_tcp_conn_static *static_data, DWORD static_size, DWORD_PTR *count )
+{
+    DWORD num = 0;
+    NTSTATUS status = STATUS_SUCCESS;
+    BOOL want_data = key_size || rw_size || dynamic_size || static_size;
+    struct nsi_tcp_conn_key key;
+    struct nsi_tcp_conn_dynamic dyn;
+    struct nsi_tcp_conn_static stat;
+
+#ifdef __linux__
+    {
+        FILE *fp;
+        char buf[512], *ptr;
+        int inode;
+
+        if (!(fp = fopen( "/proc/net/tcp", "r" ))) return ERROR_NOT_SUPPORTED;
+
+        memset( &key, 0, sizeof(key) );
+        memset( &dyn, 0, sizeof(dyn) );
+        memset( &stat, 0, sizeof(stat) );
+
+        /* skip header line */
+        ptr = fgets( buf, sizeof(buf), fp );
+        while ((ptr = fgets( buf, sizeof(buf), fp )))
+        {
+            if (sscanf( ptr, "%*x: %x:%hx %x:%hx %x %*s %*s %*s %*s %*s %d",
+                        &key.local.Ipv4.sin_addr.WS_s_addr, &key.local.Ipv4.sin_port,
+                        &key.remote.Ipv4.sin_addr.WS_s_addr, &key.remote.Ipv4.sin_port,
+                        &dyn.state, &inode ) != 6)
+                continue;
+            dyn.state = tcp_state_to_mib_state( dyn.state );
+            if (filter && filter != dyn.state ) continue;
+
+            key.local.Ipv4.sin_family = key.remote.Ipv4.sin_family = WS_AF_INET;
+            key.local.Ipv4.sin_port = htons( key.local.Ipv4.sin_port );
+            key.remote.Ipv4.sin_port = htons( key.remote.Ipv4.sin_port );
+
+            stat.pid = 0; /* FIXME */
+            stat.create_time = 0; /* FIXME */
+            stat.mod_info = 0; /* FIXME */
+
+            if (num < *count)
+            {
+                if (key_data) *key_data++ = key;
+                if (dynamic_data) *dynamic_data++ = dyn;
+                if (static_data) *static_data++ = stat;
+            }
+            num++;
+        }
+        fclose( fp );
+
+        if ((fp = fopen( "/proc/net/tcp6", "r" )))
+        {
+            memset( &key, 0, sizeof(key) );
+            memset( &dyn, 0, sizeof(dyn) );
+            memset( &stat, 0, sizeof(stat) );
+
+            /* skip header line */
+            ptr = fgets( buf, sizeof(buf), fp );
+            while ((ptr = fgets( buf, sizeof(buf), fp )))
+            {
+                DWORD *local_addr = (DWORD *)&key.local.Ipv6.sin6_addr;
+                DWORD *remote_addr = (DWORD *)&key.remote.Ipv6.sin6_addr;
+
+                if (sscanf( ptr, "%*u: %8x%8x%8x%8x:%hx %8x%8x%8x%8x:%hx %x %*s %*s %*s %*s %*s %*s %*s %d",
+                            local_addr, local_addr + 1, local_addr + 2, local_addr + 3, &key.local.Ipv6.sin6_port,
+                            remote_addr, remote_addr + 1, remote_addr + 2, remote_addr + 3, &key.remote.Ipv6.sin6_port,
+                            &dyn.state, &inode ) != 12)
+                    continue;
+                dyn.state = tcp_state_to_mib_state( dyn.state );
+                if (filter && filter != dyn.state ) continue;
+                key.local.Ipv6.sin6_family = key.remote.Ipv6.sin6_family = WS_AF_INET6;
+                key.local.Ipv6.sin6_port = htons( key.local.Ipv6.sin6_port );
+                key.remote.Ipv6.sin6_port = htons( key.remote.Ipv6.sin6_port );
+                key.local.Ipv6.sin6_scope_id = 0; /* FIXME */
+                key.remote.Ipv6.sin6_scope_id = 0; /* FIXME */
+
+                stat.pid = 0; /* FIXME */
+                stat.create_time = 0; /* FIXME */
+                stat.mod_info = 0; /* FIXME */
+
+                if (num < *count)
+                {
+                    if (key_data) *key_data++ = key;
+                    if (dynamic_data) *dynamic_data++ = dyn;
+                    if (static_data) *static_data++ = stat;
+                }
+                num++;
+            }
+            fclose( fp );
+        }
+    }
+#elif defined(HAVE_SYS_SYSCTL_H) && defined(TCPCTL_PCBLIST) && defined(HAVE_STRUCT_XINPGEN)
+    {
+        int mib[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_PCBLIST };
+        size_t len = 0;
+        char *buf = NULL;
+        struct xinpgen *xig, *orig_xig;
+
+        if (sysctl( mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0 ) < 0)
+        {
+            ERR( "Failure to read net.inet.tcp.pcblist via sysctl\n" );
+            status = STATUS_NOT_SUPPORTED;
+            goto err;
+        }
+
+        buf = heap_alloc( len );
+        if (!buf)
+        {
+            status = STATUS_NO_MEMORY;
+            goto err;
+        }
+
+        if (sysctl( mib, ARRAY_SIZE(mib), buf, &len, NULL, 0 ) < 0)
+        {
+            ERR( "Failure to read net.inet.tcp.pcblist via sysctl\n" );
+            status = STATUS_NOT_SUPPORTED;
+            goto err;
+        }
+
+        /* Might be nothing here; first entry is just a header it seems */
+        if (len <= sizeof(struct xinpgen)) goto err;
+
+        orig_xig = (struct xinpgen *)buf;
+        xig = orig_xig;
+
+        for (xig = (struct xinpgen *)((char *)xig + xig->xig_len);
+             xig->xig_len > sizeof(struct xinpgen);
+             xig = (struct xinpgen *)((char *)xig + xig->xig_len))
+        {
+#if __FreeBSD_version >= 1200026
+            struct xtcpcb *tcp = (struct xtcpcb *)xig;
+            struct xinpcb *in = &tcp->xt_inp;
+            struct xsocket *sock = &in->xi_socket;
+#else
+            struct tcpcb *tcp = &((struct xtcpcb *)xig)->xt_tp;
+            struct inpcb *in = &((struct xtcpcb *)xig)->xt_inp;
+            struct xsocket *sock = &((struct xtcpcb *)xig)->xt_socket;
+#endif
+            static const struct in6_addr zero;
+
+            /* Ignore sockets for other protocols */
+            if (sock->xso_protocol != IPPROTO_TCP) continue;
+
+            /* Ignore PCBs that were freed while generating the data */
+            if (in->inp_gencnt > orig_xig->xig_gen) continue;
+
+            /* we're only interested in IPv4 and IPV6 addresses */
+            if (!(in->inp_vflag & (INP_IPV4 | INP_IPV6))) continue;
+
+            /* If all 0's, skip it */
+            if (in->inp_vflag & INP_IPV4 && !in->inp_laddr.s_addr && !in->inp_lport &&
+                !in->inp_faddr.s_addr && !in->inp_fport) continue;
+            if (in->inp_vflag & INP_IPV6 && !memcmp( &in->in6p_laddr, &zero, sizeof(zero) ) && !in->inp_lport &&
+                !memcmp( &in->in6p_faddr, &zero, sizeof(zero) ) && !in->inp_fport) continue;
+
+            dyn.state = tcp_state_to_mib_state( tcp->t_state );
+            if (filter && filter != dyn.state ) continue;
+
+            if (in->inp_vflag & INP_IPV4)
+            {
+                key.local.Ipv4.sin_family = key.remote.Ipv4.sin_family = WS_AF_INET;
+                key.local.Ipv4.sin_addr.WS_s_addr = in->inp_laddr.s_addr;
+                key.local.Ipv4.sin_port = in->inp_lport;
+                key.remote.Ipv4.sin_addr.WS_s_addr = in->inp_faddr.s_addr;
+                key.remote.Ipv4.sin_port = in->inp_fport;
+            }
+            else
+            {
+                key.local.Ipv6.sin6_family = key.remote.Ipv6.sin6_family = WS_AF_INET6;
+                memcpy( &key.local.Ipv6.sin6_addr, &in->in6p_laddr, sizeof(in->in6p_laddr) );
+                key.local.Ipv6.sin6_port = in->inp_lport;
+                memcpy( &key.remote.Ipv6.sin6_addr, &in->in6p_faddr, sizeof(in->in6p_faddr) );
+                key.remote.Ipv6.sin6_port = in->inp_fport;
+                key.local.Ipv6.sin6_scope_id = 0; /* FIXME */
+                key.remote.Ipv6.sin6_scope_id = 0; /* FIXME */
+            }
+
+            stat.pid = 0; /* FIXME */
+            stat.create_time = 0; /* FIXME */
+            stat.mod_info = 0; /* FIXME */
+
+            if (num < *count)
+            {
+                if (key_data) *key_data++ = key;
+                if (dynamic_data) *dynamic_data++ = dyn;
+                if (static_data) *static_data++ = stat;
+            }
+            num++;
+        }
+    err:
+        heap_free( buf );
+    }
+#else
+    FIXME( "not implemented\n" );
+    status = STATUS_NOT_IMPLEMENTED;
+#endif
+
+    if (!want_data || num <= *count) *count = num;
+    else status = STATUS_MORE_ENTRIES;
+
+    return status;
+}
+
+static NTSTATUS tcp_all_enumerate_all( void *key_data, DWORD key_size, void *rw_data, DWORD rw_size,
+                                       void *dynamic_data, DWORD dynamic_size,
+                                       void *static_data, DWORD static_size, DWORD_PTR *count )
+{
+    TRACE( "%p %d %p %d %p %d %p %d %p\n", key_data, key_size, rw_data, rw_size,
+           dynamic_data, dynamic_size, static_data, static_size, count );
+
+    return tcp_conns_enumerate_all( 0, key_data, key_size, rw_data, rw_size,
+                                    dynamic_data, dynamic_size, static_data, static_size, count );
+}
+
+static NTSTATUS tcp_estab_enumerate_all( void *key_data, DWORD key_size, void *rw_data, DWORD rw_size,
+                                         void *dynamic_data, DWORD dynamic_size,
+                                         void *static_data, DWORD static_size, DWORD_PTR *count )
+{
+    TRACE( "%p %d %p %d %p %d %p %d %p\n", key_data, key_size, rw_data, rw_size,
+           dynamic_data, dynamic_size, static_data, static_size, count );
+
+    return tcp_conns_enumerate_all( MIB_TCP_STATE_ESTAB, key_data, key_size, rw_data, rw_size,
+                                    dynamic_data, dynamic_size, static_data, static_size, count );
+}
+
+static NTSTATUS tcp_listen_enumerate_all( void *key_data, DWORD key_size, void *rw_data, DWORD rw_size,
+                                          void *dynamic_data, DWORD dynamic_size,
+                                          void *static_data, DWORD static_size, DWORD_PTR *count )
+{
+    TRACE( "%p %d %p %d %p %d %p %d %p\n", key_data, key_size, rw_data, rw_size,
+           dynamic_data, dynamic_size, static_data, static_size, count );
+
+    return tcp_conns_enumerate_all( MIB_TCP_STATE_LISTEN, key_data, key_size, rw_data, rw_size,
+                                    dynamic_data, dynamic_size, static_data, static_size, count );
+}
+
 static struct module_table tcp_tables[] =
 {
     {
@@ -182,6 +460,30 @@ static struct module_table tcp_tables[] =
         NULL,
         tcp_stats_get_all_parameters,
     },
+    {
+        NSI_TCP_ALL_TABLE,
+        {
+            sizeof(struct nsi_tcp_conn_key), 0,
+            sizeof(struct nsi_tcp_conn_dynamic), sizeof(struct nsi_tcp_conn_static)
+        },
+        tcp_all_enumerate_all,
+    },
+    {
+        NSI_TCP_ESTAB_TABLE,
+        {
+            sizeof(struct nsi_tcp_conn_key), 0,
+            sizeof(struct nsi_tcp_conn_dynamic), sizeof(struct nsi_tcp_conn_static)
+        },
+        tcp_estab_enumerate_all,
+    },
+    {
+        NSI_TCP_LISTEN_TABLE,
+        {
+            sizeof(struct nsi_tcp_conn_key), 0,
+            sizeof(struct nsi_tcp_conn_dynamic), sizeof(struct nsi_tcp_conn_static)
+        },
+        tcp_listen_enumerate_all,
+    },
     {
         ~0u
     }
diff --git a/include/wine/nsi.h b/include/wine/nsi.h
index 3c92fb81aae..39a1ecebda1 100644
--- a/include/wine/nsi.h
+++ b/include/wine/nsi.h
@@ -21,6 +21,8 @@
 
 #include "inaddr.h"
 #include "in6addr.h"
+#include "ws2def.h"
+#include "ws2ipdef.h"
 #include "winioctl.h"
 
 /* Undocumented NSI NDIS tables */
@@ -294,6 +296,9 @@ struct nsi_ip_forward_static
 
 /* Undocumented NSI TCP tables */
 #define NSI_TCP_STATS_TABLE                0
+#define NSI_TCP_ALL_TABLE                  3
+#define NSI_TCP_ESTAB_TABLE                4
+#define NSI_TCP_LISTEN_TABLE               5
 
 struct nsi_tcp_stats_dynamic
 {
@@ -321,6 +326,26 @@ struct nsi_tcp_stats_static
     DWORD unk;
 };
 
+struct nsi_tcp_conn_key
+{
+    SOCKADDR_INET local;
+    SOCKADDR_INET remote;
+};
+
+struct nsi_tcp_conn_dynamic
+{
+    DWORD state;
+    DWORD unk[3];
+};
+
+struct nsi_tcp_conn_static
+{
+    DWORD unk[3];
+    DWORD pid;
+    ULONGLONG create_time;
+    ULONGLONG mod_info;
+};
+
 /* Wine specific ioctl interface */
 
 #define IOCTL_NSIPROXY_WINE_ENUMERATE_ALL         CTL_CODE(FILE_DEVICE_NETWORK, 0x400, METHOD_BUFFERED, 0)
-- 
2.23.0




More information about the wine-devel mailing list