git: a5e5f4105346 - stable/13 - netlink: improve snl(3)

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Sat, 08 Apr 2023 19:44:54 UTC
The branch stable/13 has been updated by melifaro:

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

commit a5e5f410534627f306778ff38ed942f714962f9b
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-03-15 13:56:26 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-04-08 19:11:41 +0000

    netlink: improve snl(3)
    
    Summary:
    * add snl_send_message() as a convenient send wrapper
    * add signed integer parsers
    * add snl_read_reply_code() to simplify operation result checks
    * add snl_read_reply_multi() to simplify reading multipart messages
    * add snl_create_genl_msg_request()
    * add snl_get_genl_family() to simplify family name->id resolution
    * add tests for some of the functionality
    
    Reviewed by:    kp
    Differential Revision: https://reviews.freebsd.org/D39092
    MFC after:      2 weeks
    
    (cherry picked from commit 73ae25c174b515616050496dea764a943796efef)
---
 sys/netlink/netlink_snl.h            | 104 +++++++++++++++++++++++++++++------
 sys/netlink/netlink_snl_generic.h    |  97 ++++++++++++++++++++++++++++++++
 tests/sys/netlink/Makefile           |   2 +-
 tests/sys/netlink/test_snl.c         |  58 ++++++++++---------
 tests/sys/netlink/test_snl_generic.c |  77 ++++++++++++++++++++++++++
 5 files changed, 294 insertions(+), 44 deletions(-)

diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h
index 31b90003b519..be512b67c7ec 100644
--- a/sys/netlink/netlink_snl.h
+++ b/sys/netlink/netlink_snl.h
@@ -258,6 +258,14 @@ snl_send(struct snl_state *ss, void *data, int sz)
 	return (send(ss->fd, data, sz, 0) == sz);
 }
 
+static inline bool
+snl_send_message(struct snl_state *ss, struct nlmsghdr *hdr)
+{
+	ssize_t sz = NLMSG_ALIGN(hdr->nlmsg_len);
+
+	return (send(ss->fd, hdr, sz, 0) == sz);
+}
+
 static inline uint32_t
 snl_get_seq(struct snl_state *ss)
 {
@@ -298,10 +306,9 @@ snl_read_message(struct snl_state *ss)
 static inline struct nlmsghdr *
 snl_read_reply(struct snl_state *ss, uint32_t nlmsg_seq)
 {
-	while (true) {
-		struct nlmsghdr *hdr = snl_read_message(ss);
-		if (hdr == NULL)
-			break;
+	struct nlmsghdr *hdr;
+
+	while ((hdr = snl_read_message(ss)) != NULL) {
 		if (hdr->nlmsg_seq == nlmsg_seq)
 			return (hdr);
 	}
@@ -309,16 +316,6 @@ snl_read_reply(struct snl_state *ss, uint32_t nlmsg_seq)
 	return (NULL);
 }
 
-static inline struct nlmsghdr *
-snl_get_reply(struct snl_state *ss, struct nlmsghdr *hdr)
-{
-	uint32_t nlmsg_seq = hdr->nlmsg_seq;
-
-	if (snl_send(ss, hdr, hdr->nlmsg_len))
-		return (snl_read_reply(ss, nlmsg_seq));
-	return (NULL);
-}
-
 /*
  * Checks that attributes are sorted by attribute type.
  */
@@ -472,6 +469,34 @@ snl_attr_get_uint64(struct snl_state *ss __unused, struct nlattr *nla,
 	return (false);
 }
 
+static inline bool
+snl_attr_get_int8(struct snl_state *ss, struct nlattr *nla, const void *arg,
+    void *target)
+{
+	return (snl_attr_get_uint8(ss, nla, arg, target));
+}
+
+static inline bool
+snl_attr_get_int16(struct snl_state *ss, struct nlattr *nla, const void *arg,
+    void *target)
+{
+	return (snl_attr_get_uint16(ss, nla, arg, target));
+}
+
+static inline bool
+snl_attr_get_int32(struct snl_state *ss, struct nlattr *nla, const void *arg,
+    void *target)
+{
+	return (snl_attr_get_uint32(ss, nla, arg, target));
+}
+
+static inline bool
+snl_attr_get_int64(struct snl_state *ss, struct nlattr *nla, const void *arg,
+    void *target)
+{
+	return (snl_attr_get_uint64(ss, nla, arg, target));
+}
+
 static inline bool
 snl_attr_get_string(struct snl_state *ss __unused, struct nlattr *nla,
     const void *arg __unused, void *target)
@@ -573,14 +598,55 @@ static const struct snl_field_parser nlf_p_errmsg[] = {
 #undef _OUT
 SNL_DECLARE_PARSER(snl_errmsg_parser, struct nlmsgerr, nlf_p_errmsg, nla_p_errmsg);
 
+#define	_IN(_field)	offsetof(struct nlmsgerr, _field)
+#define	_OUT(_field)	offsetof(struct snl_errmsg_data, _field)
+static const struct snl_attr_parser nla_p_donemsg[] = {};
+
+static const struct snl_field_parser nlf_p_donemsg[] = {
+	{ .off_in = _IN(error), .off_out = _OUT(error), .cb = snl_field_get_uint32 },
+};
+#undef _IN
+#undef _OUT
+SNL_DECLARE_PARSER(snl_donemsg_parser, struct nlmsgerr, nlf_p_donemsg, nla_p_donemsg);
+
 static inline bool
-snl_check_return(struct snl_state *ss, struct nlmsghdr *hdr, struct snl_errmsg_data *e)
+snl_read_reply_code(struct snl_state *ss, uint32_t nlmsg_seq, struct snl_errmsg_data *e)
 {
-	if (hdr != NULL && hdr->nlmsg_type == NLMSG_ERROR)
-		return (snl_parse_nlmsg(ss, hdr, &snl_errmsg_parser, e));
+	struct nlmsghdr *hdr = snl_read_reply(ss, nlmsg_seq);
+
+	if (hdr == NULL) {
+		e->error = EINVAL;
+	} else if (hdr->nlmsg_type == NLMSG_ERROR) {
+		if (!snl_parse_nlmsg(ss, hdr, &snl_errmsg_parser, e))
+			e->error = EINVAL;
+		return (e->error == 0);
+	}
+
 	return (false);
 }
 
+/*
+ * Assumes e is zeroed
+ */
+static inline struct nlmsghdr *
+snl_read_reply_multi(struct snl_state *ss, uint32_t nlmsg_seq, struct snl_errmsg_data *e)
+{
+	struct nlmsghdr *hdr = snl_read_reply(ss, nlmsg_seq);
+
+	if (hdr == NULL) {
+		e->error = EINVAL;
+	} else if (hdr->nlmsg_type == NLMSG_ERROR) {
+		if (!snl_parse_nlmsg(ss, hdr, &snl_errmsg_parser, e))
+			e->error = EINVAL;
+	} if (hdr->nlmsg_type == NLMSG_DONE) {
+		snl_parse_nlmsg(ss, hdr, &snl_donemsg_parser, e);
+	} else
+		return (hdr);
+
+	return (NULL);
+}
+
+
 /* writer logic */
 struct snl_writer {
 	char			*base;
@@ -849,4 +915,8 @@ snl_send_msgs(struct snl_writer *nw)
 	return (snl_send(nw->ss, nw->base, offset));
 }
 
+static const struct snl_hdr_parser *snl_all_core_parsers[] = {
+	&snl_errmsg_parser, &snl_donemsg_parser,
+};
+
 #endif
diff --git a/sys/netlink/netlink_snl_generic.h b/sys/netlink/netlink_snl_generic.h
new file mode 100644
index 000000000000..a725e6948b43
--- /dev/null
+++ b/sys/netlink/netlink_snl_generic.h
@@ -0,0 +1,97 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef	_NETLINK_NETLINK_SNL_GENERIC_H_
+#define	_NETLINK_NETLINK_SNL_GENERIC_H_
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_snl.h>
+
+/* Genetlink helpers */
+static inline struct nlmsghdr *
+snl_create_genl_msg_request(struct snl_writer *nw, int genl_family, uint8_t genl_cmd)
+{
+	assert(nw->hdr == NULL);
+
+	struct nlmsghdr *hdr = snl_reserve_msg_object(nw, struct nlmsghdr);
+	hdr->nlmsg_type = genl_family;
+	hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	nw->hdr = hdr;
+	struct genlmsghdr *ghdr = snl_reserve_msg_object(nw, struct genlmsghdr);
+	ghdr->cmd = genl_cmd;
+
+	return (hdr);
+}
+
+static struct snl_field_parser snl_fp_genl[] = {};
+
+#define	SNL_DECLARE_GENL_PARSER(_name, _np)	SNL_DECLARE_PARSER(_name,\
+    struct genlmsghdr, snl_fp_genl, _np)
+
+struct _getfamily_attrs {
+	uint16_t family_id;
+	char	*family_name;
+};
+
+#define	_IN(_field)	offsetof(struct genlmsghdr, _field)
+#define	_OUT(_field)	offsetof(struct _getfamily_attrs, _field)
+static struct snl_attr_parser _nla_p_getfam[] = {
+	{ .type = CTRL_ATTR_FAMILY_ID , .off = _OUT(family_id), .cb = snl_attr_get_uint16 },
+	{ .type = CTRL_ATTR_FAMILY_NAME, .off = _OUT(family_name), .cb = snl_attr_get_string },
+};
+#undef _IN
+#undef _OUT
+SNL_DECLARE_GENL_PARSER(_genl_ctrl_getfam_parser, _nla_p_getfam);
+
+static inline uint16_t
+snl_get_genl_family(struct snl_state *ss, const char *family_name)
+{
+	struct snl_writer nw;
+	struct nlmsghdr *hdr;
+
+	snl_init_writer(ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY);
+	snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, family_name);
+	if (snl_finalize_msg(&nw) == NULL || !snl_send_message(ss, hdr))
+		return (0);
+
+	hdr = snl_read_reply(ss, hdr->nlmsg_seq);
+	if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR) {
+		struct _getfamily_attrs attrs = {};
+
+		if (snl_parse_nlmsg(ss, hdr, &_genl_ctrl_getfam_parser, &attrs))
+			return (attrs.family_id);
+	}
+
+	return (0);
+}
+
+static const struct snl_hdr_parser *snl_all_genl_parsers[] = {
+	&_genl_ctrl_getfam_parser,
+};
+
+#endif
diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile
index 45095ac95309..656582be33b6 100644
--- a/tests/sys/netlink/Makefile
+++ b/tests/sys/netlink/Makefile
@@ -5,7 +5,7 @@ WARNS?=		1
 
 TESTSDIR=       ${TESTSBASE}/sys/netlink
 
-#ATF_TESTS_C +=	test_snl
+ATF_TESTS_C +=	test_snl test_snl_generic
 ATF_TESTS_PYTEST +=	test_nl_core.py
 ATF_TESTS_PYTEST +=	test_rtnl_iface.py
 ATF_TESTS_PYTEST +=	test_rtnl_ifaddr.py
diff --git a/tests/sys/netlink/test_snl.c b/tests/sys/netlink/test_snl.c
index daacc1aaacec..85bdff7fb163 100644
--- a/tests/sys/netlink/test_snl.c
+++ b/tests/sys/netlink/test_snl.c
@@ -20,13 +20,25 @@ require_netlink(void)
 		atf_tc_skip("netlink module not loaded");
 }
 
-ATF_TC(snl_verify_parsers);
-ATF_TC_HEAD(snl_verify_parsers, tc)
+ATF_TC(snl_verify_core_parsers);
+ATF_TC_HEAD(snl_verify_core_parsers, tc)
 {
-	atf_tc_set_md_var(tc, "descr", "Tests snl(3) parsers are correct");
+	atf_tc_set_md_var(tc, "descr", "Tests snl(3) core nlmsg parsers are correct");
 }
 
-ATF_TC_BODY(snl_verify_parsers, tc)
+ATF_TC_BODY(snl_verify_core_parsers, tc)
+{
+	SNL_VERIFY_PARSERS(snl_all_core_parsers);
+
+}
+
+ATF_TC(snl_verify_route_parsers);
+ATF_TC_HEAD(snl_verify_route_parsers, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Tests snl(3) route parsers are correct");
+}
+
+ATF_TC_BODY(snl_verify_route_parsers, tc)
 {
 	SNL_VERIFY_PARSERS(snl_all_route_parsers);
 
@@ -61,45 +73,39 @@ SNL_DECLARE_PARSER(link_parser, struct ifinfomsg, fp_link, ap_link);
 ATF_TC_BODY(snl_list_ifaces, tc)
 {
 	struct snl_state ss;
+	struct snl_writer nw;
 
 	require_netlink();
 
 	if (!snl_init(&ss, NETLINK_ROUTE))
 		atf_tc_fail("snl_init() failed");
 
-	struct {
-		struct nlmsghdr hdr;
-		struct ifinfomsg ifmsg;
-	} msg = {
-		.hdr.nlmsg_type = RTM_GETLINK,
-		.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
-		.hdr.nlmsg_seq = snl_get_seq(&ss),
-	};
-	msg.hdr.nlmsg_len = sizeof(msg);
-
-	if (!snl_send(&ss, &msg, sizeof(msg))) {
-		snl_free(&ss);
-		atf_tc_fail("snl_send() failed");
-	}
+	snl_init_writer(&ss, &nw);
+
+	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK);
+	ATF_CHECK(hdr != NULL);
+	ATF_CHECK(snl_reserve_msg_object(&nw, struct ifinfomsg) != NULL);
+	ATF_CHECK(snl_finalize_msg(&nw) != NULL);
+	uint32_t seq_id = hdr->nlmsg_seq;
 
-	struct nlmsghdr *hdr;
+	ATF_CHECK(snl_send_message(&ss, hdr));
+
+	struct snl_errmsg_data e = {};
 	int count = 0;
-	while ((hdr = snl_read_message(&ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) {
-		if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq)
-			continue;
 
-		struct nl_parsed_link link = {};
-		if (!snl_parse_nlmsg(&ss, hdr, &link_parser, &link))
-			continue;
+	while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
 		count++;
 	}
+	ATF_REQUIRE(e.error == 0);
+
 	ATF_REQUIRE_MSG(count > 0, "Empty interface list");
 }
 
 ATF_TP_ADD_TCS(tp)
 {
+	ATF_TP_ADD_TC(tp, snl_verify_core_parsers);
+	ATF_TP_ADD_TC(tp, snl_verify_route_parsers);
 	ATF_TP_ADD_TC(tp, snl_list_ifaces);
-	ATF_TP_ADD_TC(tp, snl_verify_parsers);
 
 	return (atf_no_error());
 }
diff --git a/tests/sys/netlink/test_snl_generic.c b/tests/sys/netlink/test_snl_generic.c
new file mode 100644
index 000000000000..c65d134f080d
--- /dev/null
+++ b/tests/sys/netlink/test_snl_generic.c
@@ -0,0 +1,77 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/param.h>
+#include <sys/module.h>
+
+#include <netlink/netlink.h>
+#include "netlink/netlink_snl.h"
+#include "netlink/netlink_snl_generic.h"
+
+#include <atf-c.h>
+
+static void
+require_netlink(void)
+{
+	if (modfind("netlink") == -1)
+		atf_tc_skip("netlink module not loaded");
+}
+
+ATF_TC(snl_verify_genl_parsers);
+ATF_TC_HEAD(snl_verify_genl_parsers, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Tests snl(3) generic parsers are correct");
+}
+
+ATF_TC_BODY(snl_verify_genl_parsers, tc)
+{
+	SNL_VERIFY_PARSERS(snl_all_genl_parsers);
+
+}
+
+ATF_TC(test_snl_get_genl_family_success);
+ATF_TC_HEAD(test_snl_get_genl_family_success, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Tests successfull resolution of the 'nlctrl' family");
+}
+
+ATF_TC_BODY(test_snl_get_genl_family_success, tc)
+{
+	struct snl_state ss;
+
+	require_netlink();
+
+	if (!snl_init(&ss, NETLINK_GENERIC))
+		atf_tc_fail("snl_init() failed");
+
+	ATF_CHECK_EQ(snl_get_genl_family(&ss, "nlctrl"), GENL_ID_CTRL);
+}
+
+ATF_TC(test_snl_get_genl_family_failure);
+ATF_TC_HEAD(test_snl_get_genl_family_failure, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Tests unsuccessfull resolution of 'no-such-family' family");
+}
+
+ATF_TC_BODY(test_snl_get_genl_family_failure, tc)
+{
+	struct snl_state ss;
+
+	require_netlink();
+
+	if (!snl_init(&ss, NETLINK_GENERIC))
+		atf_tc_fail("snl_init() failed");
+
+	ATF_CHECK_EQ(snl_get_genl_family(&ss, "no-such-family"), 0);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	ATF_TP_ADD_TC(tp, snl_verify_genl_parsers);
+	ATF_TP_ADD_TC(tp, test_snl_get_genl_family_success);
+	ATF_TP_ADD_TC(tp, test_snl_get_genl_family_failure);
+
+	return (atf_no_error());
+}
+