* [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.