A major problem with this patch is that the function is agnostic to the source argument. Even after some tinkering I still haven't figured out how exactly Windows uses this argument.
Also, possibly move GetBestRoute2 implementation to nsiproxy.sys if applicable?
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- include/netioapi.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/netioapi.h b/include/netioapi.h index 6005e58e216..832f350b861 100644 --- a/include/netioapi.h +++ b/include/netioapi.h @@ -250,6 +250,7 @@ DWORD WINAPI ConvertInterfaceNameToLuidA(const char*,NET_LUID*); DWORD WINAPI ConvertInterfaceNameToLuidW(const WCHAR*,NET_LUID*); DWORD WINAPI ConvertLengthToIpv4Mask(ULONG,ULONG*); void WINAPI FreeMibTable(void*); +DWORD WINAPI GetBestRoute2(NET_LUID*,NET_IFINDEX,const SOCKADDR_INET*,const SOCKADDR_INET*,ULONG,PMIB_IPFORWARD_ROW2,SOCKADDR_INET*); DWORD WINAPI GetIfEntry2(MIB_IF_ROW2*); DWORD WINAPI GetIfEntry2Ex(MIB_IF_TABLE_LEVEL,MIB_IF_ROW2*); DWORD WINAPI GetIfTable2(MIB_IF_TABLE2**);
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/iphlpapi/tests/iphlpapi.c | 224 +++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+)
diff --git a/dlls/iphlpapi/tests/iphlpapi.c b/dlls/iphlpapi/tests/iphlpapi.c index 29256eb2774..f2148d29b52 100644 --- a/dlls/iphlpapi/tests/iphlpapi.c +++ b/dlls/iphlpapi/tests/iphlpapi.c @@ -2391,6 +2391,229 @@ static void test_GetUnicastIpAddressTable(void) FreeMibTable( table ); }
+static void test_GetBestRoute2(void) +{ + DWORD apiReturn; + SOCKADDR_INET source, destination, bestaddress, bestaddress_memory; + MIB_IPFORWARD_ROW2 bestroute, bestroute_memory; + int validmemflags = 0; + static const MIB_IPFORWARD_ROW2 route_zero; + static const NET_LUID zero_luid, ones_luid = { -1 }; + static const SOCKADDR_INET unspecaddr; + static const SOCKADDR invalidaddr = { -1 }; + static const SOCKADDR_IN in4any = { AF_INET }; + static const SOCKADDR_IN in4loopback = { AF_INET, 0, {{{ 127, 0, 0, 1 }}} }; + static const SOCKADDR_IN in4broadcast = { AF_INET, 0, {{{ 255, 255, 255, 255 }}} }; + static const SOCKADDR_IN6 in6any = { AF_INET6 }; + static const SOCKADDR_IN6 in6loopback = { AF_INET6, 0, 0, {{{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }}} }; + static const SOCKADDR_IN6 in6broadcast = { AF_INET6, 0, 0, {{{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }}} }; + struct sockaddr_ptrlen + { + const struct sockaddr *ptr; + size_t len; + }; + static const struct getbestroute_test + { + int line; + const NET_LUID *luid; + NET_IFINDEX ifindex; + struct sockaddr_ptrlen source; + struct sockaddr_ptrlen destination; + ULONG options; + DWORD result; + BOOL todo; + int memload; + int memstore; + } getbestroute_tests[] = { +#define SA_PL(x) { ((struct sockaddr *)&(x)), sizeof(x) } + { __LINE__, &zero_luid, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, &zero_luid, -1, { NULL }, SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, &ones_luid, 0, { NULL }, SA_PL(in4loopback), 0, ERROR_NOT_FOUND }, + { __LINE__, NULL, -1, { NULL }, SA_PL(in4loopback), 0, ERROR_FILE_NOT_FOUND }, + { __LINE__, &zero_luid, 0, SA_PL(unspecaddr), SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, &ones_luid, 0, SA_PL(unspecaddr), SA_PL(in4loopback), 0, ERROR_NOT_FOUND }, + { __LINE__, NULL, 0, SA_PL(unspecaddr), SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, NULL, 0, SA_PL(in4any), SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, NULL, 0, SA_PL(in4loopback), SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, NULL, 0, SA_PL(in4broadcast), SA_PL(in4loopback), 0, ERROR_INVALID_PARAMETER, TRUE }, + { __LINE__, NULL, 0, SA_PL(in6any), SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, NULL, 0, SA_PL(in6loopback), SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, NULL, 0, SA_PL(in6broadcast), SA_PL(in4loopback), 0, NO_ERROR }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0, 0xc7 }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc0 }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc2 }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc4 }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc6 }, +#if 0 /* not reliable (fails spuriously) on Wine */ + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc1 }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc3 }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc5 }, + { __LINE__, NULL, 0, { NULL }, SA_PL(in4loopback), 0, NO_ERROR, FALSE, 0xc7 }, +#endif +#undef SA_PL + }; + UINT i; + + memset( &destination, 0, sizeof(destination) ); + destination.Ipv4.sin_family = AF_INET; + destination.Ipv4.sin_addr.S_un.S_addr = htonl( INADDR_ANY ); + apiReturn = GetBestRoute2( NULL, 0, NULL, &destination, 0, &bestroute, &bestaddress ); + trace( "GetBestRoute2(NULL, 0, NULL, [...], 0, [...], [...]) = %lu\n", apiReturn ); + if (apiReturn == ERROR_NOT_SUPPORTED) + { + skip("GetBestRoute2 not supported\n"); + return; + } + + apiReturn = GetBestRoute2( NULL, 0, NULL, NULL, 0, NULL, NULL ); + ok( apiReturn == ERROR_INVALID_PARAMETER, + "GetBestRoute2(NULL, 0, NULL, NULL, 0, NULL, NULL) returned %lu, expected %d\n", + apiReturn, ERROR_INVALID_PARAMETER ); + + apiReturn = GetBestRoute2( NULL, 0, NULL, NULL, 0, &bestroute, NULL ); + ok( apiReturn == ERROR_INVALID_PARAMETER, + "GetBestRoute2(NULL, 0, NULL, NULL, 0, %p, NULL) returned %lu, expected %d\n", + &bestroute, apiReturn, ERROR_INVALID_PARAMETER ); + + apiReturn = GetBestRoute2( NULL, 0, NULL, NULL, 0, NULL, &bestaddress ); + memcpy( &bestaddress, &invalidaddr, sizeof(invalidaddr) ); + ok( apiReturn == ERROR_INVALID_PARAMETER, + "GetBestRoute2(NULL, 0, NULL, NULL, 0, NULL, %p) returned %lu, expected %d\n", + &bestaddress, apiReturn, ERROR_INVALID_PARAMETER ); + ok( memcmp(&bestaddress, &invalidaddr, sizeof(invalidaddr)) == 0, "bestaddress(.si_family = %u) has changed\n", bestaddress.si_family ); + + apiReturn = GetBestRoute2( NULL, 0, NULL, NULL, 0, &bestroute, &bestaddress ); + memcpy( &bestaddress, &invalidaddr, sizeof(invalidaddr) ); + ok( apiReturn == ERROR_INVALID_PARAMETER, + "GetBestRoute2(NULL, 0, NULL, NULL, 0, %p, %p) returned %lu, expected %d\n", + &bestroute, &bestaddress, apiReturn, ERROR_INVALID_PARAMETER ); + ok( memcmp(&bestaddress, &invalidaddr, sizeof(invalidaddr)) == 0, "bestaddress(.si_family = %u) has changed\n", bestaddress.si_family ); + + memset( &destination, 0, sizeof(destination) ); + destination.Ipv4.sin_family = AF_INET; + destination.Ipv4.sin_addr.S_un.S_addr = htonl( INADDR_LOOPBACK ); + memcpy( &bestaddress, &invalidaddr, sizeof(invalidaddr) ); + apiReturn = GetBestRoute2( NULL, 0, NULL, &destination, 0, &bestroute, NULL ); + ok( apiReturn == ERROR_INVALID_PARAMETER, + "GetBestRoute2(NULL, 0, NULL, [127.0.0.1], 0, %p, NULL) returned %lu, expected %d\n", + &bestroute, apiReturn, ERROR_INVALID_PARAMETER ); + ok( memcmp(&bestaddress, &invalidaddr, sizeof(invalidaddr)) == 0, "bestaddress(.si_family = %u) has changed\n", bestaddress.si_family ); + + memset( &destination, 0, sizeof(destination) ); + destination.Ipv4.sin_family = AF_INET; + destination.Ipv4.sin_addr.S_un.S_addr = htonl( INADDR_LOOPBACK ); + memcpy( &bestaddress, &invalidaddr, sizeof(invalidaddr) ); + apiReturn = GetBestRoute2( NULL, 0, NULL, &destination, 0, NULL, &bestaddress ); + ok( apiReturn == ERROR_INVALID_PARAMETER, + "GetBestRoute2(NULL, 0, NULL, [127.0.0.1], 0, NULL, %p) returned %lu, expected %d\n", + &bestaddress, apiReturn, ERROR_INVALID_PARAMETER ); + ok( memcmp(&bestaddress, &invalidaddr, sizeof(invalidaddr)) == 0, "bestaddress(.si_family = %u) has changed\n", bestaddress.si_family ); + + memset( &destination, 0, sizeof(destination) ); + memcpy( &bestaddress, &invalidaddr, sizeof(invalidaddr) ); + apiReturn = GetBestRoute2( NULL, 0, NULL, &destination, 0, NULL, &bestaddress ); + ok( apiReturn == ERROR_INVALID_PARAMETER, + "GetBestRoute2(NULL, 0, NULL, <AF_UNSPEC>, 0, NULL, %p) returned %lu, expected %d\n", + &bestaddress, apiReturn, ERROR_INVALID_PARAMETER ); + ok( memcmp(&bestaddress, &invalidaddr, sizeof(invalidaddr)) == 0, "bestaddress(.si_family = %u) has changed\n", bestaddress.si_family ); + + for (i = 0; i < ARRAY_SIZE(getbestroute_tests); i++) + { + const struct getbestroute_test *item = &getbestroute_tests[i]; + int validflags = item->memload & validmemflags; + NET_LUID luid; + NET_IFINDEX ifindex = 0; + + winetest_push_context("Subtest #%u (decl at line %d)", i, item->line); + + ok( (item->memload & ~validmemflags) == 0, + "current subtest may be incomplete due to previous test failure (memload = %d, validmemflags = %d)\n", + item->memload, validmemflags ); + + if (item->luid) + { + memcpy(&luid, item->luid, sizeof(luid)); + validflags |= 1; + } + else if (validflags & 1) + { + memcpy(&luid, &bestroute_memory.InterfaceLuid, sizeof(luid)); + } + + if (item->ifindex) + { + ifindex = item->ifindex; + validflags |= 2; + } + else if (validflags & 2) + { + ifindex = bestroute_memory.InterfaceIndex; + } + + if (item->source.len > sizeof(source)) abort(); /* prevent memory corruption */ + if (item->source.ptr) + { + memset( &source, 0, sizeof(source) ); + memcpy( &source, item->source.ptr, item->source.len ); + validflags |= 4; + } + else if (validflags & 4) + { + memcpy( &source, &bestaddress_memory, sizeof(source) ); + } + + if (item->destination.len > sizeof(destination)) abort(); /* prevent memory corruption */ + memset( &destination, 0, sizeof(destination) ); + memcpy( &destination, item->destination.ptr, item->destination.len ); + + apiReturn = GetBestRoute2( (validflags & 1) ? &luid : NULL, + (validflags & 2) ? ifindex : 0, + (validflags & 4) ? &source : NULL, + &destination, + item->options, + &bestroute, &bestaddress ); + todo_wine_if( item->todo ) + ok( apiReturn == item->result, + "GetBestRoute2 returned %lu, expected %lu\n", apiReturn, item->result ); + if (apiReturn == NO_ERROR) + { + ok( bestaddress.si_family == destination.si_family, + "bestaddress.si_family (%u) shall equal destination.si_family (%u)\n", + bestaddress.si_family, destination.si_family ); + + if (validflags & 0x40) + { + ok( memcmp( &bestroute_memory, &bestroute, sizeof(bestroute) ) == 0, + "returned bestroute does not match last cached value\n" ); + } + + if (validflags & 0x80) + { + ok( memcmp( &bestaddress_memory, &bestaddress, sizeof(bestaddress) ) == 0, + "returned bestaddress does not match last cached value\n" ); + } + + if (item->memstore) + { + memcpy( &bestroute_memory, &bestroute, sizeof(bestroute_memory) ); + memcpy( &bestaddress_memory, &bestaddress, sizeof(bestaddress_memory) ); + validmemflags = item->memstore; + } + } + else + { + ok( memcmp( &bestaddress, &unspecaddr, sizeof(bestaddress) ) == 0, "bestaddress shall be zeroed by GetBestRoute2\n" ); + ok( memcmp( &bestroute, &route_zero, sizeof(bestroute) ) == 0, "bestroute shall be zeroed by GetBestRoute2\n" ); + } + + winetest_pop_context(); + } +} + static void test_ConvertLengthToIpv4Mask(void) { DWORD ret; @@ -2703,6 +2926,7 @@ START_TEST(iphlpapi) test_GetIfTable2Ex(); test_GetUnicastIpAddressEntry(); test_GetUnicastIpAddressTable(); + test_GetBestRoute2(); test_ConvertLengthToIpv4Mask(); test_GetTcp6Table(); test_GetUdp6Table();
From: Jinoh Kang jinoh.kang.kr@gmail.com
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/iphlpapi/iphlpapi_main.c | 137 +++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 3 deletions(-)
diff --git a/dlls/iphlpapi/iphlpapi_main.c b/dlls/iphlpapi/iphlpapi_main.c index 33b12361d70..32e1d7e23a7 100644 --- a/dlls/iphlpapi/iphlpapi_main.c +++ b/dlls/iphlpapi/iphlpapi_main.c @@ -4441,16 +4441,147 @@ DWORD WINAPI GetBestRoute2(NET_LUID *luid, NET_IFINDEX index, ULONG options, PMIB_IPFORWARD_ROW2 bestroute, SOCKADDR_INET *bestaddress) { - static int once; + MIB_IPFORWARD_TABLE2 *table; + MIB_UNICASTIPADDRESS_TABLE *table_ip; + const MIB_IPFORWARD_ROW2 *matched; + NET_LUID luid_match; + DWORD ndx, ndx_ip; + SOCKADDR_INET dest, matched_src; + UINT addr_bytes; + const BYTE *dest_prefix; + DWORD ret; + static unsigned int once;
if (!once++) - FIXME("(%p, %ld, %p, %p, 0x%08lx, %p, %p): stub\n", luid, index, source, + FIXME("(%p, %ld, %p, %p, 0x%08lx, %p, %p): partial stub\n", luid, index, source, destination, options, bestroute, bestaddress);
if (!destination || !bestroute || !bestaddress) return ERROR_INVALID_PARAMETER;
- return ERROR_NOT_SUPPORTED; + memcpy( &dest, destination, sizeof(dest) ); + switch (dest.si_family) + { + case AF_INET: + addr_bytes = sizeof(dest.Ipv4.sin_addr); + dest_prefix = (const BYTE *)&dest.Ipv4.sin_addr; + break; + case AF_INET6: + addr_bytes = sizeof(dest.Ipv6.sin6_addr); + dest_prefix = (const BYTE *)&dest.Ipv6.sin6_addr; + break; + default: + return ERROR_INVALID_PARAMETER; + } + + if (luid) + { + NET_IFINDEX unused; + memcpy( &luid_match, luid, sizeof(luid_match) ); + if (luid_match.Value) + { + /* check LUID existence */ + ret = ConvertInterfaceLuidToIndex( &luid_match, &unused ); + if (ret) + { + if (ret == ERROR_FILE_NOT_FOUND) ret = ERROR_NOT_FOUND; + goto fail; + } + } + } + else if (index) + { + ret = ConvertInterfaceIndexToLuid( index, &luid_match ); + if (ret) goto fail; + } + else + { + memset( &luid_match, 0, sizeof(luid_match) ); + } + + ret = GetIpForwardTable2( dest.si_family, &table ); + if (ret) goto fail; + + ret = GetUnicastIpAddressTable( dest.si_family, &table_ip ); + if (ret) goto free_ipforwardtable2; + + for (ndx = 0, matched = NULL; ndx < table->NumEntries; ndx++) + { + const MIB_IPFORWARD_ROW2 *row = &table->Table[ndx]; + const BYTE *row_prefix; + const SOCKADDR_INET *forward_src; + UINT prefix_bytes, prefix_lastshr; + + if (luid_match.Value && row->InterfaceLuid.Value != luid_match.Value) continue; + if (row->DestinationPrefix.Prefix.si_family != dest.si_family) continue; + + if (row->DestinationPrefix.PrefixLength > addr_bytes * 8) continue; + switch (row->DestinationPrefix.Prefix.si_family) + { + case AF_INET: + row_prefix = (const BYTE *)&row->DestinationPrefix.Prefix.Ipv4.sin_addr; + break; + case AF_INET6: + row_prefix = (const BYTE *)&row->DestinationPrefix.Prefix.Ipv6.sin6_addr; + break; + default: + continue; + } + + prefix_bytes = row->DestinationPrefix.PrefixLength >> 3; + prefix_lastshr = -row->DestinationPrefix.PrefixLength & 7; + if (memcmp(dest_prefix, row_prefix, prefix_bytes) != 0 || + (prefix_lastshr && ((dest_prefix[prefix_bytes] ^ row_prefix[prefix_bytes]) >> + prefix_lastshr) != 0)) continue; + + forward_src = NULL; + for (ndx_ip = 0; ndx_ip < table_ip->NumEntries; ndx_ip++) + { + if (row->InterfaceLuid.Value == table_ip->Table[ndx_ip].InterfaceLuid.Value) + { + forward_src = &table_ip->Table[ndx_ip].Address; + break; + } + } + if (!forward_src) continue; + + if (!matched || row->DestinationPrefix.PrefixLength > matched->DestinationPrefix.PrefixLength || + (row->DestinationPrefix.PrefixLength == matched->DestinationPrefix.PrefixLength && + row->Metric < matched->Metric)) + { + matched = row; + memcpy( &matched_src, forward_src, sizeof(matched_src) ); + } + } + + if (matched) + { + ret = NO_ERROR; + } + else + { + /* No route matches, which can happen if there's no default route. */ + ret = ERROR_HOST_UNREACHABLE; + } + + heap_free( table_ip ); + +free_ipforwardtable2: + heap_free( table ); + +fail: + if (ret == NO_ERROR && matched) + { + memcpy( bestroute, matched, sizeof(*bestroute) ); + memcpy( bestaddress, &matched_src, sizeof(*bestaddress) ); + } + else + { + memset( bestroute, 0, sizeof(*bestroute) ); + memset( bestaddress, 0, sizeof(*bestaddress) ); + } + TRACE("returning %lu\n", ret); + return ret; }
/******************************************************************
Looking back at my nsiproxy notes, I did partially reverse this one (mainly because it turns out to be table 0, so it was the first of the IP tables that I looked at).
For IPv4, the key is 8 UINTs long, with the dest addr in the 2nd UINT. I guess the NET_LUID would also be sent along with any src address (although we could leave those as undetermined for now). You'd want to call `NsiGetAllParameters()` and retrieve the `dynamic` table. It has the size of 26 UINTs: the prefix is in the 2nd, the prefix length in the 3rd, next hop is in the 11th. Many other properties can easily be found by comparing with the result of `GetBestRoute2()`. I'd suggest testing with localhost as well as a remote addr.
I didn't put much effort into IPv6, save that the key size is 14 UINTs i.e. 6 more than IPv4 (which makes sense if there are two addresses), and the dynamic table is 38 UINTs.
So with all that, it probably makes sense to implement this in `nsiproxy.sys`.