diff --git a/dlls/nsi/tests/nsi.c b/dlls/nsi/tests/nsi.c index 869fa255bb2..9f6349bd0e7 100644 --- a/dlls/nsi/tests/nsi.c +++ b/dlls/nsi/tests/nsi.c @@ -485,6 +485,121 @@ static void test_ip_unicast( int family ) winetest_pop_context(); } +static void test_ip_forward( int family ) +{ + DWORD rw_sizes[] = { FIELD_OFFSET(struct nsi_ip_forward_rw, unk), + FIELD_OFFSET(struct nsi_ip_forward_rw, unk2), sizeof(struct nsi_ip_forward_rw) }; + DWORD dyn_sizes4[] = { sizeof(struct nsi_ipv4_forward_dynamic) - 3 * sizeof(DWORD), + sizeof(struct nsi_ipv4_forward_dynamic) }; + DWORD dyn_sizes6[] = { sizeof(struct nsi_ipv6_forward_dynamic) - 3 * sizeof(DWORD), + sizeof(struct nsi_ipv6_forward_dynamic) }; + DWORD *dyn_sizes = family == AF_INET ? dyn_sizes4 : dyn_sizes6; + struct nsi_ipv4_forward_key *key_tbl, *key4; + struct nsi_ipv6_forward_key *key6; + struct nsi_ip_forward_rw *rw_tbl, *rw; + struct nsi_ipv4_forward_dynamic *dyn_tbl, *dyn4; + struct nsi_ipv6_forward_dynamic *dyn6; + struct nsi_ip_forward_static *stat_tbl, *stat; + MIB_IPFORWARD_TABLE2 *table; + const NPI_MODULEID *mod = (family == AF_INET) ? &NPI_MS_IPV4_MODULEID : &NPI_MS_IPV6_MODULEID; + DWORD key_size = (family == AF_INET) ? sizeof(*key4) : sizeof(*key6); + DWORD err, count, i, rw_size, dyn_size; + + winetest_push_context( family == AF_INET ? "AF_INET" : "AF_INET6" ); + + for (i = 0; i < ARRAY_SIZE(rw_sizes); i++) + { + err = NsiAllocateAndGetTable( 1, mod, NSI_IP_FORWARD_TABLE, (void **)&key_tbl, key_size, + (void **)&rw_tbl, rw_sizes[i], NULL, 0, + NULL, 0, &count, 0 ); + if (!err) break; + } +todo_wine_if (family == AF_INET6) + ok( !err, "got %d\n", err ); + if (err) { winetest_pop_context(); return; } + rw_size = rw_sizes[i]; + NsiFreeTable( key_tbl, rw_tbl, NULL, NULL ); + + for (i = 0; i < ARRAY_SIZE(dyn_sizes4); i++) + { + err = NsiAllocateAndGetTable( 1, mod, NSI_IP_FORWARD_TABLE, (void **)&key_tbl, key_size, + (void **)&rw_tbl, rw_size, (void **)&dyn_tbl, dyn_sizes[i], + (void **)&stat_tbl, sizeof(*stat_tbl), &count, 0 ); + if (!err) break; + } + ok( !err, "got %d\n", err ); + dyn_size = dyn_sizes[i]; + + err = GetIpForwardTable2( family, &table ); +todo_wine + ok( !err, "got %d\n", err ); + if (err) { winetest_pop_context(); return; } + ok( table->NumEntries == count, "table entries %d count %d\n", table->NumEntries, count ); + + for (i = 0; i < count; i++) + { + MIB_IPFORWARD_ROW2 *row = table->Table + i; + rw = (struct nsi_ip_forward_rw *)((BYTE *)rw_tbl + i * rw_size); + stat = stat_tbl + i; + winetest_push_context( "%d", i ); + + ok( row->DestinationPrefix.Prefix.si_family == family, "mismatch\n" ); + + if (family == AF_INET) + { + key4 = key_tbl + i; + dyn4 = (struct nsi_ipv4_forward_dynamic *)((BYTE *)dyn_tbl + i * dyn_size); + + ok( row->InterfaceLuid.Value == key4->luid.Value, "mismatch\n" ); + ok( row->InterfaceLuid.Value == key4->luid2.Value, "mismatch\n" ); + ok( row->DestinationPrefix.Prefix.Ipv4.sin_addr.s_addr == key4->prefix.s_addr, "mismatch\n" ); + ok( row->DestinationPrefix.Prefix.Ipv4.sin_port == 0, "mismatch\n" ); + ok( row->DestinationPrefix.PrefixLength == key4->prefix_len, "mismatch\n" ); + ok( row->NextHop.Ipv4.sin_addr.s_addr == key4->next_hop.s_addr, "mismatch\n" ); + ok( row->NextHop.Ipv4.sin_port == 0, "mismatch\n" ); + ok( row->Age == dyn4->age, "mismatch\n" ); + } + else + { + key6 = (struct nsi_ipv6_forward_key *)key_tbl + i; + dyn6 = (struct nsi_ipv6_forward_dynamic *)((BYTE *)dyn_tbl + i * dyn_size); + + ok( row->InterfaceLuid.Value == key6->luid.Value, "mismatch\n" ); + ok( row->InterfaceLuid.Value == key6->luid2.Value, "mismatch\n" ); + ok( !memcmp( &row->DestinationPrefix.Prefix.Ipv6.sin6_addr, &key6->prefix, sizeof(key6->prefix) ), + "mismatch\n" ); + ok( row->DestinationPrefix.Prefix.Ipv6.sin6_port == 0, "mismatch\n" ); + ok( row->DestinationPrefix.Prefix.Ipv6.sin6_flowinfo == 0, "mismatch\n" ); + ok( row->DestinationPrefix.Prefix.Ipv6.sin6_scope_id == 0, "mismatch\n" ); + ok( row->DestinationPrefix.PrefixLength == key6->prefix_len, "mismatch\n" ); + ok( !memcmp( &row->NextHop.Ipv6.sin6_addr, &key6->next_hop, sizeof(key6->next_hop) ), "mismatch\n" ); + ok( row->NextHop.Ipv6.sin6_port == 0, "mismatch\n" ); + ok( row->NextHop.Ipv6.sin6_flowinfo == 0, "mismatch\n" ); + ok( row->NextHop.Ipv6.sin6_scope_id == 0, "mismatch\n" ); + ok( row->Age == dyn6->age, "mismatch\n" ); + } + + ok( row->InterfaceIndex == stat->if_index, "mismatch\n" ); + ok( row->SitePrefixLength == rw->site_prefix_len, "mismatch\n" ); + ok( row->ValidLifetime == rw->valid_lifetime, "mismatch\n" ); + ok( row->PreferredLifetime == rw->preferred_lifetime, "mismatch\n" ); + + ok( row->Metric == rw->metric, "mismatch\n" ); + ok( row->Protocol == rw->protocol, "mismatch\n" ); + ok( row->Loopback == rw->loopback, "mismatch\n" ); + ok( row->AutoconfigureAddress == rw->autoconf, "mismatch\n" ); + ok( row->Publish == rw->publish, "mismatch\n" ); + ok( row->Immortal == rw->immortal, "mismatch\n" ); + ok( row->Origin == stat->origin, "mismatch\n" ); + + winetest_pop_context(); + } + + FreeMibTable( table ); + NsiFreeTable( key_tbl, rw_tbl, dyn_tbl, stat_tbl ); + winetest_pop_context(); +} + START_TEST( nsi ) { test_nsi_api(); @@ -494,4 +609,6 @@ START_TEST( nsi ) test_ip_unicast( AF_INET ); test_ip_unicast( AF_INET6 ); + test_ip_forward( AF_INET ); + test_ip_forward( AF_INET6 ); } diff --git a/dlls/nsiproxy.sys/ip.c b/dlls/nsiproxy.sys/ip.c index 93fdefa24cc..dbd019c8442 100644 --- a/dlls/nsiproxy.sys/ip.c +++ b/dlls/nsiproxy.sys/ip.c @@ -25,6 +25,14 @@ #include #endif +#ifdef HAVE_NET_ROUTE_H +#include +#endif + +#ifdef HAVE_SYS_SYSCTL_H +#include +#endif + #ifdef HAVE_NETINET_IN_H #include #endif @@ -45,6 +53,7 @@ #include "nldef.h" #include "ifdef.h" #include "netiodef.h" +#include "wine/heap.h" #include "wine/nsi.h" #include "wine/debug.h" @@ -221,6 +230,249 @@ static NTSTATUS ip_unicast_get_all_parameters( const void *key, DWORD key_size, return status; } +struct ipv4_route_data +{ + NET_LUID luid; + DWORD if_index; + struct in_addr prefix; + DWORD prefix_len; + struct in_addr next_hop; + DWORD metric; + DWORD protocol; + BYTE loopback; +}; + +static void ipv4_forward_fill_entry( struct ipv4_route_data *entry, struct nsi_ipv4_forward_key *key, + struct nsi_ip_forward_rw *rw, struct nsi_ipv4_forward_dynamic *dyn, + struct nsi_ip_forward_static *stat ) +{ + if (key) + { + key->unk = 0; + key->prefix.WS_s_addr = entry->prefix.s_addr; + key->prefix_len = entry->prefix_len; + memset( key->unk2, 0, sizeof(key->unk2) ); + memset( key->unk3, 0, sizeof(key->unk3) ); + key->luid = entry->luid; + key->luid2 = entry->luid; + key->next_hop.WS_s_addr = entry->next_hop.s_addr; + key->pad = 0; + } + + if (rw) + { + rw->site_prefix_len = 0; + rw->valid_lifetime = ~0u; + rw->preferred_lifetime = ~0u; + rw->metric = entry->metric; + rw->protocol = entry->protocol; + rw->loopback = entry->loopback; + rw->autoconf = 1; + rw->publish = 0; + rw->immortal = 1; + memset( rw->unk, 0, sizeof(rw->unk) ); + rw->unk2 = 0; + } + + if (dyn) + { + memset( dyn, 0, sizeof(*dyn) ); + } + + if (stat) + { + stat->origin = NlroManual; + stat->if_index = entry->if_index; + } +} + +static NTSTATUS ipv4_forward_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 ipv4_route_data entry; + + 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__ + { + char buf[512], *ptr; + struct in_addr mask; + DWORD rtf_flags; + FILE *fp; + + if (!(fp = fopen( "/proc/net/route", "r" ))) return STATUS_NOT_SUPPORTED; + + /* skip header line */ + fgets( buf, sizeof(buf), fp ); + while ((ptr = fgets( buf, sizeof(buf), fp ))) + { + while (!isspace( *ptr )) ptr++; + *ptr++ = '\0'; + + if (!convert_unix_name_to_luid( buf, &entry.luid )) continue; + if (!convert_luid_to_index( &entry.luid, &entry.if_index )) continue; + + entry.prefix.s_addr = strtoul( ptr, &ptr, 16 ); + entry.next_hop.s_addr = strtoul( ptr + 1, &ptr, 16 ); + rtf_flags = strtoul( ptr + 1, &ptr, 16 ); + strtoul( ptr + 1, &ptr, 16 ); /* refcount, skip */ + strtoul( ptr + 1, &ptr, 16 ); /* use, skip */ + entry.metric = strtoul( ptr + 1, &ptr, 16 ); + mask.s_addr = strtoul( ptr + 1, &ptr, 16 ); + entry.prefix_len = mask_v4_to_prefix( &mask ); + entry.protocol = (rtf_flags & RTF_GATEWAY) ? MIB_IPPROTO_NETMGMT : MIB_IPPROTO_LOCAL; + entry.loopback = entry.protocol == MIB_IPPROTO_LOCAL && entry.prefix_len == 32; + + if (num < *count) + { + ipv4_forward_fill_entry( &entry, key_data, rw_data, dynamic_data, static_data ); + key_data = (BYTE *)key_data + key_size; + rw_data = (BYTE *)rw_data + rw_size; + dynamic_data = (BYTE *)dynamic_data + dynamic_size; + static_data = (BYTE *)static_data + static_size; + } + num++; + } + fclose( fp ); + } +#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, *addr_ptr; + struct rt_msghdr *rtm; + + if (sysctl( mib, ARRAY_SIZE(mib), NULL, &needed, NULL, 0 ) < 0) return STATUS_NOT_SUPPORTED; + + buf = heap_alloc( needed ); + if (!buf) return STATUS_NO_MEMORY; + + if (sysctl( mib, 6, buf, &needed, NULL, 0 ) < 0) + { + heap_free( buf ); + return STATUS_NOT_SUPPORTED; + } + + lim = buf + needed; + for (next = buf; next < lim; next += rtm->rtm_msglen) + { + int i; + sa_family_t dst_family = AF_UNSPEC; + + rtm = (struct rt_msghdr *)next; + + if (rtm->rtm_type != RTM_GET) + { + WARN( "Got unexpected message type 0x%x!\n", rtm->rtm_type ); + continue; + } + + /* Ignore gateway routes which are multicast */ + if ((rtm->rtm_flags & RTF_GATEWAY) && (rtm->rtm_flags & RTF_MULTICAST)) continue; + + entry.if_index = rtm->rtm_index; + if (!convert_index_to_luid( entry.if_index, &entry.luid )) continue; + entry.protocol = (rtm->rtm_flags & RTF_GATEWAY) ? MIB_IPPROTO_NETMGMT : MIB_IPPROTO_LOCAL; + entry.metric = rtm->rtm_rmx.rmx_hopcount; + + addr_ptr = (char *)(rtm + 1); + + for (i = 1; i; i <<= 1) + { + struct sockaddr *sa; + struct in_addr addr; + + if (!(i & rtm->rtm_addrs)) continue; + + sa = (struct sockaddr *)addr_ptr; + if (addr_ptr + sa->sa_len > next + rtm->rtm_msglen) + { + ERR( "struct sockaddr extends beyond the route message, %p > %p\n", + addr_ptr + sa->sa_len, next + rtm->rtm_msglen ); + } + + if (sa->sa_len) addr_ptr += (sa->sa_len + sizeof(int)-1) & ~(sizeof(int)-1); + else addr_ptr += sizeof(int); + /* Apple's netstat prints the netmask together with the destination + * and only looks at the destination's address family. The netmask's + * sa_family sometimes contains the non-existent value 0xff. */ + switch (i == RTA_NETMASK ? dst_family : sa->sa_family) + { + case AF_INET: + { + /* Netmasks (and possibly other addresses) have only enough size + * to represent the non-zero bits, e.g. a netmask of 255.0.0.0 has + * 5 bytes (1 sa_len, 1 sa_family, 2 sa_port and 1 for the first + * byte of sin_addr). */ + struct sockaddr_in sin = {0}; + memcpy( &sin, sa, sa->sa_len ); + addr = sin.sin_addr; + break; + } +#ifdef AF_LINK + case AF_LINK: + if (i == RTA_GATEWAY && entry.protocol == MIB_IPPROTO_NETMGMT) + { + /* For direct route we may simply use dest addr as next hop */ + C_ASSERT(RTA_DST < RTA_GATEWAY); + addr = entry.prefix; + break; + } + /* fallthrough */ +#endif + default: + WARN( "Received unsupported sockaddr family 0x%x\n", sa->sa_family ); + addr.s_addr = 0; + } + switch (i) + { + case RTA_DST: + entry.prefix = addr; + dst_family = sa->sa_family; + break; + case RTA_GATEWAY: entry.next_hop = addr; break; + case RTA_NETMASK: entry.prefix_len = mask_v4_to_prefix( &addr ); break; + default: + WARN( "Unexpected address type 0x%x\n", i ); + } + } + + if (num < *count) + { + ipv4_forward_fill_entry( &entry, key_data, rw_data, dynamic_data, static_data ); + key_data = (BYTE *)key_data + key_size; + rw_data = (BYTE *)rw_data + rw_size; + dynamic_data = (BYTE *)dynamic_data + dynamic_size; + static_data = (BYTE *)static_data + static_size; + } + num++; + } + HeapFree( GetProcessHeap (), 0, buf ); + } +#else + FIXME( "not implemented\n" ); + return STATUS_NOT_IMPLEMENTED; +#endif + + if (!want_data || num <= *count) *count = num; + else status = STATUS_MORE_ENTRIES; + + return status; +} + +static NTSTATUS ipv6_forward_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 ) +{ + FIXME( "not implemented\n" ); + return STATUS_NOT_IMPLEMENTED; +} + static struct module_table ipv4_tables[] = { { @@ -232,6 +484,14 @@ static struct module_table ipv4_tables[] = ip_unicast_enumerate_all, ip_unicast_get_all_parameters, }, + { + NSI_IP_FORWARD_TABLE, + { + sizeof(struct nsi_ipv4_forward_key), sizeof(struct nsi_ip_forward_rw), + sizeof(struct nsi_ipv4_forward_dynamic), sizeof(struct nsi_ip_forward_static) + }, + ipv4_forward_enumerate_all, + }, { ~0u } @@ -254,6 +514,14 @@ static struct module_table ipv6_tables[] = ip_unicast_enumerate_all, ip_unicast_get_all_parameters, }, + { + NSI_IP_FORWARD_TABLE, + { + sizeof(struct nsi_ipv6_forward_key), sizeof(struct nsi_ip_forward_rw), + sizeof(struct nsi_ipv6_forward_dynamic), sizeof(struct nsi_ip_forward_static) + }, + ipv6_forward_enumerate_all, + }, { ~0u } diff --git a/dlls/nsiproxy.sys/nsiproxy_private.h b/dlls/nsiproxy.sys/nsiproxy_private.h index 0f6e7b05c59..44409ae159e 100644 --- a/dlls/nsiproxy.sys/nsiproxy_private.h +++ b/dlls/nsiproxy.sys/nsiproxy_private.h @@ -25,6 +25,44 @@ NTSTATUS nsi_get_parameter_ex( struct nsi_get_parameter_ex *params ) DECLSPEC_HI BOOL convert_luid_to_unix_name( const NET_LUID *luid, const char **unix_name ) DECLSPEC_HIDDEN; BOOL convert_unix_name_to_luid( const char *unix_name, NET_LUID *luid ) DECLSPEC_HIDDEN; +static inline BOOL convert_luid_to_index( const NET_LUID *luid, DWORD *index ) +{ + struct nsi_get_parameter_ex params; + params.unknown[0] = 0; + params.unknown[1] = 0; + params.first_arg = 1; + params.unknown2 = 0; + params.module = &NPI_MS_NDIS_MODULEID; + params.table = NSI_NDIS_IFINFO_TABLE; + params.key = luid; + params.key_size = sizeof(*luid); + params.param_type = NSI_PARAM_TYPE_STATIC; + params.data = index; + params.data_size = sizeof(*index); + params.data_offset = FIELD_OFFSET(struct nsi_ndis_ifinfo_static, if_index); + + return !nsi_get_parameter_ex( ¶ms ); +} + +static inline BOOL convert_index_to_luid( DWORD index, NET_LUID *luid ) +{ + struct nsi_get_parameter_ex params; + params.unknown[0] = 0; + params.unknown[1] = 0; + params.first_arg = 1; + params.unknown2 = 0; + params.module = &NPI_MS_NDIS_MODULEID; + params.table = NSI_NDIS_INDEX_LUID_TABLE; + params.key = &index; + params.key_size = sizeof(index); + params.param_type = NSI_PARAM_TYPE_STATIC; + params.data = luid; + params.data_size = sizeof(*luid); + params.data_offset = 0; + + return !nsi_get_parameter_ex( ¶ms ); +} + struct module_table { DWORD table; diff --git a/include/netioapi.h b/include/netioapi.h index 4735620dedb..44d877ae3c3 100644 --- a/include/netioapi.h +++ b/include/netioapi.h @@ -254,6 +254,8 @@ DWORD WINAPI GetIfEntry2(MIB_IF_ROW2*); DWORD WINAPI GetIfEntry2Ex(MIB_IF_TABLE_LEVEL,MIB_IF_ROW2*); DWORD WINAPI GetIfTable2(MIB_IF_TABLE2**); DWORD WINAPI GetIfTable2Ex(MIB_IF_TABLE_LEVEL,MIB_IF_TABLE2**); +DWORD WINAPI GetIpForwardEntry2(MIB_IPFORWARD_ROW2*); +DWORD WINAPI GetIpForwardTable2(ADDRESS_FAMILY,MIB_IPFORWARD_TABLE2**); DWORD WINAPI GetIpInterfaceTable(ADDRESS_FAMILY,MIB_IPINTERFACE_TABLE**); DWORD WINAPI GetUnicastIpAddressEntry(MIB_UNICASTIPADDRESS_ROW*); DWORD WINAPI GetUnicastIpAddressTable(ADDRESS_FAMILY,MIB_UNICASTIPADDRESS_TABLE**); diff --git a/include/wine/nsi.h b/include/wine/nsi.h index bba5be11e50..dd18c7c7511 100644 --- a/include/wine/nsi.h +++ b/include/wine/nsi.h @@ -98,6 +98,7 @@ struct nsi_ndis_ifinfo_static /* Undocumented NSI IP tables */ #define NSI_IP_UNICAST_TABLE 10 +#define NSI_IP_FORWARD_TABLE 16 struct nsi_ipv4_unicast_key { @@ -133,6 +134,67 @@ struct nsi_ip_unicast_static ULONG64 creation_time; }; +struct nsi_ipv4_forward_key +{ + DWORD unk; + IN_ADDR prefix; + BYTE prefix_len; + BYTE unk2[3]; + DWORD unk3[3]; + NET_LUID luid; + NET_LUID luid2; + IN_ADDR next_hop; + DWORD pad; +}; + +struct nsi_ipv6_forward_key +{ + DWORD unk; + IN6_ADDR prefix; + BYTE prefix_len; + BYTE unk2[3]; + DWORD unk3[3]; + DWORD pad; + NET_LUID luid; + NET_LUID luid2; + IN6_ADDR next_hop; +}; + +struct nsi_ip_forward_rw +{ + DWORD site_prefix_len; + DWORD valid_lifetime; + DWORD preferred_lifetime; + DWORD metric; + DWORD protocol; + BYTE loopback; + BYTE autoconf; + BYTE publish; + BYTE immortal; + BYTE unk[4]; + DWORD unk2; +}; + +struct nsi_ipv4_forward_dynamic +{ + DWORD age; + DWORD unk[3]; + IN_ADDR addr2; /* often a repeat of prefix */ +}; + +struct nsi_ipv6_forward_dynamic +{ + DWORD age; + DWORD unk[3]; + IN6_ADDR addr2; /* often a repeat of prefix */ +}; + +struct nsi_ip_forward_static +{ + DWORD origin; + DWORD if_index; +}; + /* Wine specific ioctl interface */ #define IOCTL_NSIPROXY_WINE_ENUMERATE_ALL CTL_CODE(FILE_DEVICE_NETWORK, 0x400, METHOD_BUFFERED, 0)