diff --git a/share/man/man3/snl.3 b/share/man/man3/snl.3 new file mode 100644 --- /dev/null +++ b/share/man/man3/snl.3 @@ -0,0 +1,303 @@ +.\" +.\" Copyright (C) 2022 Alexander Chernikov . +.\" +.\" 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_ "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_ "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 + +#include +#include +#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_snl.h b/sys/netlink/netlink_snl.h new file mode 100644 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + + +#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 --- /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 + * + * 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 + +/* + * 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 diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile --- a/tests/sys/netlink/Makefile +++ b/tests/sys/netlink/Makefile @@ -5,8 +5,7 @@ TESTSDIR= ${TESTSBASE}/sys/netlink -#ATF_TESTS_C += test_rtsock_l3 -#ATF_TESTS_C += test_rtsock_lladdr +ATF_TESTS_C += test_snl ATF_TESTS_PYTEST += test_rtnl_iface.py CFLAGS+= -I${.CURDIR:H:H:H} diff --git a/tests/sys/netlink/test_snl.c b/tests/sys/netlink/test_snl.c new file mode 100644 --- /dev/null +++ b/tests/sys/netlink/test_snl.c @@ -0,0 +1,92 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include "netlink/netlink_snl.h" +#include "netlink/netlink_snl_route.h" + +#include + +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()); +}