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:
parent
cc67cd58fc
commit
73ae25c174
@ -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
|
||||
|
97
sys/netlink/netlink_snl_generic.h
Normal file
97
sys/netlink/netlink_snl_generic.h
Normal 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
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
77
tests/sys/netlink/test_snl_generic.c
Normal file
77
tests/sys/netlink/test_snl_generic.c
Normal 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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user