Signed-off-by: Huw Davies huw@codeweavers.com --- dlls/nsi/tests/nsi.c | 65 ++++++ dlls/nsiproxy.sys/Makefile.in | 3 +- dlls/nsiproxy.sys/nsi.c | 1 + dlls/nsiproxy.sys/nsiproxy_private.h | 19 ++ dlls/nsiproxy.sys/tcp.c | 20 +- dlls/nsiproxy.sys/udp.c | 291 +++++++++++++++++++++++++++ include/wine/nsi.h | 18 ++ 7 files changed, 400 insertions(+), 17 deletions(-) create mode 100644 dlls/nsiproxy.sys/udp.c
diff --git a/dlls/nsi/tests/nsi.c b/dlls/nsi/tests/nsi.c index b5352e2b121..ce09bbf8a22 100644 --- a/dlls/nsi/tests/nsi.c +++ b/dlls/nsi/tests/nsi.c @@ -938,6 +938,68 @@ static void test_tcp_tables( int family, int table_type ) winetest_pop_context(); }
+static void test_udp_tables( int family ) +{ + DWORD i, err, count, size; + struct nsi_udp_endpoint_key *keys; + struct nsi_udp_endpoint_static *stat; + MIB_UDPTABLE_OWNER_MODULE *table; + MIB_UDP6TABLE_OWNER_MODULE *table6; + MIB_UDPROW_OWNER_MODULE *row; + MIB_UDP6ROW_OWNER_MODULE *row6; + + winetest_push_context( "%s", family == AF_INET ? "AF_INET" : "AF_INET6" ); + + err = NsiAllocateAndGetTable( 1, &NPI_MS_UDP_MODULEID, NSI_UDP_ENDPOINT_TABLE, (void **)&keys, sizeof(*keys), + NULL, 0, NULL, 0, (void **)&stat, sizeof(*stat), &count, 0 ); + ok( !err, "got %x\n", err ); + + size = 0; + err = GetExtendedUdpTable( NULL, &size, 0, family, UDP_TABLE_OWNER_MODULE, 0 ); + size *= 2; + table = malloc( size ); + table6 = (MIB_UDP6TABLE_OWNER_MODULE *)table; + err = GetExtendedUdpTable( table, &size, 0, family, UDP_TABLE_OWNER_MODULE, 0 ); + ok( !err, "got %d\n", err ); + + row = table->table; + row6 = table6->table; + + for (i = 0; i < count; i++) + { + 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->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->dwFlags == stat[i].flags ), "%x vs %x\n", row->dwFlags, stat[i].flags ); + 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" ); + 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( 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->dwFlags == stat[i].flags ), "%x vs %x\n", row6->dwFlags, stat[i].flags ); + ok( unstable( row6->OwningModuleInfo[0] == stat[i].mod_info ), "mismatch\n"); + row6++; + } + } + free( table ); + NsiFreeTable( keys, NULL, NULL, stat ); + winetest_pop_context(); +}
START_TEST( nsi ) { @@ -967,4 +1029,7 @@ START_TEST( nsi ) 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 ); + + test_udp_tables( AF_INET ); + test_udp_tables( AF_INET6 ); } diff --git a/dlls/nsiproxy.sys/Makefile.in b/dlls/nsiproxy.sys/Makefile.in index 3f8965236ba..cee4b72c33f 100644 --- a/dlls/nsiproxy.sys/Makefile.in +++ b/dlls/nsiproxy.sys/Makefile.in @@ -7,4 +7,5 @@ C_SRCS = \ ip.c \ ndis.c \ nsi.c \ - tcp.c + tcp.c \ + udp.c diff --git a/dlls/nsiproxy.sys/nsi.c b/dlls/nsiproxy.sys/nsi.c index ef57c65ce4d..e29446abb50 100644 --- a/dlls/nsiproxy.sys/nsi.c +++ b/dlls/nsiproxy.sys/nsi.c @@ -41,6 +41,7 @@ static const struct module *modules[] = &ipv4_module, &ipv6_module, &tcp_module, + &udp_module, };
static const struct module_table *get_module_table( const NPI_MODULEID *id, DWORD table ) diff --git a/dlls/nsiproxy.sys/nsiproxy_private.h b/dlls/nsiproxy.sys/nsiproxy_private.h index 17e0189b378..7dd8748e3f7 100644 --- a/dlls/nsiproxy.sys/nsiproxy_private.h +++ b/dlls/nsiproxy.sys/nsiproxy_private.h @@ -92,6 +92,24 @@ static inline BOOL convert_index_to_luid( DWORD index, NET_LUID *luid ) return !nsi_get_parameter_ex( ¶ms ); }
+struct ipv6_addr_scope +{ + IN6_ADDR addr; + DWORD scope; +}; + +struct ipv6_addr_scope *get_ipv6_addr_scope_table( unsigned int *size ) DECLSPEC_HIDDEN; +DWORD find_ipv6_addr_scope( const IN6_ADDR *addr, const struct ipv6_addr_scope *table, unsigned int size ) DECLSPEC_HIDDEN; + +struct pid_map +{ + unsigned int pid; + unsigned int unix_pid; +}; + +struct pid_map *get_pid_map( unsigned int *num_entries ) DECLSPEC_HIDDEN; +unsigned int find_owning_pid( struct pid_map *map, unsigned int num_entries, UINT_PTR inode ) DECLSPEC_HIDDEN; + struct module_table { DWORD table; @@ -116,3 +134,4 @@ extern const struct module ndis_module DECLSPEC_HIDDEN; extern const struct module ipv4_module DECLSPEC_HIDDEN; extern const struct module ipv6_module DECLSPEC_HIDDEN; extern const struct module tcp_module DECLSPEC_HIDDEN; +extern const struct module udp_module DECLSPEC_HIDDEN; diff --git a/dlls/nsiproxy.sys/tcp.c b/dlls/nsiproxy.sys/tcp.c index 27f85ee3104..3d0f3a67dd7 100644 --- a/dlls/nsiproxy.sys/tcp.c +++ b/dlls/nsiproxy.sys/tcp.c @@ -230,13 +230,7 @@ static inline MIB_TCP_STATE tcp_state_to_mib_state( int state ) } }
-struct ipv6_addr_scope -{ - IN6_ADDR addr; - DWORD scope; -}; - -static struct ipv6_addr_scope *get_ipv6_addr_scope_table( unsigned int *size ) +struct ipv6_addr_scope *get_ipv6_addr_scope_table( unsigned int *size ) { struct ipv6_addr_scope *table = NULL; unsigned int table_size = 0, num = 0; @@ -323,7 +317,7 @@ failed: return NULL; }
-static DWORD find_ipv6_addr_scope( const IN6_ADDR *addr, const struct ipv6_addr_scope *table, unsigned int size ) +DWORD find_ipv6_addr_scope( const IN6_ADDR *addr, const struct ipv6_addr_scope *table, unsigned int size ) { const BYTE multicast_scope_mask = 0x0F; const BYTE multicast_scope_shift = 0; @@ -343,13 +337,7 @@ static DWORD find_ipv6_addr_scope( const IN6_ADDR *addr, const struct ipv6_addr_ return -1; }
-struct pid_map -{ - unsigned int pid; - unsigned int unix_pid; -}; - -static struct pid_map *get_pid_map( unsigned int *num_entries ) +struct pid_map *get_pid_map( unsigned int *num_entries ) { struct pid_map *map; unsigned int i = 0, buffer_len = 4096, process_count, pos = 0; @@ -405,7 +393,7 @@ static struct pid_map *get_pid_map( unsigned int *num_entries ) return map; }
-static unsigned int find_owning_pid( struct pid_map *map, unsigned int num_entries, UINT_PTR inode ) +unsigned int find_owning_pid( struct pid_map *map, unsigned int num_entries, UINT_PTR inode ) { #ifdef __linux__ unsigned int i, len_socket; diff --git a/dlls/nsiproxy.sys/udp.c b/dlls/nsiproxy.sys/udp.c new file mode 100644 index 00000000000..454aefe95aa --- /dev/null +++ b/dlls/nsiproxy.sys/udp.c @@ -0,0 +1,291 @@ +/* + * nsiproxy.sys udp module + * + * Copyright 2003, 2006, 2011 Juan Lang + * Copyright 2021 Huw Davies + * + * 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 <stdarg.h> + +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_SYS_SOCKETVAR_H +#include <sys/socketvar.h> +#endif + +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif + +#ifdef HAVE_NETINET_IP_VAR_H +#include <netinet/ip_var.h> +#endif + +#ifdef HAVE_NETINET_IN_PCB_H +#include <netinet/in_pcb.h> +#endif + +#ifdef HAVE_NETINET_UDP_H +#include <netinet/udp.h> +#endif + +#ifdef HAVE_NETINET_UDP_VAR_H +#include <netinet/udp_var.h> +#endif + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#define USE_WS_PREFIX +#include "winsock2.h" +#include "ifdef.h" +#include "netiodef.h" +#include "ws2ipdef.h" +#include "udpmib.h" +#include "wine/heap.h" +#include "wine/nsi.h" +#include "wine/debug.h" +#include "wine/server.h" + +#include "nsiproxy_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(nsi); + +static NTSTATUS udp_endpoint_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 ) +{ + DWORD num = 0; + NTSTATUS status = STATUS_SUCCESS; + BOOL want_data = key_size || rw_size || dynamic_size || static_size; + struct nsi_udp_endpoint_key key, *key_out = key_data; + struct nsi_udp_endpoint_static stat, *stat_out = static_data; + struct ipv6_addr_scope *addr_scopes = NULL; + unsigned int addr_scopes_size = 0, pid_map_size = 0; + struct pid_map *pid_map = NULL; + + 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 ); + +#ifdef __linux__ + { + FILE *fp; + char buf[512], *ptr; + int inode; + + if (!(fp = fopen( "/proc/net/udp", "r" ))) return ERROR_NOT_SUPPORTED; + + memset( &key, 0, sizeof(key) ); + memset( &stat, 0, sizeof(stat) ); + pid_map = get_pid_map( &pid_map_size ); + + /* skip header line */ + ptr = fgets( buf, sizeof(buf), fp ); + while ((ptr = fgets( buf, sizeof(buf), fp ))) + { + if (sscanf( ptr, "%*u: %x:%hx %*s %*s %*s %*s %*s %*s %*s %d", + &key.local.Ipv4.sin_addr.WS_s_addr, &key.local.Ipv4.sin_port, &inode ) != 3) + continue; + + key.local.Ipv4.sin_family = WS_AF_INET; + key.local.Ipv4.sin_port = htons( key.local.Ipv4.sin_port ); + + stat.pid = find_owning_pid( pid_map, pid_map_size, inode ); + stat.create_time = 0; /* FIXME */ + stat.flags = 0; /* FIXME */ + stat.mod_info = 0; /* FIXME */ + + if (num < *count) + { + if (key_out) *key_out++ = key; + if (stat_out) *stat_out++ = stat; + } + num++; + } + fclose( fp ); + + if ((fp = fopen( "/proc/net/udp6", "r" ))) + { + memset( &key, 0, sizeof(key) ); + memset( &stat, 0, sizeof(stat) ); + + addr_scopes = get_ipv6_addr_scope_table( &addr_scopes_size ); + + /* 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; + + if (sscanf( ptr, "%*u: %8x%8x%8x%8x:%hx %*s %*s %*s %*s %*s %*s %*s %d", + local_addr, local_addr + 1, local_addr + 2, local_addr + 3, + &key.local.Ipv6.sin6_port, &inode ) != 6) + continue; + key.local.Ipv6.sin6_family = WS_AF_INET6; + key.local.Ipv6.sin6_port = htons( key.local.Ipv6.sin6_port ); + key.local.Ipv6.sin6_scope_id = find_ipv6_addr_scope( &key.local.Ipv6.sin6_addr, addr_scopes, + addr_scopes_size ); + + stat.pid = find_owning_pid( pid_map, pid_map_size, inode ); + stat.create_time = 0; /* FIXME */ + stat.flags = 0; /* FIXME */ + stat.mod_info = 0; /* FIXME */ + + if (num < *count) + { + if (key_out) *key_out++ = key; + if (stat_out) *stat_out++ = stat; + } + num++; + } + fclose( fp ); + } + } +#elif defined(HAVE_SYS_SYSCTL_H) && defined(UDPCTL_PCBLIST) && defined(HAVE_STRUCT_XINPGEN) + { + int mib[] = { CTL_NET, PF_INET, IPPROTO_UDP, UDPCTL_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.udp.pcblist via sysctlbyname!\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.udp.pcblist via sysctlbyname!\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; + + addr_scopes = get_ipv6_addr_scope_table( &addr_scopes_size ); + pid_map = get_pid_map( &pid_map_size ); + + 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 xinpcb *in = (struct xinpcb *)xig; + struct xsocket *sock = &in->xi_socket; +#else + struct inpcb *in = &((struct xinpcb *)xig)->xi_inp; + struct xsocket *sock = &((struct xinpcb *)xig)->xi_socket; +#endif + static const struct in6_addr zero; + + /* Ignore sockets for other protocols */ + if (sock->xso_protocol != IPPROTO_UDP) 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) continue; + if (in->inp_vflag & INP_IPV6 && !memcmp( &in->in6p_laddr, &zero, sizeof(zero) ) && !in->inp_lport) continue; + + if (in->inp_vflag & INP_IPV4) + { + key.local.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; + } + else + { + key.local.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; + key.local.Ipv6.sin6_scope_id = find_ipv6_addr_scope( &key.local.Ipv6.sin6_addr, addr_scopes, + addr_scopes_size ); + } + + stat.pid = find_owning_pid( pid_map, pid_map_size, (UINT_PTR)sock->so_pcb ); + stat.create_time = 0; /* FIXME */ + stat.flags = !(in->inp_flags & INP_ANONPORT); + stat.mod_info = 0; /* FIXME */ + + if (num < *count) + { + if (key_out) *key_out++ = key; + if (stat_out) *stat_out++ = stat; + } + num++; + } + err: + heap_free( buf ); + } +#else + FIXME( "not implemented\n" ); + return STATUS_NOT_IMPLEMENTED; +#endif + + if (!want_data || num <= *count) *count = num; + else status = STATUS_MORE_ENTRIES; + + heap_free( pid_map ); + heap_free( addr_scopes ); + return status; +} + +static struct module_table udp_tables[] = +{ + { + NSI_UDP_ENDPOINT_TABLE, + { + sizeof(struct nsi_udp_endpoint_key), 0, + 0, sizeof(struct nsi_udp_endpoint_static) + }, + udp_endpoint_enumerate_all, + }, + { + ~0u + } +}; + +const struct module udp_module = +{ + &NPI_MS_UDP_MODULEID, + udp_tables +}; diff --git a/include/wine/nsi.h b/include/wine/nsi.h index 39a1ecebda1..8bf3bc1d5d0 100644 --- a/include/wine/nsi.h +++ b/include/wine/nsi.h @@ -346,6 +346,24 @@ struct nsi_tcp_conn_static ULONGLONG mod_info; };
+/* Undocumented NSI UDP tables */ +#define NSI_UDP_ENDPOINT_TABLE 1 + +struct nsi_udp_endpoint_key +{ + SOCKADDR_INET local; +}; + +struct nsi_udp_endpoint_static +{ + DWORD pid; + DWORD unk; + ULONGLONG create_time; + DWORD flags; + DWORD unk2; + ULONGLONG mod_info; +}; + /* Wine specific ioctl interface */
#define IOCTL_NSIPROXY_WINE_ENUMERATE_ALL CTL_CODE(FILE_DEVICE_NETWORK, 0x400, METHOD_BUFFERED, 0)