All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] qga: implement 'guest-network-get-route' for NetBSD and OpenBSD
@ 2026-10-31  4:51 Brad Smith
  2026-10-31 15:27 ` Roman Bogorodskiy
  0 siblings, 1 reply; 2+ messages in thread
From: Brad Smith @ 2026-10-31  4:51 UTC (permalink / raw)
  To: Michael Roth, Kostiantyn Kostiuk, Roman Bogorodskiy; +Cc: qemu-devel

qga: implement 'guest-network-get-route' for NetBSD and OpenBSD

Extend the FreeBSD routing table implementation to NetBSD and
OpenBSD. All three dump the table with the NET_RT_DUMP sysctl and
walk rt_msghdr messages, but differ in a few details:

- Locating the sockaddrs: OpenBSD places them after rtm_hdrlen
  bytes and pads each to sizeof(long). NetBSD uses RT_ROUNDUP() and
  FreeBSD SA_SIZE(). Add RT_FIRST_SA() and RT_SA_SIZE() macros to
  cover these.

- The sysctl MIB: NetBSD rejects the trailing routing table ID with
  EINVAL, so use the 6 element form there. FreeBSD and OpenBSD keep
  the 7 element form and dump table 0.

- ARP/NDP entries: OpenBSD and NetBSD return neighbour entries in
  the routing table dump. Skip them via RTF_LLINFO (OpenBSD) or
  RTF_LLDATA (NetBSD, which renamed the flag).

- IPv6 scope: NetBSD and OpenBSD return link-local and scoped
  multicast addresses with the KAME embedded scope ID in bytes 2-3
  of the address. Move it into sin6_scope_id before calling
  getnameinfo(), so these print as fe80::...%iface as on FreeBSD.
  This is a no-op on FreeBSD, where the kernel already does this.

Also use ARRAY_SIZE() instead of nitems(), which NetBSD lacks.

Tested on FreeBSD, NetBSD and OpenBSD, comparing the output
against netstat -rn.

Signed-off-by: Brad Smith <brad@comstyle.com>
---
 qga/commands-bsd.c   | 98 +++++++++++++++++++++++++++++++++++++-------
 qga/qapi-schema.json | 22 +++++-----
 2 files changed, 95 insertions(+), 25 deletions(-)

diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
index 830ad08ac0..d607f8df10 100644
--- a/qga/commands-bsd.c
+++ b/qga/commands-bsd.c
@@ -22,11 +22,9 @@
 #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>
@@ -36,14 +34,27 @@
 #include <paths.h>
 #ifdef __FreeBSD__
 #include <ufs/ffs/fs.h>
+#include <devstat.h>
 #endif
+
 #ifdef __NetBSD__
 typedef struct statvfs bsd_fsinfo_t;
 #else
 typedef struct statfs bsd_fsinfo_t;
 #endif
-#ifdef CONFIG_FREEBSD
-#include <devstat.h>
+
+#if defined(__OpenBSD__)
+#define RT_FIRST_SA(rtm) \
+    ((struct sockaddr *)((char *)(rtm) + (rtm)->rtm_hdrlen))
+#define RT_SA_SIZE(sa) \
+    ((sa)->sa_len > 0 ? 1 + (((sa)->sa_len - 1) | (sizeof(long) - 1)) \
+                      : sizeof(long))
+#elif defined(__NetBSD__)
+#define RT_FIRST_SA(rtm)  ((struct sockaddr *)((rtm) + 1))
+#define RT_SA_SIZE(sa)    RT_ROUNDUP((sa)->sa_len)
+#else /* FreeBSD, DragonFly */
+#define RT_FIRST_SA(rtm)  ((struct sockaddr *)((rtm) + 1))
+#define RT_SA_SIZE(sa)    SA_SIZE(sa)
 #endif
 
 bool build_fs_mount_list(FsMountList *mounts, Error **errp)
@@ -358,22 +369,51 @@ error:
     free(dev_select);
     return NULL;
 }
+#endif
+
+static void sockaddr_in6_normalize(const struct sockaddr *sa,
+                                   struct sockaddr_in6 *sin6)
+{
+    size_t len = MIN(sa->sa_len, sizeof(*sin6));
+
+    memset(sin6, 0, sizeof(*sin6));
+    memcpy(sin6, sa, len);
+
+    sin6->sin6_len = sizeof(*sin6);
+
+    if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
+        (sin6->sin6_addr.s6_addr[0] == 0xff &&
+        ((sin6->sin6_addr.s6_addr[1] & 0x0f) == 1 ||
+        (sin6->sin6_addr.s6_addr[1] & 0x0f) == 2))) {
+        if (sin6->sin6_scope_id == 0) {
+            sin6->sin6_scope_id =
+                (sin6->sin6_addr.s6_addr[2] << 8) |
+                 sin6->sin6_addr.s6_addr[3];
+            sin6->sin6_addr.s6_addr[2] = 0;
+            sin6->sin6_addr.s6_addr[3] = 0;
+        }
+    }
+}
 
 GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
 {
     GuestNetworkRouteList *head = NULL, **tail = &head;
+#ifdef __NetBSD__
+    int mib[] = { CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0 };
+#else
     int mib[] = { CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0, 0 };
+#endif
     g_autofree char *buf = NULL;
     size_t len;
 
-    if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) == -1) {
+    if (sysctl(mib, ARRAY_SIZE(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) {
+    if (sysctl(mib, ARRAY_SIZE(mib), buf, &len, NULL, 0) == -1) {
         error_setg_errno(errp, errno, "sysctl routes");
         return NULL;
     }
@@ -382,14 +422,17 @@ GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
         struct rt_msghdr *rtm = (struct rt_msghdr *)p;
         struct sockaddr *addresses[RTAX_MAX] = { NULL };
         struct sockaddr *sa;
+        struct sockaddr_in6 sin6;
         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;
+        const struct sockaddr *fmt_sa;
         struct sockaddr_in normalized_mask = { 0 };
         char netmask_buf[INET_ADDRSTRLEN];
+        socklen_t salen;
 
         g_autoptr(GuestNetworkRoute) route = g_new0(GuestNetworkRoute, 1);
 
@@ -405,11 +448,23 @@ GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
             continue;
         }
 
-        sa = (struct sockaddr *)(rtm + 1);
+#if defined(RTF_LLINFO)
+        /* OpenBSD: skip ARP/NDP entries */
+        if (rtm->rtm_flags & RTF_LLINFO) {
+            continue;
+        }
+#elif defined(RTF_LLDATA)
+        /* NetBSD: skip ARP/NDP entries */
+        if (rtm->rtm_flags & RTF_LLDATA) {
+            continue;
+        }
+#endif
+
+        sa = RT_FIRST_SA(rtm);
         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));
+                sa = (struct sockaddr *)((char *)sa + RT_SA_SIZE(sa));
             }
         }
 
@@ -435,17 +490,33 @@ GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
         } else {
             continue;
         }
-        if (getnameinfo(dst, dst->sa_len,
+
+        fmt_sa = dst;
+        salen = dst->sa_len;
+        if (dst->sa_family == AF_INET6) {
+            sockaddr_in6_normalize(dst, &sin6);
+            fmt_sa = (struct sockaddr *)&sin6;
+            salen = sizeof(sin6);
+        }
+        if (getnameinfo(fmt_sa, salen,
                         destination, NI_MAXHOST,
-                        NULL, 0, NI_NUMERICHOST) != 0)
+                        NULL, 0, NI_NUMERICHOST) != 0) {
             continue;
+        }
         route->destination = g_strdup(destination);
 
         gw = addresses[RTAX_GATEWAY];
         if (gw && (gw->sa_family == AF_INET || gw->sa_family == AF_INET6)) {
-            if (getnameinfo(gw, gw->sa_len,
-                            gateway, NI_MAXHOST,
-                            NULL, 0, NI_NUMERICHOST) != 0) {
+            fmt_sa = gw;
+            salen = gw->sa_len;
+            if (gw->sa_family == AF_INET6) {
+                sockaddr_in6_normalize(gw, &sin6);
+                fmt_sa = (struct sockaddr *)&sin6;
+                salen = sizeof(sin6);
+            }
+            if (getnameinfo(fmt_sa, salen, gateway,
+                            NI_MAXHOST, NULL, 0,
+                            NI_NUMERICHOST) != 0) {
                 continue;
             }
             if (gw->sa_family == AF_INET) {
@@ -526,4 +597,3 @@ GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
 
     return head;
 }
-#endif /* __FreeBSD__ */
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 7b937713e6..1b8959d658 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1824,7 +1824,7 @@
 ##
 # @GuestNetworkRoute:
 #     Route information.  Supported on Linux (since 9.1), Windows
-#     (since 11.0), and FreeBSD (since 11.2).
+#     (since 11.0), and BSD (since 11.2).
 #
 # @iface: The destination network or host's egress network interface
 #     in the routing table
@@ -1838,31 +1838,31 @@
 #
 # @mask: Subnet Mask (IPv4 only)
 #
-# @irtt: Initial round-trip delay (not for Windows and FreeBSD,
+# @irtt: Initial round-trip delay (not for Windows and BSD,
 #     IPv4 only)
 #
 # @flags: Route flags (not for Windows)
 #
-# @refcnt: The route's reference count (not for Windows and FreeBSD)
+# @refcnt: The route's reference count (not for Windows and BSD)
 #
-# @use: Route usage count (not for Windows and FreeBSD)
+# @use: Route usage count (not for Windows and BSD)
 #
 # @window: TCP window size, used for flow control (not for Windows
-#     and FreeBSD, IPv4 only)
+#     and BSD, IPv4 only)
 #
 # @mtu: Data link layer maximum packet size (not for Windows)
 #
 # @desprefixlen: Destination prefix length (for IPv6)
 #
-# @source: Source IP address (not for FreeBSD, IPv6 only)
+# @source: Source IP address (not for BSD, IPv6 only)
 #
-# @srcprefixlen: Source prefix length (not for FreeBSD, IPv6 only)
+# @srcprefixlen: Source prefix length (not for BSD, IPv6 only)
 #
 # @nexthop: Next hop IP address (for IPv6)
 #
 # @version: IP version (4 or 6)
 #
-# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (FreeBSD)
+# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (BSD)
 ##
 { 'struct': 'GuestNetworkRoute',
   'data': {'iface': 'str',
@@ -1882,7 +1882,7 @@
            '*nexthop': 'str',
            'version': 'int'
            },
-  'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_FREEBSD'] } }
+  'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_BSD'] } }
 
 ##
 # @guest-network-get-route:
@@ -1890,9 +1890,9 @@
 #
 # Returns: List of route info of guest.
 #
-# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (FreeBSD)
+# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (BSD)
 ##
 { 'command': 'guest-network-get-route',
   'returns': ['GuestNetworkRoute'],
-  'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_FREEBSD'] }
+  'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_BSD'] }
 }
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] qga: implement 'guest-network-get-route' for NetBSD and OpenBSD
  2026-10-31  4:51 [PATCH] qga: implement 'guest-network-get-route' for NetBSD and OpenBSD Brad Smith
@ 2026-10-31 15:27 ` Roman Bogorodskiy
  0 siblings, 0 replies; 2+ messages in thread
From: Roman Bogorodskiy @ 2026-10-31 15:27 UTC (permalink / raw)
  To: Brad Smith; +Cc: Michael Roth, Kostiantyn Kostiuk, qemu-devel

  Brad Smith wrote:

> qga: implement 'guest-network-get-route' for NetBSD and OpenBSD
> 
> Extend the FreeBSD routing table implementation to NetBSD and
> OpenBSD. All three dump the table with the NET_RT_DUMP sysctl and
> walk rt_msghdr messages, but differ in a few details:
> 
> - Locating the sockaddrs: OpenBSD places them after rtm_hdrlen
>   bytes and pads each to sizeof(long). NetBSD uses RT_ROUNDUP() and
>   FreeBSD SA_SIZE(). Add RT_FIRST_SA() and RT_SA_SIZE() macros to
>   cover these.
> 
> - The sysctl MIB: NetBSD rejects the trailing routing table ID with
>   EINVAL, so use the 6 element form there. FreeBSD and OpenBSD keep
>   the 7 element form and dump table 0.
> 
> - ARP/NDP entries: OpenBSD and NetBSD return neighbour entries in
>   the routing table dump. Skip them via RTF_LLINFO (OpenBSD) or
>   RTF_LLDATA (NetBSD, which renamed the flag).
> 
> - IPv6 scope: NetBSD and OpenBSD return link-local and scoped
>   multicast addresses with the KAME embedded scope ID in bytes 2-3
>   of the address. Move it into sin6_scope_id before calling
>   getnameinfo(), so these print as fe80::...%iface as on FreeBSD.
>   This is a no-op on FreeBSD, where the kernel already does this.
> 
> Also use ARRAY_SIZE() instead of nitems(), which NetBSD lacks.
> 
> Tested on FreeBSD, NetBSD and OpenBSD, comparing the output
> against netstat -rn.
> 
> Signed-off-by: Brad Smith <brad@comstyle.com>

Reviewed-by: Roman Bogorodskiy <bogorodskiy@gmail.com>


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-31 15:28 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-31  4:51 [PATCH] qga: implement 'guest-network-get-route' for NetBSD and OpenBSD Brad Smith
2026-10-31 15:27 ` Roman Bogorodskiy

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.