* [PATCH] qga: implement 'guest-network-get-route' for FreeBSD
@ 2026-10-16 16:33 Roman Bogorodskiy
2026-10-16 17:07 ` Markus Armbruster
0 siblings, 1 reply; 2+ messages in thread
From: Roman Bogorodskiy @ 2026-10-16 16:33 UTC (permalink / raw)
To: qemu-devel
Cc: Roman Bogorodskiy, Michael Roth, Kostiantyn Kostiuk, Eric Blake,
Markus Armbruster
Implement 'guest-network-get-route' for FreeBSD. This
implementation uses the sysctl(3) interface to get
the routing table.
Limitations:
- This implementation does not populate irtt, refcnt, use,
window, source, and srcprefixlen fields.
- The implementation uses the default FIB (routing table) 0.
Selecting QGA's active FIB is left for the follow-up patch.
Signed-off-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
---
qga/commands-bsd.c | 178 +++++++++++++++++++++++++++++++++++++++++++
qga/qapi-schema.json | 27 +++----
2 files changed, 192 insertions(+), 13 deletions(-)
diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
index 1c51d099eb..87c749a4c5 100644
--- a/qga/commands-bsd.c
+++ b/qga/commands-bsd.c
@@ -21,6 +21,11 @@
#include <sys/mount.h>
#include <net/if_dl.h>
#include <sys/statvfs.h>
+#if defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#include <net/route.h>
+#include <net/if.h>
+#endif
#if defined(__NetBSD__) || defined(__OpenBSD__)
#include <net/if_arp.h>
#include <netinet/if_ether.h>
@@ -228,4 +233,177 @@ GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
free_fs_mount_list(&mounts);
return ret;
}
+
+GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
+{
+ GuestNetworkRouteList *head = NULL, **tail = &head;
+ int mib[] = { CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0, 0 };
+ g_autofree char *buf = NULL;
+ size_t len;
+
+ if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) == -1) {
+ error_setg_errno(errp, errno, "sysctl size");
+ return NULL;
+ }
+
+ buf = g_malloc0(len);
+
+ if (sysctl(mib, nitems(mib), buf, &len, NULL, 0) == -1) {
+ error_setg_errno(errp, errno, "sysctl routes");
+ return NULL;
+ }
+
+ for (char *p = buf, *end = buf + len; p < end; ) {
+ struct rt_msghdr *rtm = (struct rt_msghdr *)p;
+ struct sockaddr *addresses[RTAX_MAX] = { NULL };
+ struct sockaddr *sa;
+ char destination[NI_MAXHOST];
+ char gateway[NI_MAXHOST];
+ char ifname[IFNAMSIZ] = "?";
+ const struct sockaddr *dst = NULL;
+ const struct sockaddr *gw = NULL;
+ const struct sockaddr *netmask = NULL;
+ struct sockaddr_in normalized_mask = { 0 };
+ char netmask_buf[INET_ADDRSTRLEN];
+
+ g_autoptr(GuestNetworkRoute) route = g_new0(GuestNetworkRoute, 1);
+
+ if ((size_t)(end - p) < sizeof(*rtm) ||
+ rtm->rtm_msglen < sizeof(*rtm) ||
+ rtm->rtm_msglen > (size_t)(end - p)) {
+ break;
+ }
+
+ p += rtm->rtm_msglen;
+
+ if (rtm->rtm_version != RTM_VERSION) {
+ continue;
+ }
+
+ sa = (struct sockaddr *)(rtm + 1);
+ for (int i = 0; i < RTAX_MAX; i++) {
+ if (rtm->rtm_addrs & (1U << i)) {
+ addresses[i] = sa;
+ sa = (struct sockaddr *)((char *)sa + SA_SIZE(sa));
+ }
+ }
+
+ if_indextoname(rtm->rtm_index, ifname);
+
+ route->iface = g_strdup(ifname);
+ route->metric = rtm->rtm_rmx.rmx_metric;
+
+ dst = addresses[RTAX_DST];
+ if (!dst) {
+ continue;
+ }
+
+ if (dst->sa_family == AF_INET) {
+ if (inet_ntop(AF_INET,
+ &((const struct sockaddr_in *)dst)->sin_addr,
+ destination, NI_MAXHOST) == NULL) {
+ continue;
+ }
+ route->version = 4;
+ } else if (dst->sa_family == AF_INET6) {
+ if (getnameinfo(dst, dst->sa_len,
+ destination, NI_MAXHOST,
+ NULL, 0, NI_NUMERICHOST) != 0)
+ continue;
+ route->version = 6;
+ } else {
+ continue;
+ }
+ route->destination = g_strdup(destination);
+
+ gw = addresses[RTAX_GATEWAY];
+ if (gw) {
+ if (gw->sa_family == AF_INET) {
+ if (inet_ntop(AF_INET,
+ &((const struct sockaddr_in *)gw)->sin_addr,
+ gateway, NI_MAXHOST) == NULL) {
+ continue;
+ }
+ route->gateway = g_strdup(gateway);
+ } else if (gw->sa_family == AF_INET6) {
+ if (getnameinfo(gw, gw->sa_len,
+ gateway, NI_MAXHOST,
+ NULL, 0, NI_NUMERICHOST) != 0) {
+ continue;
+ }
+ route->nexthop = g_strdup(gateway);
+ }
+ }
+
+ netmask = addresses[RTAX_NETMASK];
+ if (dst->sa_family == AF_INET) {
+ if (netmask && netmask->sa_len != 0) {
+ memcpy(&normalized_mask, netmask,
+ MIN(netmask->sa_len, sizeof(normalized_mask)));
+ } else if (rtm->rtm_flags & RTF_HOST) {
+ normalized_mask.sin_addr.s_addr = 0xFFFFFFFF;
+ }
+
+ if (inet_ntop(AF_INET, &normalized_mask.sin_addr,
+ netmask_buf, sizeof(netmask_buf)) != NULL) {
+ route->mask = g_strdup(netmask_buf);
+ } else {
+ route->mask = NULL;
+ }
+ } else if (dst->sa_family == AF_INET6) {
+ struct sockaddr_in6 normalized_mask6;
+ memset(&normalized_mask6, 0, sizeof(normalized_mask6));
+
+ if (netmask && netmask->sa_len != 0) {
+ int prefix_len = 0;
+ bool valid_mask = true;
+ bool zero_found = false;
+
+ memcpy(&normalized_mask6, netmask,
+ MIN(netmask->sa_len, sizeof(normalized_mask6)));
+
+ for (int i = 0; i < sizeof(normalized_mask6.sin6_addr); i++) {
+ uint8_t byte = normalized_mask6.sin6_addr.s6_addr[i];
+ for (int bit = 7; bit >= 0; bit--) {
+ if ((byte >> bit) & 1) {
+ if (zero_found) {
+ valid_mask = false;
+ break;
+ }
+ prefix_len++;
+ } else {
+ zero_found = true;
+ }
+ }
+ if (!valid_mask) {
+ break;
+ }
+ }
+
+ if (valid_mask) {
+ route->desprefixlen = g_strdup_printf("%d", prefix_len);
+ } else {
+ route->desprefixlen = NULL;
+ }
+ } else if (rtm->rtm_flags & RTF_HOST) {
+ route->desprefixlen = g_strdup("128");
+ } else {
+ route->desprefixlen = g_strdup("0");
+ }
+ }
+
+ if (rtm->rtm_rmx.rmx_mtu) {
+ route->mtu = rtm->rtm_rmx.rmx_mtu;
+ route->has_mtu = true;
+ }
+
+ route->flags = (uint32_t)rtm->rtm_flags;
+ route->has_flags = true;
+
+ QAPI_LIST_APPEND(tail, route);
+ route = NULL;
+ }
+
+ return head;
+}
#endif /* __FreeBSD__ */
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 05fe4ea484..2f9d9f13a0 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1823,8 +1823,8 @@
##
# @GuestNetworkRoute:
-# Route information. Supported on Linux (since 9.1) and Windows
-# (since 11.0).
+# Route information. Supported on Linux (since 9.1), Windows
+# (since 11.0), and FreeBSD (since 11.2).
#
# @iface: The destination network or host's egress network interface
# in the routing table
@@ -1838,30 +1838,31 @@
#
# @mask: Subnet Mask (IPv4 only)
#
-# @irtt: Initial round-trip delay (not for windows, IPv4 only)
+# @irtt: Initial round-trip delay (not for windows and freebsd,
+# IPv4 only)
#
# @flags: Route flags (not for windows)
#
-# @refcnt: The route's reference count (not for windows)
+# @refcnt: The route's reference count (not for windows and freebsd)
#
-# @use: Route usage count (not for windows)
+# @use: Route usage count (not for windows and freebsd)
#
-# @window: TCP window size, used for flow control (not for windows,
-# IPv4 only)
+# @window: TCP window size, used for flow control (not for windows
+# and freebsd, IPv4 only)
#
# @mtu: Data link layer maximum packet size (not for windows)
#
# @desprefixlen: Destination prefix length (for IPv6)
#
-# @source: Source IP address (for IPv6)
+# @source: Source IP address (not for freebsd, IPv6 only)
#
-# @srcprefixlen: Source prefix length (for IPv6)
+# @srcprefixlen: Source prefix length (not for freebsd, IPv6 only)
#
# @nexthop: Next hop IP address (for IPv6)
#
# @version: IP version (4 or 6)
#
-# Since: 9.1 (Linux only); 11.0 (Windows)
+# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (FreeBSD)
##
{ 'struct': 'GuestNetworkRoute',
'data': {'iface': 'str',
@@ -1881,7 +1882,7 @@
'*nexthop': 'str',
'version': 'int'
},
- 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] } }
+ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_FREEBSD'] } }
##
# @guest-network-get-route:
@@ -1889,9 +1890,9 @@
#
# Returns: List of route info of guest.
#
-# Since: 9.1 (Linux only); 11.0 (Windows)
+# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (FreeBSD)
##
{ 'command': 'guest-network-get-route',
'returns': ['GuestNetworkRoute'],
- 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] }
+ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_FREEBSD'] }
}
--
2.55.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] qga: implement 'guest-network-get-route' for FreeBSD
2026-10-16 16:33 [PATCH] qga: implement 'guest-network-get-route' for FreeBSD Roman Bogorodskiy
@ 2026-10-16 17:07 ` Markus Armbruster
0 siblings, 0 replies; 2+ messages in thread
From: Markus Armbruster @ 2026-10-16 17:07 UTC (permalink / raw)
To: Roman Bogorodskiy
Cc: qemu-devel, Michael Roth, Kostiantyn Kostiuk, Eric Blake
Roman Bogorodskiy <bogorodskiy@gmail.com> writes:
> Implement 'guest-network-get-route' for FreeBSD. This
> implementation uses the sysctl(3) interface to get
> the routing table.
>
> Limitations:
>
> - This implementation does not populate irtt, refcnt, use,
> window, source, and srcprefixlen fields.
> - The implementation uses the default FIB (routing table) 0.
> Selecting QGA's active FIB is left for the follow-up patch.
>
> Signed-off-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
[...]
> diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
> index 05fe4ea484..2f9d9f13a0 100644
> --- a/qga/qapi-schema.json
> +++ b/qga/qapi-schema.json
> @@ -1823,8 +1823,8 @@
>
> ##
> # @GuestNetworkRoute:
> -# Route information. Supported on Linux (since 9.1) and Windows
> -# (since 11.0).
> +# Route information. Supported on Linux (since 9.1), Windows
> +# (since 11.0), and FreeBSD (since 11.2).
> #
> # @iface: The destination network or host's egress network interface
> # in the routing table
> @@ -1838,30 +1838,31 @@
> #
> # @mask: Subnet Mask (IPv4 only)
> #
> -# @irtt: Initial round-trip delay (not for windows, IPv4 only)
> +# @irtt: Initial round-trip delay (not for windows and freebsd,
> +# IPv4 only)
FreeBSD, please, and use the opportunity to spell Windows correctly.
More of the same below.
> #
> # @flags: Route flags (not for windows)
> #
> -# @refcnt: The route's reference count (not for windows)
> +# @refcnt: The route's reference count (not for windows and freebsd)
> #
> -# @use: Route usage count (not for windows)
> +# @use: Route usage count (not for windows and freebsd)
> #
> -# @window: TCP window size, used for flow control (not for windows,
> -# IPv4 only)
> +# @window: TCP window size, used for flow control (not for windows
> +# and freebsd, IPv4 only)
> #
> # @mtu: Data link layer maximum packet size (not for windows)
> #
> # @desprefixlen: Destination prefix length (for IPv6)
> #
> -# @source: Source IP address (for IPv6)
> +# @source: Source IP address (not for freebsd, IPv6 only)
> #
> -# @srcprefixlen: Source prefix length (for IPv6)
> +# @srcprefixlen: Source prefix length (not for freebsd, IPv6 only)
> #
> # @nexthop: Next hop IP address (for IPv6)
> #
> # @version: IP version (4 or 6)
> #
> -# Since: 9.1 (Linux only); 11.0 (Windows)
> +# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (FreeBSD)
> ##
> { 'struct': 'GuestNetworkRoute',
> 'data': {'iface': 'str',
> @@ -1881,7 +1882,7 @@
> '*nexthop': 'str',
> 'version': 'int'
> },
> - 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] } }
> + 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_FREEBSD'] } }
>
> ##
> # @guest-network-get-route:
> @@ -1889,9 +1890,9 @@
> #
> # Returns: List of route info of guest.
> #
> -# Since: 9.1 (Linux only); 11.0 (Windows)
> +# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (FreeBSD)
> ##
> { 'command': 'guest-network-get-route',
> 'returns': ['GuestNetworkRoute'],
> - 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] }
> + 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_FREEBSD'] }
> }
Acked-by: Markus Armbruster <armbru@redhat.com>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-16 17:08 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 16:33 [PATCH] qga: implement 'guest-network-get-route' for FreeBSD Roman Bogorodskiy
2026-10-16 17:07 ` Markus Armbruster
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.