git: b0b892aa6115 - stable/15 - rtnetlink: Add FreeBSD-specific IFLAF_GROUP support

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Sat, 31 Oct 2026 18:58:36 UTC
The branch stable/15 has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=b0b892aa61150fc99c9d3f08e722b7be5f14e5a3

commit b0b892aa61150fc99c9d3f08e722b7be5f14e5a3
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-08-27 12:43:28 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-10-31 18:57:03 +0000

    rtnetlink: Add FreeBSD-specific IFLAF_GROUP support
    
    Netlink IFLA_GROUP works with a single group id, in our
    implementation an interface can be joined to multiple groups
    and it works with group name.
    Store interface groups in IFLAF_GROUP attribute.
    
    Reviewed by:    glebius, melifaro
    Discussed with: markj
    Differential Revision: https://reviews.freebsd.org/D58643
    
    (cherry picked from commit 2f8f892ca344d884abbed0305886bb532f16368f)
---
 sys/net/if.c                            | 24 ++++++++++++++++++++++
 sys/net/if_var.h                        |  2 ++
 sys/netlink/netlink_snl_route_parsers.h |  8 ++++++++
 sys/netlink/route/iface.c               | 35 ++++++++++++++++++++++++++++++++-
 sys/netlink/route/interface.h           |  1 +
 5 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/sys/net/if.c b/sys/net/if.c
index ec8385bb93a5..dd4e0b719353 100644
--- a/sys/net/if.c
+++ b/sys/net/if.c
@@ -1577,6 +1577,30 @@ if_delgroups(struct ifnet *ifp)
 	IFNET_WUNLOCK();
 }
 
+/*
+ * XXX: This KPI should not expose ifg_group. therefore the current
+ * implementation is questionable and may change in the future.
+ */
+int
+if_foreach_group(struct ifnet *ifp, if_foreach_group_cb_t cb, void *cb_arg)
+{
+	struct ifg_list *ifgl;
+	int error;
+
+	MPASS(cb);
+
+	error = 0;
+	IFNET_RLOCK();
+	CK_STAILQ_FOREACH(ifgl, &ifp->if_groups, ifgl_next) {
+		error = cb(ifgl->ifgl_group, cb_arg);
+		if (error != 0)
+			break;
+	}
+	IFNET_RUNLOCK();
+
+	return (error);
+}
+
 /*
  * Stores all groups from an interface in memory pointed to by ifgr.
  */
diff --git a/sys/net/if_var.h b/sys/net/if_var.h
index 60d9378dbb76..b00dcf4a2ea9 100644
--- a/sys/net/if_var.h
+++ b/sys/net/if_var.h
@@ -509,6 +509,8 @@ VNET_DECLARE(if_t, loif);	/* first loopback interface */
 #define MCDPRINTF(...)
 #endif
 
+typedef int (*if_foreach_group_cb_t)(struct ifg_group *, void *);
+int	if_foreach_group(if_t, if_foreach_group_cb_t, void *);
 int	if_addgroup(if_t, const char *);
 int	if_delgroup(if_t, const char *);
 int	if_addmulti(if_t, struct sockaddr *, struct ifmultiaddr **);
diff --git a/sys/netlink/netlink_snl_route_parsers.h b/sys/netlink/netlink_snl_route_parsers.h
index b7d525b5f206..99f8a0b747a3 100644
--- a/sys/netlink/netlink_snl_route_parsers.h
+++ b/sys/netlink/netlink_snl_route_parsers.h
@@ -169,6 +169,11 @@ SNL_DECLARE_PARSER_EXT(snl_rtm_route_parser, sizeof(struct rtmsg),
 		sizeof(struct snl_parsed_route), _fp_p_route, _nla_p_route,
 		_cb_p_route);
 
+static const struct snl_attr_parser _nla_p_ifgroups[] = {
+	{ .type = IFLAF_GROUP, .cb = snl_attr_copy_string, .arg_u32 = sizeof(char[IFNAMSIZ]) },
+};
+SNL_DECLARE_ATTR_PARSER_EXT(_ifgroups_parser, sizeof(char[IFNAMSIZ]), _nla_p_ifgroups, NULL);
+
 /* RTM_<NEW|DEL|GET>LINK message parser */
 struct snl_parsed_link {
 	uint32_t			ifi_index;
@@ -186,6 +191,7 @@ struct snl_parsed_link {
 	struct rtnl_link_stats64	*ifla_stats64;
 	struct nlattr			*iflaf_orig_hwaddr;
 	struct snl_attr_bitset		iflaf_caps;
+	struct snl_parray		iflaf_groups;
 };
 
 #define	_IN(_field)	offsetof(struct ifinfomsg, _field)
@@ -193,6 +199,8 @@ struct snl_parsed_link {
 static const struct snl_attr_parser _nla_p_link_fbsd[] = {
 	{ .type = IFLAF_ORIG_HWADDR, .off = _OUT(iflaf_orig_hwaddr), .cb = snl_attr_dup_nla },
 	{ .type = IFLAF_CAPS, .off = _OUT(iflaf_caps), .cb = snl_attr_get_bitset_c },
+	{ .type = IFLAF_GROUP, .off = _OUT(iflaf_groups), .cb = snl_attr_get_parray,
+		.arg = &_ifgroups_parser },
 };
 SNL_DECLARE_ATTR_PARSER(_link_fbsd_parser, _nla_p_link_fbsd);
 
diff --git a/sys/netlink/route/iface.c b/sys/netlink/route/iface.c
index 91d2d266822b..e065b3728121 100644
--- a/sys/netlink/route/iface.c
+++ b/sys/netlink/route/iface.c
@@ -202,6 +202,38 @@ get_hwaddr(struct nl_writer *nw, if_t ifp)
 	}
 }
 
+static int
+dump_group_cb(struct ifg_group *ifg, void *_arg)
+{
+	struct nl_writer *nw = (struct nl_writer *)_arg;
+	int off, ret = 0;
+
+	off = nlattr_add_nested(nw, IFLAF_GROUP);
+	if (off == 0)
+		return (ENOMEM);
+
+	if (!nlattr_add_string(nw, IFLAF_GROUP, ifg->ifg_group))
+		ret = ENOMEM;
+
+	nlattr_set_len(nw, off);
+	return (ret);
+}
+
+static int
+dump_group(struct nl_writer *nw, if_t ifp)
+{
+	int off, ret;
+
+	off = nlattr_add_nested(nw, IFLAF_GROUP);
+	if (off == 0)
+		return (ENOMEM);
+
+	ret = if_foreach_group(ifp, dump_group_cb, nw);
+	nlattr_set_len(nw, off);
+
+	return (ret);
+}
+
 static unsigned
 ifp_flags_to_netlink(const if_t ifp)
 {
@@ -341,7 +373,6 @@ dump_iface(struct nl_writer *nw, if_t ifp, const struct nlmsghdr *hdr,
 /*
         nlattr_add_u32(nw, IFLA_MIN_MTU, 60);
         nlattr_add_u32(nw, IFLA_MAX_MTU, 9000);
-        nlattr_add_u32(nw, IFLA_GROUP, 0);
 */
 
 	if (if_getdescr(ifp) != NULL)
@@ -352,6 +383,8 @@ dump_iface(struct nl_writer *nw, if_t ifp, const struct nlmsghdr *hdr,
 	if (off != 0) {
 		get_hwaddr(nw, ifp);
 		dump_iface_caps(nw, ifp);
+		if (dump_group(nw, ifp) != 0)
+			goto enomem;
 
 		nlattr_set_len(nw, off);
 	}
diff --git a/sys/netlink/route/interface.h b/sys/netlink/route/interface.h
index 6034ff1657eb..6f9c3cba5015 100644
--- a/sys/netlink/route/interface.h
+++ b/sys/netlink/route/interface.h
@@ -152,6 +152,7 @@ enum {
 	IFLAF_ORIG_IFNAME	= 1,	/* string, original interface name at creation */
 	IFLAF_ORIG_HWADDR	= 2,	/* binary, original hardware address */
 	IFLAF_CAPS		= 3,	/* bitset, interface capabilities */
+	IFLAF_GROUP		= 4,	/* string, interface group name (multi-attr) */
 	__IFLAF_MAX
 };
 #define IFLAF_MAX (__IFLAF_MAX - 1)