netlink: add snl(3) - simple netlink library
Reviewed by: bapt, pauamma
Differential Revision: https://reviews.freebsd.org/D37736
(cherry picked from commit f2c8381fce
)
This commit is contained in:
parent
65f065ec71
commit
b309249b7f
303
share/man/man3/snl.3
Normal file
303
share/man/man3/snl.3
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
.\"
|
||||||
|
.\" Copyright (C) 2022 Alexander 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.
|
||||||
|
.\"
|
||||||
|
.\" $FreeBSD$
|
||||||
|
.Dd December 16, 2022
|
||||||
|
.Dt SNL 3
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm snl_init ,
|
||||||
|
.Nm snl_free ,
|
||||||
|
.Nm snl_read_message ,
|
||||||
|
.Nm snl_send ,
|
||||||
|
.Nm snl_get_seq ,
|
||||||
|
.Nm snl_allocz ,
|
||||||
|
.Nm snl_clear_lb ,
|
||||||
|
.Nm snl_parse_nlmsg ,
|
||||||
|
.Nm snl_parse_header ,
|
||||||
|
.Nm snl_parse_attrs ,
|
||||||
|
.Nm snl_parse_attrs_raw ,
|
||||||
|
.Nm snl_attr_get_flag ,
|
||||||
|
.Nm snl_attr_get_ip ,
|
||||||
|
.Nm snl_attr_get_uint16 ,
|
||||||
|
.Nm snl_attr_get_uint32 ,
|
||||||
|
.Nm snl_attr_get_string ,
|
||||||
|
.Nm snl_attr_get_stringn ,
|
||||||
|
.Nm snl_attr_get_nla ,
|
||||||
|
.Nm snl_field_get_uint8 ,
|
||||||
|
.Nm snl_field_get_uint16 ,
|
||||||
|
.Nm snl_field_get_uint32
|
||||||
|
.Nd "simple netlink library"
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.In netlink/netlink_snl.h
|
||||||
|
.In netlink/netlink_snl_route.h
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_init "struct snl_state *ss" "int netlink_family"
|
||||||
|
.Fn snl_free "struct snl_state *ss"
|
||||||
|
.Ft "struct nlmsghdr *"
|
||||||
|
.Fn snl_read_message "struct snl_state *ss"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_send "struct snl_state *ss" "void *data" "int sz"
|
||||||
|
.Ft "uint32_t"
|
||||||
|
.Fn snl_get_seq "struct snl_state *ss"
|
||||||
|
.Ft "void *"
|
||||||
|
.Fn snl_allocz "struct snl_state *ss" "int len"
|
||||||
|
.Fn snl_clear_lb "struct snl_state *ss"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_parse_nlmsg "struct snl_state *ss" "struct nlmsghdr *hdr" "const struct snl_hdr_parser *ps" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_parse_header "struct snl_state *ss" "void *hdr" "int len" "const struct snl_hdr_parser *ps" "int pslen" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_parse_attrs "struct snl_state *ss" "struct nlmsghdr *hdr" "int hdrlen" "const struct snl_attr_parser *ps" "int pslen" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_parse_attrs_raw "struct snl_state *ss" "struct nlattr *nla_head" "int len" "const struct snl_attr_parser *ps" "int pslen" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_flag "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_uint16 "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_uint32 "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_string "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_stringn "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_nla "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_ip "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_ipvia "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Xr snl 3
|
||||||
|
library provides an easy way of sending and receiving Netlink messages,
|
||||||
|
taking care of serialisation and deserialisation.
|
||||||
|
.Ss INITIALISATION
|
||||||
|
Call
|
||||||
|
.Fn snl_init
|
||||||
|
with a pointer to the
|
||||||
|
.Dv struct snl_state
|
||||||
|
and the desired Netlink family to initialise the library instance.
|
||||||
|
To free the library instance, call
|
||||||
|
.Fn snl_free .
|
||||||
|
.Pp
|
||||||
|
The library functions are NOT multithread-safe.
|
||||||
|
If multithreading is desired, consider initializing an instance
|
||||||
|
per thread.
|
||||||
|
.Ss MEMORY ALLOCATION
|
||||||
|
The library uses pre-allocated extendable memory buffers to handle message parsing.
|
||||||
|
The typical usage pattern is to allocate the necessary data structures during the
|
||||||
|
message parsing or writing process via
|
||||||
|
.Fn snl_allocz
|
||||||
|
and free all allocated data at once using
|
||||||
|
.Fn snl_clear_lb
|
||||||
|
after handling the message.
|
||||||
|
.Ss COMPOSING AND SENDING MESSAGES
|
||||||
|
The library does not currently offer any wrappers for writing netlink messages.
|
||||||
|
Simple request messages can be composed by filling in all needed fields directly.
|
||||||
|
Example for constructing an interface dump request:
|
||||||
|
.Bd -literal
|
||||||
|
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);
|
||||||
|
.Ed
|
||||||
|
.Fn snl_get_seq
|
||||||
|
can be used to generate a unique message number.
|
||||||
|
To send the resulting message,
|
||||||
|
.Fn snl_send
|
||||||
|
can be used.
|
||||||
|
.Ss RECEIVING AND PARSING MESSAGES
|
||||||
|
To receive a message, use
|
||||||
|
.Fn snl_read_message .
|
||||||
|
Currently, this call is blocking.
|
||||||
|
.Pp
|
||||||
|
The library provides an easy way to convert the message to the pre-defined C
|
||||||
|
structure.
|
||||||
|
For each message type, one needs to define rules, converting the protocol header
|
||||||
|
fields and the desired attributes to the specified structure.
|
||||||
|
It can be accomplished by using message parsers.
|
||||||
|
Each message parser consists of an array of attribute getters and an array of
|
||||||
|
header field getters.
|
||||||
|
The former array needs to be sorted by the attribute type.
|
||||||
|
There is a
|
||||||
|
.Fn SNL_VERIFY_PARSERS
|
||||||
|
macro to check if the order is correct.
|
||||||
|
.Fn SNL_DECLARE_PARSER "parser_name" "family header type" "struct snl_field_parser[]" "struct snl_attr_parser[]"
|
||||||
|
can be used to create a new parser.
|
||||||
|
.Fn SNL_DECLARE_ATTR_PARSER "parser_name" "struct snl_field_parser[]"
|
||||||
|
can be used to create an attribute-only message parser.
|
||||||
|
.Pp
|
||||||
|
Each attribute getter needs to be embedded in the following structure:
|
||||||
|
.Bd -literal
|
||||||
|
typedef bool snl_parse_attr_f(struct snl_state *ss, struct nlattr *attr, const void *arg, void *target);
|
||||||
|
struct snl_attr_parser {
|
||||||
|
uint16_t type; /* Attribute type */
|
||||||
|
uint16_t off; /* field offset in the target structure */
|
||||||
|
snl_parse_attr_f *cb; /* getter function to call */
|
||||||
|
const void *arg; /* getter function custom argument */
|
||||||
|
};
|
||||||
|
.Ed
|
||||||
|
The generic attribute getter has the following signature:
|
||||||
|
.Ft "bool"
|
||||||
|
.Fn snl_attr_get_<type> "struct snl_state *ss" "struct nlattr *nla" "const void *arg" "void *target" .
|
||||||
|
nla contains the pointer of the attribute to use as the datasource.
|
||||||
|
The target field is the pointer to the field in the target structure.
|
||||||
|
It is up to the getter to know the type of the target field.
|
||||||
|
The getter must check the input attribute and return
|
||||||
|
false if the attribute is not formed correctly.
|
||||||
|
Otherwise, the getter fetches the attribute value and stores it in the target,
|
||||||
|
then returns true.
|
||||||
|
It is possible to use
|
||||||
|
.Fn snl_allocz
|
||||||
|
to create the desired data structure .
|
||||||
|
A number of predefined getters for the common data types exist.
|
||||||
|
.Fn snl_attr_get_flag
|
||||||
|
converts a flag-type attribute to an uint8_t value of 1 or 0, depending on the
|
||||||
|
attribute presence.
|
||||||
|
.Fn snl_attr_get_uint16
|
||||||
|
stores a uint16_t type attribute into the uint16_t target field.
|
||||||
|
.Fn snl_attr_get_uint32
|
||||||
|
stores a uint32_t type attribute into the uint32_t target field.
|
||||||
|
.Fn snl_attr_get_ip
|
||||||
|
and
|
||||||
|
.Fn snl_attr_get_ipvia
|
||||||
|
stores a pointer to the sockaddr structure with the IPv4/IPv6 address contained
|
||||||
|
in the attribute.
|
||||||
|
Sockaddr is allocated using
|
||||||
|
.Fn snl_allocz .
|
||||||
|
.Fn snl_attr_get_string
|
||||||
|
stores a pointer to the NULL-terminated string.
|
||||||
|
The string itself is allocated using
|
||||||
|
.Fn snl_allocz .
|
||||||
|
.Fn snl_attr_get_nla
|
||||||
|
stores a pointer to the specified attribute.
|
||||||
|
.Fn snl_attr_get_stringn
|
||||||
|
stores a pointer to the non-NULL-terminated string.
|
||||||
|
.Pp
|
||||||
|
Similarly, each family header getter needs to be embedded in the following structure:
|
||||||
|
.Bd -literal
|
||||||
|
typedef void snl_parse_field_f(struct snl_state *ss, void *hdr, void *target);
|
||||||
|
struct snl_field_parser {
|
||||||
|
uint16_t off_in; /* field offset in the input structure */
|
||||||
|
uint16_t off_out;/* field offset in the target structure */
|
||||||
|
snl_parse_field_f *cb; /* getter function to call */
|
||||||
|
};
|
||||||
|
.Ed
|
||||||
|
The generic field getter has the following signature:
|
||||||
|
.Ft "void"
|
||||||
|
snl_field_get_<type> "struct snl_state *ss" "void *src" "void *target" .
|
||||||
|
A number of pre-defined getters for the common data types exist.
|
||||||
|
.Fn "snl_field_get_uint8"
|
||||||
|
fetches an uint8_t value and stores it in the target.
|
||||||
|
.Fn "snl_field_get_uint16"
|
||||||
|
fetches an uint8_t value and stores it in the target.
|
||||||
|
.Fn "snl_field_get_uint32"
|
||||||
|
fetches an uint32_t value and stores it in the target.
|
||||||
|
.Sh EXAMPLES
|
||||||
|
The following example demonstrates how to list all system interfaces
|
||||||
|
using netlink.
|
||||||
|
.Bd -literal
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <netlink/netlink.h>
|
||||||
|
#include <netlink/netlink_route.h>
|
||||||
|
#include "netlink/netlink_snl.h"
|
||||||
|
#include "netlink/netlink_snl_route.h"
|
||||||
|
|
||||||
|
struct nl_parsed_link {
|
||||||
|
uint32_t ifi_index;
|
||||||
|
uint32_t ifla_mtu;
|
||||||
|
char *ifla_ifname;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define _IN(_field) offsetof(struct ifinfomsg, _field)
|
||||||
|
#define _OUT(_field) offsetof(struct nl_parsed_link, _field)
|
||||||
|
static const struct snl_attr_parser ap_link[] = {
|
||||||
|
{ .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = snl_attr_get_string },
|
||||||
|
{ .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = snl_attr_get_uint32 },
|
||||||
|
};
|
||||||
|
static const struct snl_field_parser fp_link[] = {
|
||||||
|
{.off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = snl_field_get_uint32 },
|
||||||
|
};
|
||||||
|
#undef _IN
|
||||||
|
#undef _OUT
|
||||||
|
SNL_DECLARE_PARSER(link_parser, struct ifinfomsg, fp_link, ap_link);
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int ac, char *argv[])
|
||||||
|
{
|
||||||
|
struct snl_state ss;
|
||||||
|
|
||||||
|
if (!snl_init(&ss, NETLINK_ROUTE))
|
||||||
|
return (1);
|
||||||
|
|
||||||
|
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);
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct nlmsghdr *hdr;
|
||||||
|
while ((hdr = snl_read_message(&ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) {
|
||||||
|
if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq)
|
||||||
|
break;
|
||||||
|
|
||||||
|
struct nl_parsed_link link = {};
|
||||||
|
if (!snl_parse_nlmsg(&ss, hdr, &link_parser, &link))
|
||||||
|
continue;
|
||||||
|
printf("Link#%u %s mtu %u\n", link.ifi_index, link.ifla_ifname, link.ifla_mtu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
.Ed
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr genetlink 4 ,
|
||||||
|
.Xr netlink 4 ,
|
||||||
|
and
|
||||||
|
.Xr rtnetlink 4
|
||||||
|
.Sh HISTORY
|
||||||
|
The
|
||||||
|
.Dv SNL
|
||||||
|
library appeared in
|
||||||
|
.Fx 14.0 .
|
||||||
|
.Sh AUTHORS
|
||||||
|
This library was implemented by
|
||||||
|
.An Alexander Chernikov Aq Mt melifaro@FreeBSD.org .
|
@ -76,7 +76,9 @@ SYSCTL_ULONG(_net_netlink, OID_AUTO, recvspace, CTLFLAG_RW, &nl_recvspace, 0,
|
|||||||
"Default netlink socket receive space");
|
"Default netlink socket receive space");
|
||||||
|
|
||||||
extern u_long sb_max_adj;
|
extern u_long sb_max_adj;
|
||||||
|
#if 0
|
||||||
static u_long nl_maxsockbuf = 512 * 1024 * 1024; /* 512M, XXX: init based on physmem */
|
static u_long nl_maxsockbuf = 512 * 1024 * 1024; /* 512M, XXX: init based on physmem */
|
||||||
|
#endif
|
||||||
|
|
||||||
uint32_t
|
uint32_t
|
||||||
nlp_get_pid(const struct nlpcb *nlp)
|
nlp_get_pid(const struct nlpcb *nlp)
|
||||||
@ -671,6 +673,7 @@ nl_ctloutput(struct socket *so, struct sockopt *sopt)
|
|||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
static int
|
static int
|
||||||
nl_setsbopt(struct socket *so, struct sockopt *sopt)
|
nl_setsbopt(struct socket *so, struct sockopt *sopt)
|
||||||
{
|
{
|
||||||
@ -697,32 +700,44 @@ nl_setsbopt(struct socket *so, struct sockopt *sopt)
|
|||||||
|
|
||||||
return (result ? 0 : ENOBUFS);
|
return (result ? 0 : ENOBUFS);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct pr_usrreqs nl_usrreqs = {
|
||||||
|
.pru_abort = nl_pru_abort,
|
||||||
|
.pru_attach = nl_pru_attach,
|
||||||
|
.pru_bind = nl_pru_bind,
|
||||||
|
.pru_connect = nl_pru_connect,
|
||||||
|
.pru_detach = nl_pru_detach,
|
||||||
|
.pru_disconnect = nl_pru_disconnect,
|
||||||
|
.pru_peeraddr = nl_pru_peeraddr,
|
||||||
|
.pru_send = nl_pru_send,
|
||||||
|
//.pru_soreceive = soreceive_dgram,
|
||||||
|
//.pru_sosend = sosend_dgram,
|
||||||
|
.pru_shutdown = nl_pru_shutdown,
|
||||||
|
.pru_sockaddr = nl_pru_sockaddr,
|
||||||
|
//.pru_sosetlabel = in_pcbsosetlabel,
|
||||||
|
.pru_close = nl_pru_close,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct domain netlinkdomain;
|
||||||
|
|
||||||
static struct protosw netlinksw = {
|
static struct protosw netlinksw = {
|
||||||
.pr_type = SOCK_RAW,
|
.pr_type = SOCK_RAW,
|
||||||
|
.pr_domain = &netlinkdomain,
|
||||||
|
.pr_protocol = 0, // IPPROTO_UDP
|
||||||
.pr_flags = PR_ATOMIC | PR_ADDR | PR_WANTRCVD,
|
.pr_flags = PR_ATOMIC | PR_ADDR | PR_WANTRCVD,
|
||||||
.pr_ctloutput = nl_ctloutput,
|
.pr_ctloutput = nl_ctloutput,
|
||||||
.pr_setsbopt = nl_setsbopt,
|
.pr_usrreqs = &nl_usrreqs,
|
||||||
.pr_abort = nl_pru_abort,
|
|
||||||
.pr_attach = nl_pru_attach,
|
|
||||||
.pr_bind = nl_pru_bind,
|
|
||||||
.pr_connect = nl_pru_connect,
|
|
||||||
.pr_detach = nl_pru_detach,
|
|
||||||
.pr_disconnect = nl_pru_disconnect,
|
|
||||||
.pr_peeraddr = nl_pru_peeraddr,
|
|
||||||
.pr_send = nl_pru_send,
|
|
||||||
.pr_rcvd = nl_pru_rcvd,
|
|
||||||
.pr_shutdown = nl_pru_shutdown,
|
|
||||||
.pr_sockaddr = nl_pru_sockaddr,
|
|
||||||
.pr_close = nl_pru_close
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct domain netlinkdomain = {
|
static struct domain netlinkdomain = {
|
||||||
.dom_family = PF_NETLINK,
|
.dom_family = AF_NETLINK,
|
||||||
.dom_name = "netlink",
|
.dom_name = "netlink",
|
||||||
|
#ifdef DOMF_UNLOADABLE
|
||||||
.dom_flags = DOMF_UNLOADABLE,
|
.dom_flags = DOMF_UNLOADABLE,
|
||||||
.dom_nprotosw = 1,
|
#endif
|
||||||
.dom_protosw = { &netlinksw },
|
.dom_protosw = &netlinksw,
|
||||||
|
.dom_protoswNPROTOSW = (&netlinksw + 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
DOMAIN_SET(netlink);
|
DOMAIN_SET(netlink);
|
||||||
|
435
sys/netlink/netlink_snl.h
Normal file
435
sys/netlink/netlink_snl.h
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
/*-
|
||||||
|
* 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_H_
|
||||||
|
#define _NETLINK_NETLINK_SNL_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple Netlink Library
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define _roundup2(x, y) (((x)+((y)-1))&(~((y)-1)))
|
||||||
|
|
||||||
|
#define NETLINK_ALIGN_SIZE sizeof(uint32_t)
|
||||||
|
#define NETLINK_ALIGN(_len) _roundup2(_len, NETLINK_ALIGN_SIZE)
|
||||||
|
|
||||||
|
#define NLA_ALIGN_SIZE sizeof(uint32_t)
|
||||||
|
#define NLA_HDRLEN ((int)sizeof(struct nlattr))
|
||||||
|
#define NLA_DATA_LEN(_nla) ((int)((_nla)->nla_len - NLA_HDRLEN))
|
||||||
|
#define NLA_DATA(_nla) NL_ITEM_DATA(_nla, NLA_HDRLEN)
|
||||||
|
#define NLA_DATA_CONST(_nla) NL_ITEM_DATA_CONST(_nla, NLA_HDRLEN)
|
||||||
|
|
||||||
|
#define NLA_TYPE(_nla) ((_nla)->nla_type & 0x3FFF)
|
||||||
|
|
||||||
|
#define NLA_NEXT(_attr) (struct nlattr *)((char *)_attr + NLA_ALIGN(_attr->nla_len))
|
||||||
|
|
||||||
|
#define _NLA_END(_start, _len) ((char *)(_start) + (_len))
|
||||||
|
#define NLA_FOREACH(_attr, _start, _len) \
|
||||||
|
for (_attr = (_start); \
|
||||||
|
((char *)_attr < _NLA_END(_start, _len)) && \
|
||||||
|
((char *)NLA_NEXT(_attr) <= _NLA_END(_start, _len)); \
|
||||||
|
_attr = NLA_NEXT(_attr))
|
||||||
|
|
||||||
|
#define NL_ARRAY_LEN(_a) (sizeof(_a) / sizeof((_a)[0]))
|
||||||
|
|
||||||
|
struct linear_buffer {
|
||||||
|
char *base; /* Base allocated memory pointer */
|
||||||
|
uint32_t offset; /* Currently used offset */
|
||||||
|
uint32_t size; /* Total buffer size */
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline char *
|
||||||
|
lb_allocz(struct linear_buffer *lb, int len)
|
||||||
|
{
|
||||||
|
len = roundup2(len, sizeof(uint64_t));
|
||||||
|
if (lb->offset + len > lb->size)
|
||||||
|
return (NULL);
|
||||||
|
void *data = (void *)(lb->base + lb->offset);
|
||||||
|
lb->offset += len;
|
||||||
|
return (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
lb_clear(struct linear_buffer *lb)
|
||||||
|
{
|
||||||
|
memset(lb->base, 0, lb->offset);
|
||||||
|
lb->offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct snl_state {
|
||||||
|
int fd;
|
||||||
|
char *buf;
|
||||||
|
size_t off;
|
||||||
|
size_t bufsize;
|
||||||
|
size_t datalen;
|
||||||
|
uint32_t seq;
|
||||||
|
bool init_done;
|
||||||
|
struct linear_buffer lb;
|
||||||
|
};
|
||||||
|
#define SCRATCH_BUFFER_SIZE 1024
|
||||||
|
|
||||||
|
typedef void snl_parse_field_f(struct snl_state *ss, void *hdr, void *target);
|
||||||
|
struct snl_field_parser {
|
||||||
|
uint16_t off_in;
|
||||||
|
uint16_t off_out;
|
||||||
|
snl_parse_field_f *cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef bool snl_parse_attr_f(struct snl_state *ss, struct nlattr *attr,
|
||||||
|
const void *arg, void *target);
|
||||||
|
struct snl_attr_parser {
|
||||||
|
uint16_t type; /* Attribute type */
|
||||||
|
uint16_t off; /* field offset in the target structure */
|
||||||
|
snl_parse_attr_f *cb; /* parser function to call */
|
||||||
|
const void *arg; /* Optional argument parser */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct snl_hdr_parser {
|
||||||
|
int hdr_off; /* aligned header size */
|
||||||
|
int fp_size;
|
||||||
|
int np_size;
|
||||||
|
const struct snl_field_parser *fp; /* array of header field parsers */
|
||||||
|
const struct snl_attr_parser *np; /* array of attribute parsers */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SNL_DECLARE_PARSER(_name, _t, _fp, _np) \
|
||||||
|
static const struct snl_hdr_parser _name = { \
|
||||||
|
.hdr_off = sizeof(_t), \
|
||||||
|
.fp = &((_fp)[0]), \
|
||||||
|
.np = &((_np)[0]), \
|
||||||
|
.fp_size = NL_ARRAY_LEN(_fp), \
|
||||||
|
.np_size = NL_ARRAY_LEN(_np), \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SNL_DECLARE_ATTR_PARSER(_name, _np) \
|
||||||
|
static const struct snl_hdr_parser _name = { \
|
||||||
|
.np = &((_np)[0]), \
|
||||||
|
.np_size = NL_ARRAY_LEN(_np), \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
snl_free(struct snl_state *ss)
|
||||||
|
{
|
||||||
|
if (ss->init_done) {
|
||||||
|
close(ss->fd);
|
||||||
|
if (ss->buf != NULL)
|
||||||
|
free(ss->buf);
|
||||||
|
if (ss->lb.base != NULL)
|
||||||
|
free(ss->lb.base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_init(struct snl_state *ss, int netlink_family)
|
||||||
|
{
|
||||||
|
memset(ss, 0, sizeof(*ss));
|
||||||
|
|
||||||
|
ss->fd = socket(AF_NETLINK, SOCK_RAW, netlink_family);
|
||||||
|
if (ss->fd == -1)
|
||||||
|
return (false);
|
||||||
|
ss->init_done = true;
|
||||||
|
|
||||||
|
int rcvbuf;
|
||||||
|
socklen_t optlen = sizeof(rcvbuf);
|
||||||
|
if (getsockopt(ss->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) == -1) {
|
||||||
|
snl_free(ss);
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ss->bufsize = rcvbuf;
|
||||||
|
ss->buf = malloc(ss->bufsize);
|
||||||
|
if (ss->buf == NULL) {
|
||||||
|
snl_free(ss);
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ss->lb.size = SCRATCH_BUFFER_SIZE;
|
||||||
|
ss->lb.base = calloc(1, ss->lb.size);
|
||||||
|
if (ss->lb.base == NULL) {
|
||||||
|
snl_free(ss);
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *
|
||||||
|
snl_allocz(struct snl_state *ss, int len)
|
||||||
|
{
|
||||||
|
return (lb_allocz(&ss->lb, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
snl_clear_lb(struct snl_state *ss)
|
||||||
|
{
|
||||||
|
lb_clear(&ss->lb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_send(struct snl_state *ss, void *data, int sz)
|
||||||
|
{
|
||||||
|
return (send(ss->fd, data, sz, 0) == sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
snl_get_seq(struct snl_state *ss)
|
||||||
|
{
|
||||||
|
return (++ss->seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct nlmsghdr *
|
||||||
|
snl_read_message(struct snl_state *ss)
|
||||||
|
{
|
||||||
|
if (ss->off == ss->datalen) {
|
||||||
|
struct sockaddr_nl nladdr;
|
||||||
|
struct iovec iov = {
|
||||||
|
.iov_base = ss->buf,
|
||||||
|
.iov_len = ss->bufsize,
|
||||||
|
};
|
||||||
|
struct msghdr msg = {
|
||||||
|
.msg_name = &nladdr,
|
||||||
|
.msg_namelen = sizeof(nladdr),
|
||||||
|
.msg_iov = &iov,
|
||||||
|
.msg_iovlen = 1,
|
||||||
|
};
|
||||||
|
ss->off = 0;
|
||||||
|
ss->datalen = 0;
|
||||||
|
for (;;) {
|
||||||
|
ssize_t datalen = recvmsg(ss->fd, &msg, 0);
|
||||||
|
if (datalen > 0) {
|
||||||
|
ss->datalen = datalen;
|
||||||
|
break;
|
||||||
|
} else if (errno != EINTR)
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct nlmsghdr *hdr = (struct nlmsghdr *)&ss->buf[ss->off];
|
||||||
|
ss->off += NLMSG_ALIGN(hdr->nlmsg_len);
|
||||||
|
return (hdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks that attributes are sorted by attribute type.
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
snl_verify_parsers(const struct snl_hdr_parser **parser, int count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const struct snl_hdr_parser *p = parser[i];
|
||||||
|
int attr_type = 0;
|
||||||
|
for (int j = 0; j < p->np_size; j++) {
|
||||||
|
assert(p->np[j].type > attr_type);
|
||||||
|
attr_type = p->np[j].type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#define SNL_VERIFY_PARSERS(_p) snl_verify_parsers((_p), NL_ARRAY_LEN(_p))
|
||||||
|
|
||||||
|
static const struct snl_attr_parser *
|
||||||
|
find_parser(const struct snl_attr_parser *ps, int pslen, int key)
|
||||||
|
{
|
||||||
|
int left_i = 0, right_i = pslen - 1;
|
||||||
|
|
||||||
|
if (key < ps[0].type || key > ps[pslen - 1].type)
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
|
while (left_i + 1 < right_i) {
|
||||||
|
int mid_i = (left_i + right_i) / 2;
|
||||||
|
if (key < ps[mid_i].type)
|
||||||
|
right_i = mid_i;
|
||||||
|
else if (key > ps[mid_i].type)
|
||||||
|
left_i = mid_i + 1;
|
||||||
|
else
|
||||||
|
return (&ps[mid_i]);
|
||||||
|
}
|
||||||
|
if (ps[left_i].type == key)
|
||||||
|
return (&ps[left_i]);
|
||||||
|
else if (ps[right_i].type == key)
|
||||||
|
return (&ps[right_i]);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_parse_attrs_raw(struct snl_state *ss, struct nlattr *nla_head, int len,
|
||||||
|
const struct snl_attr_parser *ps, int pslen, void *target)
|
||||||
|
{
|
||||||
|
struct nlattr *nla;
|
||||||
|
|
||||||
|
NLA_FOREACH(nla, nla_head, len) {
|
||||||
|
if (nla->nla_len < sizeof(struct nlattr))
|
||||||
|
return (false);
|
||||||
|
int nla_type = nla->nla_type & NLA_TYPE_MASK;
|
||||||
|
const struct snl_attr_parser *s = find_parser(ps, pslen, nla_type);
|
||||||
|
if (s != NULL) {
|
||||||
|
void *ptr = (void *)((char *)target + s->off);
|
||||||
|
if (!s->cb(ss, nla, s->arg, ptr))
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_parse_attrs(struct snl_state *ss, struct nlmsghdr *hdr, int hdrlen,
|
||||||
|
const struct snl_attr_parser *ps, int pslen, void *target)
|
||||||
|
{
|
||||||
|
int off = NLMSG_HDRLEN + NETLINK_ALIGN(hdrlen);
|
||||||
|
int len = hdr->nlmsg_len - off;
|
||||||
|
struct nlattr *nla_head = (struct nlattr *)((char *)hdr + off);
|
||||||
|
|
||||||
|
return (snl_parse_attrs_raw(ss, nla_head, len, ps, pslen, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_parse_header(struct snl_state *ss, void *hdr, int len,
|
||||||
|
const struct snl_hdr_parser *parser, void *target)
|
||||||
|
{
|
||||||
|
/* Extract fields first (if any) */
|
||||||
|
for (int i = 0; i < parser->fp_size; i++) {
|
||||||
|
const struct snl_field_parser *fp = &parser->fp[i];
|
||||||
|
void *src = (char *)hdr + fp->off_in;
|
||||||
|
void *dst = (char *)target + fp->off_out;
|
||||||
|
|
||||||
|
fp->cb(ss, src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct nlattr *nla_head = (struct nlattr *)((char *)hdr + parser->hdr_off);
|
||||||
|
bool result = snl_parse_attrs_raw(ss, nla_head, len - parser->hdr_off,
|
||||||
|
parser->np, parser->np_size, target);
|
||||||
|
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_parse_nlmsg(struct snl_state *ss, struct nlmsghdr *hdr,
|
||||||
|
const struct snl_hdr_parser *parser, void *target)
|
||||||
|
{
|
||||||
|
return (snl_parse_header(ss, hdr + 1, hdr->nlmsg_len - sizeof(*hdr), parser, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_flag(struct snl_state *ss, struct nlattr *nla, void *target)
|
||||||
|
{
|
||||||
|
if (NLA_DATA_LEN(nla) == 0) {
|
||||||
|
*((uint8_t *)target) = 1;
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_uint16(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||||
|
{
|
||||||
|
if (NLA_DATA_LEN(nla) == sizeof(uint16_t)) {
|
||||||
|
*((uint16_t *)target) = *((const uint16_t *)NL_RTA_DATA_CONST(nla));
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_uint32(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||||
|
{
|
||||||
|
if (NLA_DATA_LEN(nla) == sizeof(uint32_t)) {
|
||||||
|
*((uint32_t *)target) = *((const uint32_t *)NL_RTA_DATA_CONST(nla));
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_string(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||||
|
{
|
||||||
|
size_t maxlen = NLA_DATA_LEN(nla);
|
||||||
|
|
||||||
|
if (strnlen((char *)NLA_DATA(nla), maxlen) < maxlen) {
|
||||||
|
*((char **)target) = (char *)NLA_DATA(nla);
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_stringn(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||||
|
{
|
||||||
|
int maxlen = NLA_DATA_LEN(nla);
|
||||||
|
|
||||||
|
char *buf = snl_allocz(ss, maxlen + 1);
|
||||||
|
if (buf == NULL)
|
||||||
|
return (false);
|
||||||
|
buf[maxlen] = '\0';
|
||||||
|
memcpy(buf, NLA_DATA(nla), maxlen);
|
||||||
|
|
||||||
|
*((char **)target) = buf;
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_nested(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||||
|
{
|
||||||
|
const struct snl_hdr_parser *p = (const struct snl_hdr_parser *)arg;
|
||||||
|
|
||||||
|
/* Assumes target points to the beginning of the structure */
|
||||||
|
return (snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), p, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_nla(struct snl_state *ss, struct nlattr *nla, void *target)
|
||||||
|
{
|
||||||
|
*((struct nlattr **)target) = nla;
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
snl_field_get_uint8(struct snl_state *ss, void *src, void *target)
|
||||||
|
{
|
||||||
|
*((uint8_t *)target) = *((uint8_t *)src);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
snl_field_get_uint16(struct snl_state *ss, void *src, void *target)
|
||||||
|
{
|
||||||
|
*((uint16_t *)target) = *((uint16_t *)src);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
snl_field_get_uint32(struct snl_state *ss, void *src, void *target)
|
||||||
|
{
|
||||||
|
*((uint32_t *)target) = *((uint32_t *)src);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
128
sys/netlink/netlink_snl_route.h
Normal file
128
sys/netlink/netlink_snl_route.h
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/*-
|
||||||
|
* 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_ROUTE_H_
|
||||||
|
#define _NETLINK_NETLINK_SNL_ROUTE_H_
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple Netlink Library - NETLINK_ROUTE helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define snl_alloc_sockaddr(_ss, _len) ((struct sockaddr *)(snl_allocz(_ss, _len)))
|
||||||
|
|
||||||
|
static inline struct sockaddr *
|
||||||
|
parse_rta_ip4(struct snl_state *ss, void *rta_data, int *perror)
|
||||||
|
{
|
||||||
|
struct sockaddr_in *sin;
|
||||||
|
|
||||||
|
sin = (struct sockaddr_in *)snl_alloc_sockaddr(ss, sizeof(struct sockaddr_in));
|
||||||
|
if (sin == NULL) {
|
||||||
|
*perror = ENOBUFS;
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
sin->sin_len = sizeof(struct sockaddr_in);
|
||||||
|
sin->sin_family = AF_INET;
|
||||||
|
memcpy(&sin->sin_addr, rta_data, sizeof(struct in_addr));
|
||||||
|
return ((struct sockaddr *)sin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct sockaddr *
|
||||||
|
parse_rta_ip6(struct snl_state *ss, void *rta_data, int *perror)
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 *sin6;
|
||||||
|
|
||||||
|
sin6 = (struct sockaddr_in6 *)snl_alloc_sockaddr(ss, sizeof(struct sockaddr_in6));
|
||||||
|
if (sin6 == NULL) {
|
||||||
|
*perror = ENOBUFS;
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
sin6->sin6_len = sizeof(struct sockaddr_in6);
|
||||||
|
sin6->sin6_family = AF_INET6;
|
||||||
|
memcpy(&sin6->sin6_addr, rta_data, sizeof(struct in6_addr));
|
||||||
|
return ((struct sockaddr *)sin6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct sockaddr *
|
||||||
|
parse_rta_ip(struct snl_state *ss, struct rtattr *rta, int *perror)
|
||||||
|
{
|
||||||
|
void *rta_data = NL_RTA_DATA(rta);
|
||||||
|
int rta_len = NL_RTA_DATA_LEN(rta);
|
||||||
|
|
||||||
|
if (rta_len == sizeof(struct in_addr)) {
|
||||||
|
return (parse_rta_ip4(ss, rta_data, perror));
|
||||||
|
} else if (rta_len == sizeof(struct in6_addr)) {
|
||||||
|
return (parse_rta_ip6(ss, rta_data, perror));
|
||||||
|
} else {
|
||||||
|
*perror = ENOTSUP;
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_ip(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
struct sockaddr *sa = parse_rta_ip(ss, (struct rtattr *)nla, &error);
|
||||||
|
if (error == 0) {
|
||||||
|
*((struct sockaddr **)target) = sa;
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct sockaddr *
|
||||||
|
parse_rta_via(struct snl_state *ss, struct rtattr *rta, int *perror)
|
||||||
|
{
|
||||||
|
struct rtvia *via = NL_RTA_DATA(rta);
|
||||||
|
|
||||||
|
switch (via->rtvia_family) {
|
||||||
|
case AF_INET:
|
||||||
|
return (parse_rta_ip4(ss, via->rtvia_addr, perror));
|
||||||
|
case AF_INET6:
|
||||||
|
return (parse_rta_ip6(ss, via->rtvia_addr, perror));
|
||||||
|
default:
|
||||||
|
*perror = ENOTSUP;
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
snl_attr_get_ipvia(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
struct sockaddr *sa = parse_rta_via(ss, (struct rtattr *)nla, &error);
|
||||||
|
if (error == 0) {
|
||||||
|
*((struct sockaddr **)target) = sa;
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -5,9 +5,10 @@ WARNS?= 1
|
|||||||
|
|
||||||
TESTSDIR= ${TESTSBASE}/sys/netlink
|
TESTSDIR= ${TESTSBASE}/sys/netlink
|
||||||
|
|
||||||
#ATF_TESTS_C += test_rtsock_l3
|
#ATF_TESTS_C += test_snl
|
||||||
#ATF_TESTS_C += test_rtsock_lladdr
|
ATF_TESTS_PYTEST += test_nl_core.py
|
||||||
ATF_TESTS_PYTEST += test_rtnl_iface.py
|
ATF_TESTS_PYTEST += test_rtnl_iface.py
|
||||||
|
ATF_TESTS_PYTEST += test_rtnl_ifaddr.py
|
||||||
|
|
||||||
CFLAGS+= -I${.CURDIR:H:H:H}
|
CFLAGS+= -I${.CURDIR:H:H:H}
|
||||||
|
|
||||||
|
92
tests/sys/netlink/test_snl.c
Normal file
92
tests/sys/netlink/test_snl.c
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/module.h>
|
||||||
|
|
||||||
|
#include <netlink/netlink.h>
|
||||||
|
#include <netlink/netlink_route.h>
|
||||||
|
#include "netlink/netlink_snl.h"
|
||||||
|
#include "netlink/netlink_snl_route.h"
|
||||||
|
|
||||||
|
#include <atf-c.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
require_netlink(void)
|
||||||
|
{
|
||||||
|
if (modfind("netlink") == -1)
|
||||||
|
atf_tc_skip("netlink module not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ATF_TC(snl_list_ifaces);
|
||||||
|
ATF_TC_HEAD(snl_list_ifaces, tc)
|
||||||
|
{
|
||||||
|
atf_tc_set_md_var(tc, "descr", "Tests snl(3) listing interfaces");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct nl_parsed_link {
|
||||||
|
uint32_t ifi_index;
|
||||||
|
uint32_t ifla_mtu;
|
||||||
|
char *ifla_ifname;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define _IN(_field) offsetof(struct ifinfomsg, _field)
|
||||||
|
#define _OUT(_field) offsetof(struct nl_parsed_link, _field)
|
||||||
|
static struct snl_attr_parser ap_link[] = {
|
||||||
|
{ .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = snl_attr_get_string },
|
||||||
|
{ .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = snl_attr_get_uint32 },
|
||||||
|
};
|
||||||
|
static struct snl_field_parser fp_link[] = {
|
||||||
|
{.off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = snl_field_get_uint32 },
|
||||||
|
};
|
||||||
|
#undef _IN
|
||||||
|
#undef _OUT
|
||||||
|
SNL_DECLARE_PARSER(link_parser, struct ifinfomsg, fp_link, ap_link);
|
||||||
|
|
||||||
|
|
||||||
|
ATF_TC_BODY(snl_list_ifaces, tc)
|
||||||
|
{
|
||||||
|
struct snl_state ss;
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct nlmsghdr *hdr;
|
||||||
|
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;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
ATF_REQUIRE_MSG(count > 0, "Empty interface list");
|
||||||
|
}
|
||||||
|
|
||||||
|
ATF_TP_ADD_TCS(tp)
|
||||||
|
{
|
||||||
|
ATF_TP_ADD_TC(tp, snl_list_ifaces);
|
||||||
|
|
||||||
|
return (atf_no_error());
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user