aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander V. Chernikov <melifaro@FreeBSD.org>2022-12-18 17:34:41 +0000
committerAlexander V. Chernikov <melifaro@FreeBSD.org>2023-01-23 22:09:00 +0000
commitb309249b7fd8f29312be7cf08ccd5693d8409260 (patch)
treeef8d1dd25614d5b91ae8adb3d8a4594d046278e5
parent65f065ec7131db4028ed37d951d94d905298bc31 (diff)
downloadsrc-b309249b7fd8f29312be7cf08ccd5693d8409260.tar.gz
src-b309249b7fd8f29312be7cf08ccd5693d8409260.zip
netlink: add snl(3) - simple netlink library
Reviewed by: bapt, pauamma Differential Revision: https://reviews.freebsd.org/D37736 (cherry picked from commit f2c8381fce9b87695ea448591e4412cbed38aa77)
-rw-r--r--share/man/man3/snl.3303
-rw-r--r--sys/netlink/netlink_domain.c47
-rw-r--r--sys/netlink/netlink_snl.h435
-rw-r--r--sys/netlink/netlink_snl_route.h128
-rw-r--r--tests/sys/netlink/Makefile5
-rw-r--r--tests/sys/netlink/test_snl.c92
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);
+ }
+ return (false);
+}
+
+#endif
diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile
index d05965761f62..45095ac95309 100644
--- a/tests/sys/netlink/Makefile
+++ b/tests/sys/netlink/Makefile
@@ -5,9 +5,10 @@ WARNS?= 1
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_nl_core.py
ATF_TESTS_PYTEST += test_rtnl_iface.py
+ATF_TESTS_PYTEST += test_rtnl_ifaddr.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
index 000000000000..d917d81d967d
--- /dev/null
+++ b/tests/sys/netlink/test_snl.c
@@ -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());
+}