mirror of
https://github.com/qemu/qemu.git
synced 2026-04-05 22:00:58 +00:00
qga: implement guest-network-get-route for Windows
Add Windows implementation of guest-network-get-route command to provide network routing information on Windows guests. Features implemented: - IPv4 and IPv6 route information retrieval using GetIpForwardTable2 - Human-readable interface names via GetAdaptersAddresses - Proper route metrics combining route and interface metrics - MTU information for network interfaces - Support for destination, gateway, mask, and metric fields for IPv4 - Support for destination, nexthop, and desprefixlen fields for IPv6 Implementation uses modern Windows IP Helper API (GetIpForwardTable2, GetIfEntry2). Signed-off-by: Elizabeth Ashurov <eashurov@redhat.com> Reviewed-by: Kostiantyn Kostiuk <kkostiuk@redhat.com> Link: https://lore.kernel.org/qemu-devel/20251222144031.3115317-1-eashurov@redhat.com Signed-off-by: Kostiantyn Kostiuk <kkostiuk@redhat.com>
This commit is contained in:
committed by
Kostiantyn Kostiuk
parent
ece408818d
commit
595a77289d
@@ -2592,3 +2592,175 @@ GuestLoadAverage *qmp_guest_get_load(Error **errp)
|
||||
ret->load15m = load_avg_15m;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Helper function to get interface name with fallbacks */
|
||||
static char *get_interface_name(const MIB_IPFORWARD_ROW2 *row,
|
||||
IP_ADAPTER_ADDRESSES *adptr_addrs)
|
||||
{
|
||||
IP_ADAPTER_ADDRESSES *adapter;
|
||||
char *iface_name = NULL;
|
||||
|
||||
if (adptr_addrs) {
|
||||
for (adapter = adptr_addrs; adapter; adapter = adapter->Next) {
|
||||
if (adapter->Luid.Value == row->InterfaceLuid.Value) {
|
||||
iface_name = guest_wctomb_dup(adapter->FriendlyName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!iface_name) {
|
||||
iface_name = g_strdup_printf("if%lu", row->InterfaceIndex);
|
||||
}
|
||||
|
||||
return iface_name;
|
||||
}
|
||||
|
||||
/* Helper function to fill IPv4 route information */
|
||||
static void fill_ipv4_route_info(GuestNetworkRoute *route,
|
||||
const MIB_IPFORWARD_ROW2 *row)
|
||||
{
|
||||
struct sockaddr_in *addr_in;
|
||||
char addr_str[INET_ADDRSTRLEN];
|
||||
|
||||
addr_in = (struct sockaddr_in *)&row->DestinationPrefix.Prefix;
|
||||
if (inet_ntop(AF_INET, &addr_in->sin_addr, addr_str, INET_ADDRSTRLEN)) {
|
||||
route->destination = g_strdup(addr_str);
|
||||
} else {
|
||||
route->destination = g_strdup("0.0.0.0");
|
||||
}
|
||||
|
||||
if (row->DestinationPrefix.PrefixLength == 0) {
|
||||
route->mask = g_strdup("0.0.0.0");
|
||||
} else {
|
||||
uint32_t mask = htonl(0xFFFFFFFF << (32 -
|
||||
row->DestinationPrefix.PrefixLength));
|
||||
struct in_addr mask_addr = { .s_addr = mask };
|
||||
route->mask = g_strdup(inet_ntoa(mask_addr));
|
||||
}
|
||||
|
||||
addr_in = (struct sockaddr_in *)&row->NextHop;
|
||||
if (inet_ntop(AF_INET, &addr_in->sin_addr, addr_str, INET_ADDRSTRLEN)) {
|
||||
route->gateway = g_strdup(addr_str);
|
||||
} else {
|
||||
route->gateway = g_strdup("0.0.0.0");
|
||||
}
|
||||
|
||||
route->version = 4;
|
||||
}
|
||||
|
||||
/* Helper function to fill IPv6 route information */
|
||||
static void fill_ipv6_route_info(GuestNetworkRoute *route,
|
||||
const MIB_IPFORWARD_ROW2 *row)
|
||||
{
|
||||
struct sockaddr_in6 *addr_in6;
|
||||
char addr_str[INET6_ADDRSTRLEN];
|
||||
|
||||
addr_in6 = (struct sockaddr_in6 *)&row->DestinationPrefix.Prefix;
|
||||
if (inet_ntop(AF_INET6, &addr_in6->sin6_addr, addr_str, INET6_ADDRSTRLEN)) {
|
||||
route->destination = g_strdup(addr_str);
|
||||
} else {
|
||||
route->destination = g_strdup("::");
|
||||
}
|
||||
|
||||
addr_in6 = (struct sockaddr_in6 *)&row->NextHop;
|
||||
if (inet_ntop(AF_INET6, &addr_in6->sin6_addr, addr_str, INET6_ADDRSTRLEN)) {
|
||||
route->nexthop = g_strdup(addr_str);
|
||||
} else {
|
||||
route->nexthop = g_strdup("::");
|
||||
}
|
||||
|
||||
route->desprefixlen = g_strdup_printf("%u",
|
||||
row->DestinationPrefix.PrefixLength);
|
||||
route->version = 6;
|
||||
}
|
||||
|
||||
GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
|
||||
{
|
||||
GuestNetworkRouteList *head = NULL, **tail = &head;
|
||||
PMIB_IPFORWARD_TABLE2 pIpForwardTable2;
|
||||
DWORD dwRetVal = 0;
|
||||
DWORD i;
|
||||
PMIB_IPFORWARD_ROW2 row;
|
||||
GuestNetworkRoute *route;
|
||||
IP_ADAPTER_ADDRESSES *adptr_addrs;
|
||||
MIB_IPINTERFACE_ROW ipifrow;
|
||||
MIB_IF_ROW2 ifrow2;
|
||||
Error *local_err = NULL;
|
||||
GHashTable *interface_metric_cache;
|
||||
|
||||
dwRetVal = GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable2);
|
||||
if (dwRetVal != NO_ERROR) {
|
||||
error_setg_win32(errp, dwRetVal, "failed to get IP routes");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
interface_metric_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||
g_free, NULL);
|
||||
adptr_addrs = guest_get_adapters_addresses(&local_err);
|
||||
if (local_err) {
|
||||
error_free(local_err);
|
||||
adptr_addrs = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < pIpForwardTable2->NumEntries; i++) {
|
||||
row = &pIpForwardTable2->Table[i];
|
||||
|
||||
if (row->DestinationPrefix.Prefix.si_family == AF_INET) {
|
||||
route = g_new0(GuestNetworkRoute, 1);
|
||||
fill_ipv4_route_info(route, row);
|
||||
} else if (row->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
||||
route = g_new0(GuestNetworkRoute, 1);
|
||||
fill_ipv6_route_info(route, row);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
route->iface = get_interface_name(row, adptr_addrs);
|
||||
|
||||
/*
|
||||
* Get interface metric for combined route metric calculation.
|
||||
* Windows calculates effective route metric as route + interface metric.
|
||||
* This matches the values displayed by Windows 'route print' command.
|
||||
* See:
|
||||
* https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2
|
||||
*/
|
||||
gchar *luid_key = g_strdup_printf("%" G_GUINT64_FORMAT,
|
||||
row->InterfaceLuid.Value);
|
||||
gpointer cached_metric = g_hash_table_lookup(interface_metric_cache,
|
||||
luid_key);
|
||||
|
||||
if (cached_metric) {
|
||||
route->metric = (int)row->Metric + GPOINTER_TO_INT(cached_metric);
|
||||
g_free(luid_key);
|
||||
} else {
|
||||
memset(&ipifrow, 0, sizeof(ipifrow));
|
||||
InitializeIpInterfaceEntry(&ipifrow);
|
||||
ipifrow.InterfaceLuid = row->InterfaceLuid;
|
||||
ipifrow.Family = row->DestinationPrefix.Prefix.si_family;
|
||||
if (GetIpInterfaceEntry(&ipifrow) == NO_ERROR) {
|
||||
g_hash_table_insert(interface_metric_cache, luid_key,
|
||||
GINT_TO_POINTER(ipifrow.Metric));
|
||||
route->metric = (int)row->Metric + (int)ipifrow.Metric;
|
||||
} else {
|
||||
route->metric = (int)row->Metric;
|
||||
g_free(luid_key);
|
||||
}
|
||||
}
|
||||
|
||||
memset(&ifrow2, 0, sizeof(ifrow2));
|
||||
ifrow2.InterfaceLuid = row->InterfaceLuid;
|
||||
if (GetIfEntry2(&ifrow2) == NO_ERROR) {
|
||||
route->has_mtu = true;
|
||||
route->mtu = (int)ifrow2.Mtu;
|
||||
}
|
||||
|
||||
QAPI_LIST_APPEND(tail, route);
|
||||
route = NULL;
|
||||
}
|
||||
|
||||
FreeMibTable(pIpForwardTable2);
|
||||
g_free(adptr_addrs);
|
||||
g_hash_table_destroy(interface_metric_cache);
|
||||
return head;
|
||||
}
|
||||
|
||||
@@ -1879,7 +1879,8 @@
|
||||
##
|
||||
# @GuestNetworkRoute:
|
||||
#
|
||||
# Route information, currently, only linux supported.
|
||||
# Route information. Supported on Linux (since 9.1) and Windows
|
||||
# (since 11.0).
|
||||
#
|
||||
# @iface: The destination network or host's egress network interface
|
||||
# in the routing table
|
||||
@@ -1916,7 +1917,7 @@
|
||||
#
|
||||
# @version: IP version (4 or 6)
|
||||
#
|
||||
# Since: 9.1
|
||||
# Since: 9.1 (Linux only); 11.0 (Windows)
|
||||
##
|
||||
{ 'struct': 'GuestNetworkRoute',
|
||||
'data': {'iface': 'str',
|
||||
@@ -1936,7 +1937,7 @@
|
||||
'*nexthop': 'str',
|
||||
'version': 'int'
|
||||
},
|
||||
'if': 'CONFIG_LINUX' }
|
||||
'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] } }
|
||||
|
||||
##
|
||||
# @guest-network-get-route:
|
||||
@@ -1945,9 +1946,9 @@
|
||||
#
|
||||
# Returns: List of route info of guest.
|
||||
#
|
||||
# Since: 9.1
|
||||
# Since: 9.1 (Linux only); 11.0 (Windows)
|
||||
##
|
||||
{ 'command': 'guest-network-get-route',
|
||||
'returns': ['GuestNetworkRoute'],
|
||||
'if': 'CONFIG_LINUX'
|
||||
'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user