Signed-off-by: Huw Davies huw@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)