git: b309249b7fd8 - stable/13 - netlink: add snl(3) - simple netlink library
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 23 Jan 2023 22:12:09 UTC
The branch stable/13 has been updated by melifaro: URL: https://cgit.FreeBSD.org/src/commit/?id=b309249b7fd8f29312be7cf08ccd5693d8409260 commit b309249b7fd8f29312be7cf08ccd5693d8409260 Author: Alexander V. Chernikov <melifaro@FreeBSD.org> AuthorDate: 2022-12-18 17:34:41 +0000 Commit: Alexander V. Chernikov <melifaro@FreeBSD.org> CommitDate: 2023-01-23 22:09:00 +0000 netlink: add snl(3) - simple netlink library Reviewed by: bapt, pauamma Differential Revision: https://reviews.freebsd.org/D37736 (cherry picked from commit f2c8381fce9b87695ea448591e4412cbed38aa77) --- share/man/man3/snl.3 | 303 ++++++++++++++++++++++++++++ sys/netlink/netlink_domain.c | 47 +++-- sys/netlink/netlink_snl.h | 435 ++++++++++++++++++++++++++++++++++++++++ sys/netlink/netlink_snl_route.h | 128 ++++++++++++ tests/sys/netlink/Makefile | 5 +- tests/sys/netlink/test_snl.c | 92 +++++++++ 6 files changed, 992 insertions(+), 18 deletions(-) diff --git a/share/man/man3/snl.3 b/share/man/man3/snl.3 new file mode 100644 index 000000000000..9fdeeaf56178 --- /dev/null +++ b/share/man/man3/snl.3 @@ -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 . diff --git a/sys/netlink/netlink_domain.c b/sys/netlink/netlink_domain.c index f35cc7f09991..3b5e897164f8 100644 --- a/sys/netlink/netlink_domain.c +++ b/sys/netlink/netlink_domain.c @@ -76,7 +76,9 @@ SYSCTL_ULONG(_net_netlink, OID_AUTO, recvspace, CTLFLAG_RW, &nl_recvspace, 0, "Default netlink socket receive space"); extern u_long sb_max_adj; +#if 0 static u_long nl_maxsockbuf = 512 * 1024 * 1024; /* 512M, XXX: init based on physmem */ +#endif uint32_t nlp_get_pid(const struct nlpcb *nlp) @@ -671,6 +673,7 @@ nl_ctloutput(struct socket *so, struct sockopt *sopt) return (error); } +#if 0 static int nl_setsbopt(struct socket *so, struct sockopt *sopt) { @@ -697,32 +700,44 @@ nl_setsbopt(struct socket *so, struct sockopt *sopt) 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 = { .pr_type = SOCK_RAW, + .pr_domain = &netlinkdomain, + .pr_protocol = 0, // IPPROTO_UDP .pr_flags = PR_ATOMIC | PR_ADDR | PR_WANTRCVD, .pr_ctloutput = nl_ctloutput, - .pr_setsbopt = nl_setsbopt, - .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 + .pr_usrreqs = &nl_usrreqs, }; static struct domain netlinkdomain = { - .dom_family = PF_NETLINK, + .dom_family = AF_NETLINK, .dom_name = "netlink", +#ifdef DOMF_UNLOADABLE .dom_flags = DOMF_UNLOADABLE, - .dom_nprotosw = 1, - .dom_protosw = { &netlinksw }, +#endif + .dom_protosw = &netlinksw, + .dom_protoswNPROTOSW = (&netlinksw + 1), }; DOMAIN_SET(netlink); diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h new file mode 100644 index 000000000000..4a137b4e5d08 --- /dev/null +++ b/sys/netlink/netlink_snl.h @@ -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 diff --git a/sys/netlink/netlink_snl_route.h b/sys/netlink/netlink_snl_route.h new file mode 100644 index 000000000000..d281ec051513 --- /dev/null +++ b/sys/netlink/netlink_snl_route.h @@ -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); *** 120 LINES SKIPPED ***