git: 60c0a075472c - main - netlink: Parse repeated nested attributes

Kevin Bowling <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a864055.419c1.41f86686__5473.13043368689$1787183230$gmane$org@gitrepo.freebsd.org>
The branch main has been updated by kbowling:

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

commit 60c0a075472cabd0ac7f54411f6a65626169984a
Author:     Kevin Bowling <[email protected]>
AuthorDate: 2026-08-10 21:34:12 +0000
Commit:     Kevin Bowling <[email protected]>
CommitDate: 2026-08-19 23:44:55 +0000

    netlink: Parse repeated nested attributes
    
    Modern Netlink arrays encode their elements as repeated attributes of
    the same type rather than as children of an additional array container.
    
    Add an SNL callback that parses one nested element for each occurrence
    and appends it to a geometrically grown parser array.  Retain the
    existing parray callback for protocols that use the legacy container
    form.
    
    Store the growth capacity in struct snl_parray, appended after its
    existing public count and items fields so their offsets remain stable
    on LP64 and ILP32.  Require parser targets to be real snl_parray
    objects, and convert bitset, generic Netlink, and route multipath arrays
    accordingly.  This avoids relying on layout aliases for private growth
    state.
    
    Add regression coverage for a nested bit array that grows beyond its
    initial allocation, while preserving replacement semantics when a
    legacy array target is reused.
    
    Reviewed by:    melifaro, pouria
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D58775
---
 sbin/route/route_netlink.c              |  22 +++----
 sys/netlink/netlink_snl.h               | 106 +++++++++++++++++++++++---------
 sys/netlink/netlink_snl_generic.h       |  18 +++---
 sys/netlink/netlink_snl_route_parsers.h |  11 ++--
 tests/sys/netlink/test_rtnl_route.c     |  10 +--
 tests/sys/netlink/test_snl.c            |  65 ++++++++++++++++++++
 tests/sys/netlink/test_snl_generic.c    |   6 +-
 usr.bin/genl/genl.c                     |  59 +++++++++---------
 usr.bin/netstat/route_netlink.c         |   8 +--
 usr.sbin/arp/arp_netlink.c              |   3 +-
 usr.sbin/ndp/ndp_netlink.c              |   3 +-
 11 files changed, 211 insertions(+), 100 deletions(-)

diff --git a/sbin/route/route_netlink.c b/sbin/route/route_netlink.c
index f145771ebb12..ce67cc71c3e8 100644
--- a/sbin/route/route_netlink.c
+++ b/sbin/route/route_netlink.c
@@ -428,11 +428,11 @@ print_nhop_getmsg(struct nl_helper *h, struct nlmsghdr *hdr, struct sockaddr *ds
 	printf("        fib: %u\n", (unsigned int)r.rta_table);
 	printf("      flags: ");
 	printb(r.rta_rtflags, routeflags);
-	printf("\n      nhops: %u\n", r.rta_multipath.num_nhops);
-	if (r.rta_multipath.num_nhops != 0) {
+	printf("\n      nhops: %u\n", r.rta_multipath.count);
+	if (r.rta_multipath.count != 0) {
 		bool first = true;
-		for (uint32_t i = 0; i < r.rta_multipath.num_nhops; i++) {
-			struct rta_mpath_nh *nh = r.rta_multipath.nhops[i];
+		for (uint32_t i = 0; i < r.rta_multipath.count; i++) {
+			struct rta_mpath_nh *nh = r.rta_multipath.items[i];
 
 			printf("\tvia ");
 			print_nlmsg_route_nhop(h, &r, nh, first);
@@ -601,14 +601,14 @@ print_nlmsg_route(struct nl_helper *h, struct nlmsghdr *hdr,
 		return;
 	}
 
-	if (r.rta_multipath.num_nhops != 0) {
+	if (r.rta_multipath.count != 0) {
 		bool first = true;
 
 		memset(buf, ' ', sizeof(buf));
 		buf[len] = '\0';
 
-		for (uint32_t i = 0; i < r.rta_multipath.num_nhops; i++) {
-			struct rta_mpath_nh *nh = r.rta_multipath.nhops[i];
+		for (uint32_t i = 0; i < r.rta_multipath.count; i++) {
+			struct rta_mpath_nh *nh = r.rta_multipath.items[i];
 
 			if (!first)
 				printf("%s", buf);
@@ -903,9 +903,10 @@ flushroute_one(struct nl_helper *h, struct snl_parsed_route *r)
 		print_nlmsg(h, hdr, &attrs);
 	}
 	else {
-		if (r->rta_multipath.num_nhops != 0) {
-			for (uint32_t i = 0; i < r->rta_multipath.num_nhops; i++) {
-				struct rta_mpath_nh *nh = r->rta_multipath.nhops[i];
+		if (r->rta_multipath.count != 0) {
+			for (uint32_t i = 0; i < r->rta_multipath.count; i++) {
+				struct rta_mpath_nh *nh =
+				    r->rta_multipath.items[i];
 
 				print_flushed_route(r, nh->gw);
 			}
@@ -975,4 +976,3 @@ flushroutes_fib_nl(int fib, int af)
 
 	return (e.error);
 }
-
diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h
index 34a0f3791af2..ecc980387fa8 100644
--- a/sys/netlink/netlink_snl.h
+++ b/sys/netlink/netlink_snl.h
@@ -724,23 +724,60 @@ snl_attr_get_nested(struct snl_state *ss, struct nlattr *nla, const void *arg, v
 struct snl_parray {
 	uint32_t count;
 	void **items;
+	uint32_t _capacity;
 };
 
+static inline bool
+snl_parray_append(struct snl_state *ss, struct snl_parray *array, void *item,
+    uint32_t start_size)
+{
+	void **new_items;
+	size_t alloc_size;
+	uint32_t new_size;
+
+	if (start_size == 0)
+		return (false);
+	if (array->_capacity == 0) {
+		new_size = start_size;
+		if (__builtin_mul_overflow((size_t)new_size,
+		    sizeof(*new_items), &alloc_size))
+			return (false);
+		array->items = (void **)snl_allocz(ss, alloc_size);
+		if (array->items == NULL)
+			return (false);
+		array->_capacity = new_size;
+	} else if (array->count == array->_capacity) {
+		if (array->_capacity > UINT32_MAX / 2)
+			return (false);
+		new_size = array->_capacity * 2;
+		if (__builtin_mul_overflow((size_t)new_size,
+		    sizeof(*new_items), &alloc_size))
+			return (false);
+		new_items = (void **)snl_allocz(ss, alloc_size);
+		if (new_items == NULL)
+			return (false);
+		memcpy(new_items, array->items,
+		    array->count * sizeof(*new_items));
+		array->items = new_items;
+		array->_capacity = new_size;
+	}
+	array->items[array->count++] = item;
+	return (true);
+}
+
 static inline bool
 snl_attr_get_parray_sz(struct snl_state *ss, struct nlattr *container_nla,
-    uint32_t start_size, const void *arg, void *target)
+    uint32_t start_size, const void *arg, struct snl_parray *array)
 {
 	const struct snl_hdr_parser *p = (const struct snl_hdr_parser *)arg;
-	struct snl_parray *array = (struct snl_parray *)target;
 	struct nlattr *nla;
-	uint32_t count = 0, size = start_size;
 
-	if (p->out_size == 0)
-		return (false);
-
-	array->items = (void **)snl_allocz(ss, size * sizeof(void *));
-	if (array->items == NULL)
+	if (p->out_size == 0 || start_size == 0)
 		return (false);
+	/* A container attribute replaces, rather than extends, its output. */
+	array->count = 0;
+	array->items = NULL;
+	array->_capacity = 0;
 
 	/*
 	 * If the provided parser is an attribute parser, assume that each
@@ -766,31 +803,49 @@ snl_attr_get_parray_sz(struct snl_state *ss, struct nlattr *container_nla,
 		if (!(snl_parse_header(ss, data, data_len, p, item)))
 			return (false);
 
-		if (count == size) {
-			uint32_t new_size = size * 2;
-			void **new_array = (void **)snl_allocz(ss, new_size *sizeof(void *));
-
-			memcpy(new_array, array->items, size * sizeof(void *));
-			array->items = new_array;
-			size = new_size;
-		}
-		array->items[count++] = item;
+		if (!snl_parray_append(ss, array, item, start_size))
+			return (false);
 	}
-	array->count = count;
 
 	return (true);
 }
 
 /*
  * Parses and stores the unknown-size array.
- * Assumes each array item is a container and the NLAs in the container are parsable
- *  by the parser provided in @arg.
- * Assumes @target is struct snl_parray
+ * Assumes each array item is a container and the NLAs in the container are
+ * parsable by the parser provided in @arg.
+ * @target must point to an actual struct snl_parray.  Do not substitute a
+ * layout-compatible structure: the parser array includes private growth state.
  */
 static inline bool
 snl_attr_get_parray(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
 {
-	return (snl_attr_get_parray_sz(ss, nla, 8, arg, target));
+	return (snl_attr_get_parray_sz(ss, nla, 8, arg,
+	    (struct snl_parray *)target));
+}
+
+/*
+ * Append one repeated nested attribute to an array.  Unlike
+ * snl_attr_get_parray(), the attribute itself is one array member rather
+ * than a container holding an entire array.  Use this for modern Netlink
+ * multi-attributes.  @target must point to an actual struct snl_parray.
+ */
+static inline bool
+snl_attr_get_multi(struct snl_state *ss, struct nlattr *nla, const void *arg,
+    void *target)
+{
+	const struct snl_hdr_parser *p = arg;
+	struct snl_parray *array = target;
+	void *item;
+
+	if (p->out_size == 0)
+		return (false);
+	item = snl_allocz(ss, p->out_size);
+	if (item == NULL)
+		return (false);
+	if (!snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), p, item))
+		return (false);
+	return (snl_parray_append(ss, array, item, 8));
 }
 
 static inline bool
@@ -849,11 +904,6 @@ struct snl_attr_bit {
 	int		bit_value;
 };
 
-struct snl_attr_bits {
-	uint32_t num_bits;
-	struct snl_attr_bit **bits;
-};
-
 #define	_OUT(_field)	offsetof(struct snl_attr_bit, _field)
 static const struct snl_attr_parser _nla_p_bit[] = {
 	{ .type = NLA_BITSET_BIT_INDEX, .off = _OUT(bit_index), .cb = snl_attr_get_uint32 },
@@ -867,7 +917,7 @@ struct snl_attr_bitset {
 	uint32_t		nla_bitset_size;
 	uint32_t		*nla_bitset_mask;
 	uint32_t		*nla_bitset_value;
-	struct snl_attr_bits	bits;
+	struct snl_parray	bits;
 };
 
 #define	_OUT(_field)	offsetof(struct snl_attr_bitset, _field)
diff --git a/sys/netlink/netlink_snl_generic.h b/sys/netlink/netlink_snl_generic.h
index 10e98a0266e0..8a8e99813086 100644
--- a/sys/netlink/netlink_snl_generic.h
+++ b/sys/netlink/netlink_snl_generic.h
@@ -65,11 +65,6 @@ struct _snl_genl_ctrl_mcast_group {
 	const char *mcast_grp_name;
 };
 
-struct _snl_genl_ctrl_mcast_groups {
-	uint32_t num_groups;
-	struct _snl_genl_ctrl_mcast_group **groups;
-};
-
 #define	_OUT(_field)	offsetof(struct _snl_genl_ctrl_mcast_group, _field)
 static struct snl_attr_parser _nla_p_getmc[] = {
 	{
@@ -90,7 +85,7 @@ SNL_DECLARE_ATTR_PARSER_EXT(_genl_ctrl_mc_parser,
 struct _getfamily_attrs {
 	uint16_t family_id;
 	const char *family_name;
-	struct _snl_genl_ctrl_mcast_groups mcast_groups;
+	struct snl_parray mcast_groups;
 };
 
 #define	_IN(_field)	offsetof(struct genlmsghdr, _field)
@@ -165,10 +160,13 @@ snl_get_genl_mcast_group(struct snl_state *ss, const char *family_name,
 		return (0);
 	if (family_id != NULL)
 		*family_id = attrs.family_id;
-	for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++)
-		if (strcmp(attrs.mcast_groups.groups[i]->mcast_grp_name,
-                    group_name) == 0)
-			return (attrs.mcast_groups.groups[i]->mcast_grp_id);
+	for (u_int i = 0; i < attrs.mcast_groups.count; i++) {
+		struct _snl_genl_ctrl_mcast_group *group;
+
+		group = attrs.mcast_groups.items[i];
+		if (strcmp(group->mcast_grp_name, group_name) == 0)
+			return (group->mcast_grp_id);
+	}
 	return (0);
 }
 
diff --git a/sys/netlink/netlink_snl_route_parsers.h b/sys/netlink/netlink_snl_route_parsers.h
index f0849884ec97..10698ca987f3 100644
--- a/sys/netlink/netlink_snl_route_parsers.h
+++ b/sys/netlink/netlink_snl_route_parsers.h
@@ -93,21 +93,18 @@ SNL_DECLARE_PARSER_EXT(_mpath_nh_parser, sizeof(struct rtnexthop),
 		sizeof(struct rta_mpath_nh), _fp_p_mp_nh, _nla_p_mp_nh,
 		_cb_p_mp_nh);
 
-struct rta_mpath {
-	uint32_t num_nhops;
-	struct rta_mpath_nh **nhops;
-};
-
 static bool
 nlattr_get_multipath(struct snl_state *ss, struct nlattr *nla,
     const void *arg __unused, void *target)
 {
+	struct snl_parray *mpath = target;
 	uint32_t start_size = 4;
 
 	while (start_size < NLA_DATA_LEN(nla) / sizeof(struct rtnexthop))
 		start_size *= 2;
 
-	return (snl_attr_get_parray_sz(ss, nla, start_size, &_mpath_nh_parser, target));
+	return (snl_attr_get_parray_sz(ss, nla, start_size,
+	    &_mpath_nh_parser, mpath));
 }
 
 struct snl_parsed_route {
@@ -115,7 +112,7 @@ struct snl_parsed_route {
 	struct sockaddr		*rta_gw;
 	struct sockaddr		*rta_pref_src;
 	struct nlattr		*rta_metrics;
-	struct rta_mpath	rta_multipath;
+	struct snl_parray	rta_multipath;
 	uint32_t		rta_oif;
 	uint32_t		rta_expire;
 	uint32_t		rta_table;
diff --git a/tests/sys/netlink/test_rtnl_route.c b/tests/sys/netlink/test_rtnl_route.c
index 334d1fea9fe9..9a1bf0d3b117 100644
--- a/tests/sys/netlink/test_rtnl_route.c
+++ b/tests/sys/netlink/test_rtnl_route.c
@@ -129,7 +129,7 @@ ATF_TC_BODY(rtnl_nhgrp, tc)
 	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
 	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
 	ATF_CHECK(r.rta_knh_id != 0);
-	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 2);
+	ATF_CHECK_INTEQ(r.rta_multipath.count, 2);
 
 	cleanup_route_by_dst(&ss, &nw, "192.0.2.0");
 }
@@ -179,7 +179,7 @@ ATF_TC_BODY(rtnl_nhop_merge, tc)
 	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
 	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
 	ATF_CHECK(r.rta_knh_id != 0);
-	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 0);
+	ATF_CHECK_INTEQ(r.rta_multipath.count, 0);
 
 	/* Append anoher nhop */
 	snl_init_writer(&ss, &nw);
@@ -202,7 +202,7 @@ ATF_TC_BODY(rtnl_nhop_merge, tc)
 	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
 	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
 	ATF_CHECK(r.rta_knh_id != 0);
-	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 2);
+	ATF_CHECK_INTEQ(r.rta_multipath.count, 2);
 
 	cleanup_route_by_dst(&ss, &nw, "198.51.100.0");
 }
@@ -293,7 +293,7 @@ ATF_TC_BODY(rtnl_nhgrp_expire, tc)
 	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
 	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
 	ATF_CHECK(r.rta_knh_id != 0);
-	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 3);
+	ATF_CHECK_INTEQ(r.rta_multipath.count, 3);
 
 	/* wait for 2 seconds and try again */
 	sleep(2);
@@ -306,7 +306,7 @@ ATF_TC_BODY(rtnl_nhgrp_expire, tc)
 	ATF_REQUIRE(snl_send_message(&ss, hdr));
 	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
 	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
-	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 2);
+	ATF_CHECK_INTEQ(r.rta_multipath.count, 2);
 
 	cleanup_route_by_dst(&ss, &nw, "203.0.113.0");
 }
diff --git a/tests/sys/netlink/test_snl.c b/tests/sys/netlink/test_snl.c
index c2bbc4aff949..15160367299a 100644
--- a/tests/sys/netlink/test_snl.c
+++ b/tests/sys/netlink/test_snl.c
@@ -37,6 +37,70 @@ ATF_TC_BODY(snl_verify_core_parsers, tc)
 
 }
 
+ATF_TC(snl_parse_bitset_array);
+ATF_TC_HEAD(snl_parse_bitset_array, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "Tests snl(3) parsing a growing nested bit array");
+	atf_tc_set_md_var(tc, "require.kmods", "netlink");
+}
+
+ATF_TC_BODY(snl_parse_bitset_array, tc)
+{
+	struct snl_attr_bit *bit;
+	struct snl_parsed_link link = {};
+	struct snl_state ss;
+	struct snl_writer nw;
+	struct nlmsghdr *hdr;
+	uint32_t mask, value;
+	char name[16];
+	int bits_off, entry_off, fbsd_off, caps_off;
+
+	ATF_REQUIRE(snl_init(&ss, NETLINK_ROUTE));
+	snl_init_writer(&ss, &nw);
+	hdr = snl_create_msg_request(&nw, RTM_NEWLINK);
+	ATF_REQUIRE(hdr != NULL);
+	ATF_REQUIRE(snl_reserve_msg_object(&nw, struct ifinfomsg) != NULL);
+
+	fbsd_off = snl_add_msg_attr_nested(&nw, IFLA_FREEBSD);
+	ATF_REQUIRE(fbsd_off != 0);
+	caps_off = snl_add_msg_attr_nested(&nw, IFLAF_CAPS);
+	ATF_REQUIRE(caps_off != 0);
+	ATF_REQUIRE(snl_add_msg_attr_u32(&nw, NLA_BITSET_SIZE, 32));
+	mask = 0x1ff;
+	value = 0x155;
+	ATF_REQUIRE(snl_add_msg_attr(&nw, NLA_BITSET_MASK, sizeof(mask),
+	    &mask));
+	ATF_REQUIRE(snl_add_msg_attr(&nw, NLA_BITSET_VALUE, sizeof(value),
+	    &value));
+	bits_off = snl_add_msg_attr_nested(&nw, NLA_BITSET_BITS);
+	ATF_REQUIRE(bits_off != 0);
+	for (uint32_t i = 0; i < 9; i++) {
+		entry_off = snl_add_msg_attr_nested(&nw, i + 1);
+		ATF_REQUIRE(entry_off != 0);
+		ATF_REQUIRE(snl_add_msg_attr_u32(&nw,
+		    NLA_BITSET_BIT_INDEX, i));
+		snprintf(name, sizeof(name), "bit-%u", i);
+		ATF_REQUIRE(snl_add_msg_attr_string(&nw,
+		    NLA_BITSET_BIT_NAME, name));
+		if ((i & 1) == 0)
+			ATF_REQUIRE(snl_add_msg_attr_flag(&nw,
+			    NLA_BITSET_BIT_VALUE));
+		snl_end_attr_nested(&nw, entry_off);
+	}
+	snl_end_attr_nested(&nw, bits_off);
+	snl_end_attr_nested(&nw, caps_off);
+	snl_end_attr_nested(&nw, fbsd_off);
+	hdr = snl_finalize_msg(&nw);
+	ATF_REQUIRE(hdr != NULL);
+
+	ATF_REQUIRE(snl_parse_nlmsg(&ss, hdr, &snl_rtm_link_parser, &link));
+	ATF_REQUIRE_EQ(link.iflaf_caps.bits.count, 9);
+	bit = link.iflaf_caps.bits.items[8];
+	ATF_CHECK_EQ(bit->bit_index, 8);
+	ATF_CHECK_STREQ(bit->bit_name, "bit-8");
+	ATF_CHECK_EQ(bit->bit_value, 1);
+}
 ATF_TC(snl_verify_route_parsers);
 ATF_TC_HEAD(snl_verify_route_parsers, tc)
 {
@@ -230,6 +294,7 @@ ATF_TC_BODY(snl_list_ifaces, tc)
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, snl_verify_core_parsers);
+	ATF_TP_ADD_TC(tp, snl_parse_bitset_array);
 	ATF_TP_ADD_TC(tp, snl_verify_route_parsers);
 	ATF_TP_ADD_TC(tp, snl_parse_errmsg_capped);
 	ATF_TP_ADD_TC(tp, snl_parse_errmsg_capped_extack);
diff --git a/tests/sys/netlink/test_snl_generic.c b/tests/sys/netlink/test_snl_generic.c
index 8613bf04a45c..67515c006d37 100644
--- a/tests/sys/netlink/test_snl_generic.c
+++ b/tests/sys/netlink/test_snl_generic.c
@@ -90,9 +90,10 @@ ATF_TC_BODY(test_snl_get_genl_family_groups, tc)
 	struct _getfamily_attrs attrs = {};
 
 	ATF_CHECK(snl_parse_nlmsg(&ss, hdr, &_genl_ctrl_getfam_parser, &attrs));
-	ATF_CHECK_EQ(attrs.mcast_groups.num_groups, 1);
+	ATF_CHECK_EQ(attrs.mcast_groups.count, 1);
 
-	struct _snl_genl_ctrl_mcast_group *group = attrs.mcast_groups.groups[0];
+	struct _snl_genl_ctrl_mcast_group *group =
+	    attrs.mcast_groups.items[0];
 
 	ATF_CHECK(group->mcast_grp_id > 0);
 	ATF_CHECK(!strcmp(group->mcast_grp_name, "notify"));
@@ -107,4 +108,3 @@ ATF_TP_ADD_TCS(tp)
 
 	return (atf_no_error());
 }
-
diff --git a/usr.bin/genl/genl.c b/usr.bin/genl/genl.c
index 4d2c252dab98..2107c154ee78 100644
--- a/usr.bin/genl/genl.c
+++ b/usr.bin/genl/genl.c
@@ -98,10 +98,6 @@ struct genl_ctrl_op {
 	uint32_t id;
 	uint32_t flags;
 };
-struct genl_ctrl_ops {
-	uint32_t num_ops;
-	struct genl_ctrl_op **ops;
-};
 static struct snl_attr_parser nla_p_getops[] = {
 #define _OUT(_field)	offsetof(struct genl_ctrl_op, _field)
 	{
@@ -123,10 +119,6 @@ struct genl_mcast_group {
 	uint32_t id;
 	const char *name;
 };
-struct genl_mcast_groups {
-	uint32_t num_groups;
-	struct genl_mcast_group **groups;
-};
 static struct snl_attr_parser nla_p_getmc[] = {
 #define	_OUT(_field)	offsetof(struct genl_mcast_group, _field)
 	{
@@ -150,8 +142,8 @@ struct genl_family {
 	uint32_t version;
 	uint32_t hdrsize;
 	uint32_t max_attr;
-	struct genl_mcast_groups mcast_groups;
-	struct genl_ctrl_ops ops;
+	struct snl_parray mcast_groups;
+	struct snl_parray ops;
 };
 
 static struct snl_attr_parser nla_p_getfamily[] = {
@@ -208,19 +200,21 @@ static struct op_capability {
 };
 
 static void
-dump_operations(struct genl_ctrl_ops *ops)
+dump_operations(struct snl_parray *ops)
 {
-	if (ops->num_ops == 0)
+	struct genl_ctrl_op *op;
+
+	if (ops->count == 0)
 		return;
 	printf("\tsupported operations: \n");
-	for (uint32_t i = 0; i < ops->num_ops; i++) {
+	for (uint32_t i = 0; i < ops->count; i++) {
 		bool p = true;
 
+		op = ops->items[i];
 		printf("\t  - ID: %#02x, Capabilities: %#02x",
-		    ops->ops[i]->id,
-		    ops->ops[i]->flags);
+		    op->id, op->flags);
 		for (size_t j = 0; j < nitems(op_caps); j++)
-			if ((ops->ops[i]->flags & op_caps[j].flag) ==
+			if ((op->flags & op_caps[j].flag) ==
 			    op_caps[j].flag) {
 				printf("%s%s", p ? " (" : "; ",
 				    op_caps[j].str);
@@ -231,15 +225,18 @@ dump_operations(struct genl_ctrl_ops *ops)
 }
 
 static void
-dump_mcast_groups(struct genl_mcast_groups *mcast_groups)
+dump_mcast_groups(struct snl_parray *mcast_groups)
 {
-	if (mcast_groups->num_groups == 0)
+	struct genl_mcast_group *group;
+
+	if (mcast_groups->count == 0)
 		return;
 	printf("\tmulticast groups: \n");
-	for (uint32_t i = 0; i < mcast_groups->num_groups; i++)
+	for (uint32_t i = 0; i < mcast_groups->count; i++) {
+		group = mcast_groups->items[i];
 		printf("\t  - ID: %#02x, Name: %s\n",
-		    mcast_groups->groups[i]->id,
-		    mcast_groups->groups[i]->name);
+		    group->id, group->name);
+	}
 }
 
 static void
@@ -298,9 +295,13 @@ static struct genl_family attrs;
 const char *
 group_name(uint32_t id)
 {
-	for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++)
-		if (attrs.mcast_groups.groups[i]->id == id)
-			return (attrs.mcast_groups.groups[i]->name);
+	struct genl_mcast_group *group;
+
+	for (u_int i = 0; i < attrs.mcast_groups.count; i++) {
+		group = attrs.mcast_groups.items[i];
+		if (group->id == id)
+			return (group->name);
+	}
 	return ("???");
 }
 
@@ -340,14 +341,16 @@ monitor_mcast(int argc, char **argv)
 
 	if (argc == 1)
 		all = true;
-	for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++) {
+	for (u_int i = 0; i < attrs.mcast_groups.count; i++) {
+		struct genl_mcast_group *group;
+
+		group = attrs.mcast_groups.items[i];
 		if (all ||
-		    strcmp(attrs.mcast_groups.groups[i]->name, argv[1]) == 0) {
+		    strcmp(group->name, argv[1]) == 0) {
 			found = true;
 			if (setsockopt(ss.fd, SOL_NETLINK,
 			    NETLINK_ADD_MEMBERSHIP,
-			    &attrs.mcast_groups.groups[i]->id,
-			    sizeof(attrs.mcast_groups.groups[i]->id))
+			    &group->id, sizeof(group->id))
 			    == -1)
 				err(EXIT_FAILURE, "Cannot subscribe to command "
 				    "notify");
diff --git a/usr.bin/netstat/route_netlink.c b/usr.bin/netstat/route_netlink.c
index f7349650f4c6..a7b1a546e285 100644
--- a/usr.bin/netstat/route_netlink.c
+++ b/usr.bin/netstat/route_netlink.c
@@ -235,11 +235,12 @@ p_rtentry_netlink(struct snl_state *ss, const char *name, struct nlmsghdr *hdr)
 	if (rt.rtax_weight == 0)
 		rt.rtax_weight = rt_default_weight;
 
-	if (rt.rta_multipath.num_nhops != 0) {
+	if (rt.rta_multipath.count != 0) {
 		uint32_t orig_rtflags = rt.rta_rtflags;
 		uint32_t orig_mtu = rt.rtax_mtu;
-		for (uint32_t i = 0; i < rt.rta_multipath.num_nhops; i++) {
-			struct rta_mpath_nh *nhop = rt.rta_multipath.nhops[i];
+		for (uint32_t i = 0; i < rt.rta_multipath.count; i++) {
+			struct rta_mpath_nh *nhop =
+			    rt.rta_multipath.items[i];
 
 			rt.rta_gw = nhop->gw;
 			rt.rta_oif = nhop->ifindex;
@@ -341,4 +342,3 @@ p_rtable_netlink(int fibnum, int af)
 	return (true);
 }
 
-
diff --git a/usr.sbin/arp/arp_netlink.c b/usr.sbin/arp/arp_netlink.c
index 34f21cf96f4f..e998579d78cb 100644
--- a/usr.sbin/arp/arp_netlink.c
+++ b/usr.sbin/arp/arp_netlink.c
@@ -132,7 +132,7 @@ guess_ifindex(struct snl_state *ss, uint32_t fibnum, struct in_addr addr)
 	if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r))
 		return (0);
 
-	if (r.rta_multipath.num_nhops > 0 || (r.rta_rtflags & RTF_GATEWAY))
+	if (r.rta_multipath.count > 0 || (r.rta_rtflags & RTF_GATEWAY))
 		return (0);
 
 	/* Check if the interface is of supported type */
@@ -448,4 +448,3 @@ set_nl(struct sockaddr_in *dst, struct sockaddr_dl *sdl, char *host)
 
 	return (e.error != 0);
 }
-
diff --git a/usr.sbin/ndp/ndp_netlink.c b/usr.sbin/ndp/ndp_netlink.c
index 7bd3252f0943..cd443bda635a 100644
--- a/usr.sbin/ndp/ndp_netlink.c
+++ b/usr.sbin/ndp/ndp_netlink.c
@@ -164,7 +164,7 @@ guess_ifindex(struct snl_state *ss, uint32_t fibnum, const struct sockaddr_in6 *
 	if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r))
 		return (0);
 
-	if (r.rta_multipath.num_nhops > 0 || (r.rta_rtflags & RTF_GATEWAY))
+	if (r.rta_multipath.count > 0 || (r.rta_rtflags & RTF_GATEWAY))
 		return (0);
 
 	/* Check if the interface is of supported type */
@@ -521,4 +521,3 @@ set_nl(uint32_t ifindex, struct sockaddr_in6 *dst, struct sockaddr_dl *sdl, char
 
 	return (e.error != 0);
 }
-
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.