On Sat, Oct 17, 2026 at 03:35:32PM +0200, Roman Bogorodskiy wrote:
> 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.
>
> While here, spell Windows correctly in @GuestNetworkRoute documentation
> in qapi-schema.json.
>
> Signed-off-by: Roman Bogorodskiy <[email protected]>
> Acked-by: Markus Armbruster <[email protected]>
> ---
> Changes since v1:
>
> - qga/qapi-schema.json spelling: windows -> Windows, freebsd -> FreeBSD
> in the "@GuestNetworkRoute" documentation.
> - meson.build: check that the 'struct rt_metrics' has 'rmx_metric'
> member which is available only on FreeBSD 16.
>
> meson.build | 6 ++
> qga/commands-bsd.c | 180 +++++++++++++++++++++++++++++++++++++++++++
> qga/qapi-schema.json | 31 ++++----
> 3 files changed, 202 insertions(+), 15 deletions(-)
>
> diff --git a/meson.build b/meson.build
> index 255c887e8e..cb69d5a3c7 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -2759,6 +2759,12 @@ config_host_data.set('HAVE_STRUCT_STAT_ST_ATIM',
> config_host_data.set('HAVE_BLK_ZONE_REP_CAPACITY',
> cc.has_member('struct blk_zone', 'capacity',
> prefix: '#include <linux/blkzoned.h>'))
> +config_host_data.set('HAVE_STRUCT_RT_METRICS_RMX_METRIC',
> + cc.has_member('struct rt_metrics', 'rmx_metric',
> + prefix: '''
> + #include <sys/types.h>
> + #include <sys/socket.h>
> + #include <net/route.h>'''))
>
> # has_type
> config_host_data.set('CONFIG_IOVEC',
> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
> index 1c51d099eb..63254e0812 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,179 @@ 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] = "?";
IIUC, by s3etting this to "?" you're expecting it to be
left alone if ....
> + 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);
....this fails to resolve the interface name.
> +
> + route->iface = g_strdup(ifname);
This is a non-optional field so we must return something on
failure, and I see that the Windows impl returns a string
based on the if index. eg matching logic would be more
like:
if (if_indextoname(rtm->rtm_index, ifname) > 0) {
route->iface = g_strdup(ifname)
} else {
route->iface = g_strdup_printf("if%u", rtm->rtm_index);
}
> + 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;
Do you need to have different code paths for AF_INET
vs AF_INET6 ? IIUC, getnameinfo() should handle
both AF_INET and AF_INET6 the same way, so we ought
to not need inet_ntop() path at all.
> + } 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);
> + }
Same Q about using getnameinfo() exclusively
> + }
> +
> + 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__ */
With regards,
Daniel
--
|: https://berrange.com ~~ https://hachyderm.io/@berrange :|
|: https://libvirt.org ~~ https://entangle-photo.org :|
|: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :|