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
This commit is contained in:
Alexander V. Chernikov 2023-03-15 13:56:26 +00:00
parent cc67cd58fc
commit 73ae25c174
5 changed files with 293 additions and 43 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
snl_init_writer(&ss, &nw);
if (!snl_send(&ss, &msg, sizeof(msg))) {
snl_free(&ss);
atf_tc_fail("snl_send() failed");
}
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());
}

View File

@ -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());
}