[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