All of lore.kernel.org
 help / color / mirror / Atom feed
* [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.