netlink: add snl(3) - simple netlink library
Reviewed by: bapt, pauamma Differential Revision: https://reviews.freebsd.org/D37736
This commit is contained in:
parent
5ba0691da9
commit
f2c8381fce
303
share/man/man3/snl.3
Normal file
303
share/man/man3/snl.3
Normal file
@ -0,0 +1,303 @@
|
||||
.\"
|
||||
.\" Copyright (C) 2022 Alexander Chernikov <melifaro@FreeBSD.org>.
|
||||
.\"
|
||||
.\" Redistribution and use in source and binary forms, with or without
|
||||
.\" modification, are permitted provided that the following conditions
|
||||
.\" are met:
|
||||
.\" 1. Redistributions of source code must retain the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer.
|
||||
.\" 2. Redistributions in binary form must reproduce the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer in the
|
||||
.\" documentation and/or other materials provided with the distribution.
|
||||
.\"
|
||||
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.Dd December 16, 2022
|
||||
.Dt SNL 3
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm snl_init ,
|
||||
.Nm snl_free ,
|
||||
.Nm snl_read_message ,
|
||||
.Nm snl_send ,
|
||||
.Nm snl_get_seq ,
|
||||
.Nm snl_allocz ,
|
||||
.Nm snl_clear_lb ,
|
||||
.Nm snl_parse_nlmsg ,
|
||||
.Nm snl_parse_header ,
|
||||
.Nm snl_parse_attrs ,
|
||||
.Nm snl_parse_attrs_raw ,
|
||||
.Nm snl_attr_get_flag ,
|
||||
.Nm snl_attr_get_ip ,
|
||||
.Nm snl_attr_get_uint16 ,
|
||||
.Nm snl_attr_get_uint32 ,
|
||||
.Nm snl_attr_get_string ,
|
||||
.Nm snl_attr_get_stringn ,
|
||||
.Nm snl_attr_get_nla ,
|
||||
.Nm snl_field_get_uint8 ,
|
||||
.Nm snl_field_get_uint16 ,
|
||||
.Nm snl_field_get_uint32
|
||||
.Nd "simple netlink library"
|
||||
.Sh SYNOPSIS
|
||||
.In netlink/netlink_snl.h
|
||||
.In netlink/netlink_snl_route.h
|
||||
.Ft "bool"
|
||||
.Fn snl_init "struct snl_state *ss" "int netlink_family"
|
||||
.Fn snl_free "struct snl_state *ss"
|
||||
.Ft "struct nlmsghdr *"
|
||||
.Fn snl_read_message "struct snl_state *ss"
|
||||
.Ft "bool"
|
||||
.Fn snl_send "struct snl_state *ss" "void *data" "int sz"
|
||||
.Ft "uint32_t"
|
||||
.Fn snl_get_seq "struct snl_state *ss"
|
||||
.Ft "void *"
|
||||
.Fn snl_allocz "struct snl_state *ss" "int len"
|
||||
.Fn snl_clear_lb "struct snl_state *ss"
|
||||
.Ft "bool"
|
||||
.Fn snl_parse_nlmsg "struct snl_state *ss" "struct nlmsghdr *hdr" "const struct snl_hdr_parser *ps" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_parse_header "struct snl_state *ss" "void *hdr" "int len" "const struct snl_hdr_parser *ps" "int pslen" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_parse_attrs "struct snl_state *ss" "struct nlmsghdr *hdr" "int hdrlen" "const struct snl_attr_parser *ps" "int pslen" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_parse_attrs_raw "struct snl_state *ss" "struct nlattr *nla_head" "int len" "const struct snl_attr_parser *ps" "int pslen" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_flag "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_uint16 "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_uint32 "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_string "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_stringn "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_nla "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_ip "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_ipvia "struct snl_state *ss" "struct nlattr *nla" "void *target"
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Xr snl 3
|
||||
library provides an easy way of sending and receiving Netlink messages,
|
||||
taking care of serialisation and deserialisation.
|
||||
.Ss INITIALISATION
|
||||
Call
|
||||
.Fn snl_init
|
||||
with a pointer to the
|
||||
.Dv struct snl_state
|
||||
and the desired Netlink family to initialise the library instance.
|
||||
To free the library instance, call
|
||||
.Fn snl_free .
|
||||
.Pp
|
||||
The library functions are NOT multithread-safe.
|
||||
If multithreading is desired, consider initializing an instance
|
||||
per thread.
|
||||
.Ss MEMORY ALLOCATION
|
||||
The library uses pre-allocated extendable memory buffers to handle message parsing.
|
||||
The typical usage pattern is to allocate the necessary data structures during the
|
||||
message parsing or writing process via
|
||||
.Fn snl_allocz
|
||||
and free all allocated data at once using
|
||||
.Fn snl_clear_lb
|
||||
after handling the message.
|
||||
.Ss COMPOSING AND SENDING MESSAGES
|
||||
The library does not currently offer any wrappers for writing netlink messages.
|
||||
Simple request messages can be composed by filling in all needed fields directly.
|
||||
Example for constructing an interface dump request:
|
||||
.Bd -literal
|
||||
struct {
|
||||
struct nlmsghdr hdr;
|
||||
struct ifinfomsg ifmsg;
|
||||
} msg = {
|
||||
.hdr.nlmsg_type = RTM_GETLINK,
|
||||
.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
|
||||
.hdr.nlmsg_seq = snl_get_seq(ss),
|
||||
};
|
||||
msg.hdr.nlmsg_len = sizeof(msg);
|
||||
.Ed
|
||||
.Fn snl_get_seq
|
||||
can be used to generate a unique message number.
|
||||
To send the resulting message,
|
||||
.Fn snl_send
|
||||
can be used.
|
||||
.Ss RECEIVING AND PARSING MESSAGES
|
||||
To receive a message, use
|
||||
.Fn snl_read_message .
|
||||
Currently, this call is blocking.
|
||||
.Pp
|
||||
The library provides an easy way to convert the message to the pre-defined C
|
||||
structure.
|
||||
For each message type, one needs to define rules, converting the protocol header
|
||||
fields and the desired attributes to the specified structure.
|
||||
It can be accomplished by using message parsers.
|
||||
Each message parser consists of an array of attribute getters and an array of
|
||||
header field getters.
|
||||
The former array needs to be sorted by the attribute type.
|
||||
There is a
|
||||
.Fn SNL_VERIFY_PARSERS
|
||||
macro to check if the order is correct.
|
||||
.Fn SNL_DECLARE_PARSER "parser_name" "family header type" "struct snl_field_parser[]" "struct snl_attr_parser[]"
|
||||
can be used to create a new parser.
|
||||
.Fn SNL_DECLARE_ATTR_PARSER "parser_name" "struct snl_field_parser[]"
|
||||
can be used to create an attribute-only message parser.
|
||||
.Pp
|
||||
Each attribute getter needs to be embedded in the following structure:
|
||||
.Bd -literal
|
||||
typedef bool snl_parse_attr_f(struct snl_state *ss, struct nlattr *attr, const void *arg, void *target);
|
||||
struct snl_attr_parser {
|
||||
uint16_t type; /* Attribute type */
|
||||
uint16_t off; /* field offset in the target structure */
|
||||
snl_parse_attr_f *cb; /* getter function to call */
|
||||
const void *arg; /* getter function custom argument */
|
||||
};
|
||||
.Ed
|
||||
The generic attribute getter has the following signature:
|
||||
.Ft "bool"
|
||||
.Fn snl_attr_get_<type> "struct snl_state *ss" "struct nlattr *nla" "const void *arg" "void *target" .
|
||||
nla contains the pointer of the attribute to use as the datasource.
|
||||
The target field is the pointer to the field in the target structure.
|
||||
It is up to the getter to know the type of the target field.
|
||||
The getter must check the input attribute and return
|
||||
false if the attribute is not formed correctly.
|
||||
Otherwise, the getter fetches the attribute value and stores it in the target,
|
||||
then returns true.
|
||||
It is possible to use
|
||||
.Fn snl_allocz
|
||||
to create the desired data structure .
|
||||
A number of predefined getters for the common data types exist.
|
||||
.Fn snl_attr_get_flag
|
||||
converts a flag-type attribute to an uint8_t value of 1 or 0, depending on the
|
||||
attribute presence.
|
||||
.Fn snl_attr_get_uint16
|
||||
stores a uint16_t type attribute into the uint16_t target field.
|
||||
.Fn snl_attr_get_uint32
|
||||
stores a uint32_t type attribute into the uint32_t target field.
|
||||
.Fn snl_attr_get_ip
|
||||
and
|
||||
.Fn snl_attr_get_ipvia
|
||||
stores a pointer to the sockaddr structure with the IPv4/IPv6 address contained
|
||||
in the attribute.
|
||||
Sockaddr is allocated using
|
||||
.Fn snl_allocz .
|
||||
.Fn snl_attr_get_string
|
||||
stores a pointer to the NULL-terminated string.
|
||||
The string itself is allocated using
|
||||
.Fn snl_allocz .
|
||||
.Fn snl_attr_get_nla
|
||||
stores a pointer to the specified attribute.
|
||||
.Fn snl_attr_get_stringn
|
||||
stores a pointer to the non-NULL-terminated string.
|
||||
.Pp
|
||||
Similarly, each family header getter needs to be embedded in the following structure:
|
||||
.Bd -literal
|
||||
typedef void snl_parse_field_f(struct snl_state *ss, void *hdr, void *target);
|
||||
struct snl_field_parser {
|
||||
uint16_t off_in; /* field offset in the input structure */
|
||||
uint16_t off_out;/* field offset in the target structure */
|
||||
snl_parse_field_f *cb; /* getter function to call */
|
||||
};
|
||||
.Ed
|
||||
The generic field getter has the following signature:
|
||||
.Ft "void"
|
||||
snl_field_get_<type> "struct snl_state *ss" "void *src" "void *target" .
|
||||
A number of pre-defined getters for the common data types exist.
|
||||
.Fn "snl_field_get_uint8"
|
||||
fetches an uint8_t value and stores it in the target.
|
||||
.Fn "snl_field_get_uint16"
|
||||
fetches an uint8_t value and stores it in the target.
|
||||
.Fn "snl_field_get_uint32"
|
||||
fetches an uint32_t value and stores it in the target.
|
||||
.Sh EXAMPLES
|
||||
The following example demonstrates how to list all system interfaces
|
||||
using netlink.
|
||||
.Bd -literal
|
||||
#include <stdio.h>
|
||||
|
||||
#include <netlink/netlink.h>
|
||||
#include <netlink/netlink_route.h>
|
||||
#include "netlink/netlink_snl.h"
|
||||
#include "netlink/netlink_snl_route.h"
|
||||
|
||||
struct nl_parsed_link {
|
||||
uint32_t ifi_index;
|
||||
uint32_t ifla_mtu;
|
||||
char *ifla_ifname;
|
||||
};
|
||||
|
||||
#define _IN(_field) offsetof(struct ifinfomsg, _field)
|
||||
#define _OUT(_field) offsetof(struct nl_parsed_link, _field)
|
||||
static const struct snl_attr_parser ap_link[] = {
|
||||
{ .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = snl_attr_get_string },
|
||||
{ .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = snl_attr_get_uint32 },
|
||||
};
|
||||
static const struct snl_field_parser fp_link[] = {
|
||||
{.off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = snl_field_get_uint32 },
|
||||
};
|
||||
#undef _IN
|
||||
#undef _OUT
|
||||
SNL_DECLARE_PARSER(link_parser, struct ifinfomsg, fp_link, ap_link);
|
||||
|
||||
|
||||
int
|
||||
main(int ac, char *argv[])
|
||||
{
|
||||
struct snl_state ss;
|
||||
|
||||
if (!snl_init(&ss, NETLINK_ROUTE))
|
||||
return (1);
|
||||
|
||||
struct {
|
||||
struct nlmsghdr hdr;
|
||||
struct ifinfomsg ifmsg;
|
||||
} msg = {
|
||||
.hdr.nlmsg_type = RTM_GETLINK,
|
||||
.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
|
||||
.hdr.nlmsg_seq = snl_get_seq(&ss),
|
||||
};
|
||||
msg.hdr.nlmsg_len = sizeof(msg);
|
||||
|
||||
if (!snl_send(&ss, &msg, sizeof(msg))) {
|
||||
snl_free(&ss);
|
||||
return (1);
|
||||
}
|
||||
|
||||
struct nlmsghdr *hdr;
|
||||
while ((hdr = snl_read_message(&ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) {
|
||||
if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq)
|
||||
break;
|
||||
|
||||
struct nl_parsed_link link = {};
|
||||
if (!snl_parse_nlmsg(&ss, hdr, &link_parser, &link))
|
||||
continue;
|
||||
printf("Link#%u %s mtu %u\n", link.ifi_index, link.ifla_ifname, link.ifla_mtu);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
.Ed
|
||||
.Sh SEE ALSO
|
||||
.Xr genetlink 4 ,
|
||||
.Xr netlink 4 ,
|
||||
and
|
||||
.Xr rtnetlink 4
|
||||
.Sh HISTORY
|
||||
The
|
||||
.Dv SNL
|
||||
library appeared in
|
||||
.Fx 14.0 .
|
||||
.Sh AUTHORS
|
||||
This library was implemented by
|
||||
.An Alexander Chernikov Aq Mt melifaro@FreeBSD.org .
|
435
sys/netlink/netlink_snl.h
Normal file
435
sys/netlink/netlink_snl.h
Normal file
@ -0,0 +1,435 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef _NETLINK_NETLINK_SNL_H_
|
||||
#define _NETLINK_NETLINK_SNL_H_
|
||||
|
||||
/*
|
||||
* Simple Netlink Library
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
#define _roundup2(x, y) (((x)+((y)-1))&(~((y)-1)))
|
||||
|
||||
#define NETLINK_ALIGN_SIZE sizeof(uint32_t)
|
||||
#define NETLINK_ALIGN(_len) _roundup2(_len, NETLINK_ALIGN_SIZE)
|
||||
|
||||
#define NLA_ALIGN_SIZE sizeof(uint32_t)
|
||||
#define NLA_HDRLEN ((int)sizeof(struct nlattr))
|
||||
#define NLA_DATA_LEN(_nla) ((int)((_nla)->nla_len - NLA_HDRLEN))
|
||||
#define NLA_DATA(_nla) NL_ITEM_DATA(_nla, NLA_HDRLEN)
|
||||
#define NLA_DATA_CONST(_nla) NL_ITEM_DATA_CONST(_nla, NLA_HDRLEN)
|
||||
|
||||
#define NLA_TYPE(_nla) ((_nla)->nla_type & 0x3FFF)
|
||||
|
||||
#define NLA_NEXT(_attr) (struct nlattr *)((char *)_attr + NLA_ALIGN(_attr->nla_len))
|
||||
|
||||
#define _NLA_END(_start, _len) ((char *)(_start) + (_len))
|
||||
#define NLA_FOREACH(_attr, _start, _len) \
|
||||
for (_attr = (_start); \
|
||||
((char *)_attr < _NLA_END(_start, _len)) && \
|
||||
((char *)NLA_NEXT(_attr) <= _NLA_END(_start, _len)); \
|
||||
_attr = NLA_NEXT(_attr))
|
||||
|
||||
#define NL_ARRAY_LEN(_a) (sizeof(_a) / sizeof((_a)[0]))
|
||||
|
||||
struct linear_buffer {
|
||||
char *base; /* Base allocated memory pointer */
|
||||
uint32_t offset; /* Currently used offset */
|
||||
uint32_t size; /* Total buffer size */
|
||||
};
|
||||
|
||||
static inline char *
|
||||
lb_allocz(struct linear_buffer *lb, int len)
|
||||
{
|
||||
len = roundup2(len, sizeof(uint64_t));
|
||||
if (lb->offset + len > lb->size)
|
||||
return (NULL);
|
||||
void *data = (void *)(lb->base + lb->offset);
|
||||
lb->offset += len;
|
||||
return (data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
lb_clear(struct linear_buffer *lb)
|
||||
{
|
||||
memset(lb->base, 0, lb->offset);
|
||||
lb->offset = 0;
|
||||
}
|
||||
|
||||
struct snl_state {
|
||||
int fd;
|
||||
char *buf;
|
||||
size_t off;
|
||||
size_t bufsize;
|
||||
size_t datalen;
|
||||
uint32_t seq;
|
||||
bool init_done;
|
||||
struct linear_buffer lb;
|
||||
};
|
||||
#define SCRATCH_BUFFER_SIZE 1024
|
||||
|
||||
typedef void snl_parse_field_f(struct snl_state *ss, void *hdr, void *target);
|
||||
struct snl_field_parser {
|
||||
uint16_t off_in;
|
||||
uint16_t off_out;
|
||||
snl_parse_field_f *cb;
|
||||
};
|
||||
|
||||
typedef bool snl_parse_attr_f(struct snl_state *ss, struct nlattr *attr,
|
||||
const void *arg, void *target);
|
||||
struct snl_attr_parser {
|
||||
uint16_t type; /* Attribute type */
|
||||
uint16_t off; /* field offset in the target structure */
|
||||
snl_parse_attr_f *cb; /* parser function to call */
|
||||
const void *arg; /* Optional argument parser */
|
||||
};
|
||||
|
||||
struct snl_hdr_parser {
|
||||
int hdr_off; /* aligned header size */
|
||||
int fp_size;
|
||||
int np_size;
|
||||
const struct snl_field_parser *fp; /* array of header field parsers */
|
||||
const struct snl_attr_parser *np; /* array of attribute parsers */
|
||||
};
|
||||
|
||||
#define SNL_DECLARE_PARSER(_name, _t, _fp, _np) \
|
||||
static const struct snl_hdr_parser _name = { \
|
||||
.hdr_off = sizeof(_t), \
|
||||
.fp = &((_fp)[0]), \
|
||||
.np = &((_np)[0]), \
|
||||
.fp_size = NL_ARRAY_LEN(_fp), \
|
||||
.np_size = NL_ARRAY_LEN(_np), \
|
||||
}
|
||||
|
||||
#define SNL_DECLARE_ATTR_PARSER(_name, _np) \
|
||||
static const struct snl_hdr_parser _name = { \
|
||||
.np = &((_np)[0]), \
|
||||
.np_size = NL_ARRAY_LEN(_np), \
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
snl_free(struct snl_state *ss)
|
||||
{
|
||||
if (ss->init_done) {
|
||||
close(ss->fd);
|
||||
if (ss->buf != NULL)
|
||||
free(ss->buf);
|
||||
if (ss->lb.base != NULL)
|
||||
free(ss->lb.base);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_init(struct snl_state *ss, int netlink_family)
|
||||
{
|
||||
memset(ss, 0, sizeof(*ss));
|
||||
|
||||
ss->fd = socket(AF_NETLINK, SOCK_RAW, netlink_family);
|
||||
if (ss->fd == -1)
|
||||
return (false);
|
||||
ss->init_done = true;
|
||||
|
||||
int rcvbuf;
|
||||
socklen_t optlen = sizeof(rcvbuf);
|
||||
if (getsockopt(ss->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) == -1) {
|
||||
snl_free(ss);
|
||||
return (false);
|
||||
}
|
||||
|
||||
ss->bufsize = rcvbuf;
|
||||
ss->buf = malloc(ss->bufsize);
|
||||
if (ss->buf == NULL) {
|
||||
snl_free(ss);
|
||||
return (false);
|
||||
}
|
||||
|
||||
ss->lb.size = SCRATCH_BUFFER_SIZE;
|
||||
ss->lb.base = calloc(1, ss->lb.size);
|
||||
if (ss->lb.base == NULL) {
|
||||
snl_free(ss);
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
static inline void *
|
||||
snl_allocz(struct snl_state *ss, int len)
|
||||
{
|
||||
return (lb_allocz(&ss->lb, len));
|
||||
}
|
||||
|
||||
static inline void
|
||||
snl_clear_lb(struct snl_state *ss)
|
||||
{
|
||||
lb_clear(&ss->lb);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_send(struct snl_state *ss, void *data, int sz)
|
||||
{
|
||||
return (send(ss->fd, data, sz, 0) == sz);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
snl_get_seq(struct snl_state *ss)
|
||||
{
|
||||
return (++ss->seq);
|
||||
}
|
||||
|
||||
static inline struct nlmsghdr *
|
||||
snl_read_message(struct snl_state *ss)
|
||||
{
|
||||
if (ss->off == ss->datalen) {
|
||||
struct sockaddr_nl nladdr;
|
||||
struct iovec iov = {
|
||||
.iov_base = ss->buf,
|
||||
.iov_len = ss->bufsize,
|
||||
};
|
||||
struct msghdr msg = {
|
||||
.msg_name = &nladdr,
|
||||
.msg_namelen = sizeof(nladdr),
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
ss->off = 0;
|
||||
ss->datalen = 0;
|
||||
for (;;) {
|
||||
ssize_t datalen = recvmsg(ss->fd, &msg, 0);
|
||||
if (datalen > 0) {
|
||||
ss->datalen = datalen;
|
||||
break;
|
||||
} else if (errno != EINTR)
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
struct nlmsghdr *hdr = (struct nlmsghdr *)&ss->buf[ss->off];
|
||||
ss->off += NLMSG_ALIGN(hdr->nlmsg_len);
|
||||
return (hdr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks that attributes are sorted by attribute type.
|
||||
*/
|
||||
static inline void
|
||||
snl_verify_parsers(const struct snl_hdr_parser **parser, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++) {
|
||||
const struct snl_hdr_parser *p = parser[i];
|
||||
int attr_type = 0;
|
||||
for (int j = 0; j < p->np_size; j++) {
|
||||
assert(p->np[j].type > attr_type);
|
||||
attr_type = p->np[j].type;
|
||||
}
|
||||
}
|
||||
}
|
||||
#define SNL_VERIFY_PARSERS(_p) snl_verify_parsers((_p), NL_ARRAY_LEN(_p))
|
||||
|
||||
static const struct snl_attr_parser *
|
||||
find_parser(const struct snl_attr_parser *ps, int pslen, int key)
|
||||
{
|
||||
int left_i = 0, right_i = pslen - 1;
|
||||
|
||||
if (key < ps[0].type || key > ps[pslen - 1].type)
|
||||
return (NULL);
|
||||
|
||||
while (left_i + 1 < right_i) {
|
||||
int mid_i = (left_i + right_i) / 2;
|
||||
if (key < ps[mid_i].type)
|
||||
right_i = mid_i;
|
||||
else if (key > ps[mid_i].type)
|
||||
left_i = mid_i + 1;
|
||||
else
|
||||
return (&ps[mid_i]);
|
||||
}
|
||||
if (ps[left_i].type == key)
|
||||
return (&ps[left_i]);
|
||||
else if (ps[right_i].type == key)
|
||||
return (&ps[right_i]);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_parse_attrs_raw(struct snl_state *ss, struct nlattr *nla_head, int len,
|
||||
const struct snl_attr_parser *ps, int pslen, void *target)
|
||||
{
|
||||
struct nlattr *nla;
|
||||
|
||||
NLA_FOREACH(nla, nla_head, len) {
|
||||
if (nla->nla_len < sizeof(struct nlattr))
|
||||
return (false);
|
||||
int nla_type = nla->nla_type & NLA_TYPE_MASK;
|
||||
const struct snl_attr_parser *s = find_parser(ps, pslen, nla_type);
|
||||
if (s != NULL) {
|
||||
void *ptr = (void *)((char *)target + s->off);
|
||||
if (!s->cb(ss, nla, s->arg, ptr))
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_parse_attrs(struct snl_state *ss, struct nlmsghdr *hdr, int hdrlen,
|
||||
const struct snl_attr_parser *ps, int pslen, void *target)
|
||||
{
|
||||
int off = NLMSG_HDRLEN + NETLINK_ALIGN(hdrlen);
|
||||
int len = hdr->nlmsg_len - off;
|
||||
struct nlattr *nla_head = (struct nlattr *)((char *)hdr + off);
|
||||
|
||||
return (snl_parse_attrs_raw(ss, nla_head, len, ps, pslen, target));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_parse_header(struct snl_state *ss, void *hdr, int len,
|
||||
const struct snl_hdr_parser *parser, void *target)
|
||||
{
|
||||
/* Extract fields first (if any) */
|
||||
for (int i = 0; i < parser->fp_size; i++) {
|
||||
const struct snl_field_parser *fp = &parser->fp[i];
|
||||
void *src = (char *)hdr + fp->off_in;
|
||||
void *dst = (char *)target + fp->off_out;
|
||||
|
||||
fp->cb(ss, src, dst);
|
||||
}
|
||||
|
||||
struct nlattr *nla_head = (struct nlattr *)((char *)hdr + parser->hdr_off);
|
||||
bool result = snl_parse_attrs_raw(ss, nla_head, len - parser->hdr_off,
|
||||
parser->np, parser->np_size, target);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_parse_nlmsg(struct snl_state *ss, struct nlmsghdr *hdr,
|
||||
const struct snl_hdr_parser *parser, void *target)
|
||||
{
|
||||
return (snl_parse_header(ss, hdr + 1, hdr->nlmsg_len - sizeof(*hdr), parser, target));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_flag(struct snl_state *ss, struct nlattr *nla, void *target)
|
||||
{
|
||||
if (NLA_DATA_LEN(nla) == 0) {
|
||||
*((uint8_t *)target) = 1;
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_uint16(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||
{
|
||||
if (NLA_DATA_LEN(nla) == sizeof(uint16_t)) {
|
||||
*((uint16_t *)target) = *((const uint16_t *)NL_RTA_DATA_CONST(nla));
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_uint32(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||
{
|
||||
if (NLA_DATA_LEN(nla) == sizeof(uint32_t)) {
|
||||
*((uint32_t *)target) = *((const uint32_t *)NL_RTA_DATA_CONST(nla));
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_string(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||
{
|
||||
size_t maxlen = NLA_DATA_LEN(nla);
|
||||
|
||||
if (strnlen((char *)NLA_DATA(nla), maxlen) < maxlen) {
|
||||
*((char **)target) = (char *)NLA_DATA(nla);
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_stringn(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||
{
|
||||
int maxlen = NLA_DATA_LEN(nla);
|
||||
|
||||
char *buf = snl_allocz(ss, maxlen + 1);
|
||||
if (buf == NULL)
|
||||
return (false);
|
||||
buf[maxlen] = '\0';
|
||||
memcpy(buf, NLA_DATA(nla), maxlen);
|
||||
|
||||
*((char **)target) = buf;
|
||||
return (true);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_nested(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||
{
|
||||
const struct snl_hdr_parser *p = (const struct snl_hdr_parser *)arg;
|
||||
|
||||
/* Assumes target points to the beginning of the structure */
|
||||
return (snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), p, target));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_nla(struct snl_state *ss, struct nlattr *nla, void *target)
|
||||
{
|
||||
*((struct nlattr **)target) = nla;
|
||||
return (true);
|
||||
}
|
||||
|
||||
static inline void
|
||||
snl_field_get_uint8(struct snl_state *ss, void *src, void *target)
|
||||
{
|
||||
*((uint8_t *)target) = *((uint8_t *)src);
|
||||
}
|
||||
|
||||
static inline void
|
||||
snl_field_get_uint16(struct snl_state *ss, void *src, void *target)
|
||||
{
|
||||
*((uint16_t *)target) = *((uint16_t *)src);
|
||||
}
|
||||
|
||||
static inline void
|
||||
snl_field_get_uint32(struct snl_state *ss, void *src, void *target)
|
||||
{
|
||||
*((uint32_t *)target) = *((uint32_t *)src);
|
||||
}
|
||||
|
||||
#endif
|
128
sys/netlink/netlink_snl_route.h
Normal file
128
sys/netlink/netlink_snl_route.h
Normal file
@ -0,0 +1,128 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef _NETLINK_NETLINK_SNL_ROUTE_H_
|
||||
#define _NETLINK_NETLINK_SNL_ROUTE_H_
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
/*
|
||||
* Simple Netlink Library - NETLINK_ROUTE helpers
|
||||
*/
|
||||
|
||||
#define snl_alloc_sockaddr(_ss, _len) ((struct sockaddr *)(snl_allocz(_ss, _len)))
|
||||
|
||||
static inline struct sockaddr *
|
||||
parse_rta_ip4(struct snl_state *ss, void *rta_data, int *perror)
|
||||
{
|
||||
struct sockaddr_in *sin;
|
||||
|
||||
sin = (struct sockaddr_in *)snl_alloc_sockaddr(ss, sizeof(struct sockaddr_in));
|
||||
if (sin == NULL) {
|
||||
*perror = ENOBUFS;
|
||||
return (NULL);
|
||||
}
|
||||
sin->sin_len = sizeof(struct sockaddr_in);
|
||||
sin->sin_family = AF_INET;
|
||||
memcpy(&sin->sin_addr, rta_data, sizeof(struct in_addr));
|
||||
return ((struct sockaddr *)sin);
|
||||
}
|
||||
|
||||
static inline struct sockaddr *
|
||||
parse_rta_ip6(struct snl_state *ss, void *rta_data, int *perror)
|
||||
{
|
||||
struct sockaddr_in6 *sin6;
|
||||
|
||||
sin6 = (struct sockaddr_in6 *)snl_alloc_sockaddr(ss, sizeof(struct sockaddr_in6));
|
||||
if (sin6 == NULL) {
|
||||
*perror = ENOBUFS;
|
||||
return (NULL);
|
||||
}
|
||||
sin6->sin6_len = sizeof(struct sockaddr_in6);
|
||||
sin6->sin6_family = AF_INET6;
|
||||
memcpy(&sin6->sin6_addr, rta_data, sizeof(struct in6_addr));
|
||||
return ((struct sockaddr *)sin6);
|
||||
}
|
||||
|
||||
static inline struct sockaddr *
|
||||
parse_rta_ip(struct snl_state *ss, struct rtattr *rta, int *perror)
|
||||
{
|
||||
void *rta_data = NL_RTA_DATA(rta);
|
||||
int rta_len = NL_RTA_DATA_LEN(rta);
|
||||
|
||||
if (rta_len == sizeof(struct in_addr)) {
|
||||
return (parse_rta_ip4(ss, rta_data, perror));
|
||||
} else if (rta_len == sizeof(struct in6_addr)) {
|
||||
return (parse_rta_ip6(ss, rta_data, perror));
|
||||
} else {
|
||||
*perror = ENOTSUP;
|
||||
return (NULL);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_ip(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||
{
|
||||
int error = 0;
|
||||
struct sockaddr *sa = parse_rta_ip(ss, (struct rtattr *)nla, &error);
|
||||
if (error == 0) {
|
||||
*((struct sockaddr **)target) = sa;
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static inline struct sockaddr *
|
||||
parse_rta_via(struct snl_state *ss, struct rtattr *rta, int *perror)
|
||||
{
|
||||
struct rtvia *via = NL_RTA_DATA(rta);
|
||||
|
||||
switch (via->rtvia_family) {
|
||||
case AF_INET:
|
||||
return (parse_rta_ip4(ss, via->rtvia_addr, perror));
|
||||
case AF_INET6:
|
||||
return (parse_rta_ip6(ss, via->rtvia_addr, perror));
|
||||
default:
|
||||
*perror = ENOTSUP;
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snl_attr_get_ipvia(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
struct sockaddr *sa = parse_rta_via(ss, (struct rtattr *)nla, &error);
|
||||
if (error == 0) {
|
||||
*((struct sockaddr **)target) = sa;
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
#endif
|
@ -5,8 +5,7 @@ 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_rtnl_iface.py
|
||||
|
||||
CFLAGS+= -I${.CURDIR:H:H:H}
|
||||
|
92
tests/sys/netlink/test_snl.c
Normal file
92
tests/sys/netlink/test_snl.c
Normal file
@ -0,0 +1,92 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/module.h>
|
||||
|
||||
#include <netlink/netlink.h>
|
||||
#include <netlink/netlink_route.h>
|
||||
#include "netlink/netlink_snl.h"
|
||||
#include "netlink/netlink_snl_route.h"
|
||||
|
||||
#include <atf-c.h>
|
||||
|
||||
static void
|
||||
require_netlink(void)
|
||||
{
|
||||
if (modfind("netlink") == -1)
|
||||
atf_tc_skip("netlink module not loaded");
|
||||
}
|
||||
|
||||
|
||||
ATF_TC(snl_list_ifaces);
|
||||
ATF_TC_HEAD(snl_list_ifaces, tc)
|
||||
{
|
||||
atf_tc_set_md_var(tc, "descr", "Tests snl(3) listing interfaces");
|
||||
}
|
||||
|
||||
struct nl_parsed_link {
|
||||
uint32_t ifi_index;
|
||||
uint32_t ifla_mtu;
|
||||
char *ifla_ifname;
|
||||
};
|
||||
|
||||
#define _IN(_field) offsetof(struct ifinfomsg, _field)
|
||||
#define _OUT(_field) offsetof(struct nl_parsed_link, _field)
|
||||
static struct snl_attr_parser ap_link[] = {
|
||||
{ .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = snl_attr_get_string },
|
||||
{ .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = snl_attr_get_uint32 },
|
||||
};
|
||||
static struct snl_field_parser fp_link[] = {
|
||||
{.off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = snl_field_get_uint32 },
|
||||
};
|
||||
#undef _IN
|
||||
#undef _OUT
|
||||
SNL_DECLARE_PARSER(link_parser, struct ifinfomsg, fp_link, ap_link);
|
||||
|
||||
|
||||
ATF_TC_BODY(snl_list_ifaces, tc)
|
||||
{
|
||||
struct snl_state ss;
|
||||
|
||||
require_netlink();
|
||||
|
||||
if (!snl_init(&ss, NETLINK_ROUTE))
|
||||
atf_tc_fail("snl_init() failed");
|
||||
|
||||
struct {
|
||||
struct nlmsghdr hdr;
|
||||
struct ifinfomsg ifmsg;
|
||||
} msg = {
|
||||
.hdr.nlmsg_type = RTM_GETLINK,
|
||||
.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
|
||||
.hdr.nlmsg_seq = snl_get_seq(&ss),
|
||||
};
|
||||
msg.hdr.nlmsg_len = sizeof(msg);
|
||||
|
||||
if (!snl_send(&ss, &msg, sizeof(msg))) {
|
||||
snl_free(&ss);
|
||||
atf_tc_fail("snl_send() failed");
|
||||
}
|
||||
|
||||
struct nlmsghdr *hdr;
|
||||
int count = 0;
|
||||
while ((hdr = snl_read_message(&ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) {
|
||||
if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq)
|
||||
continue;
|
||||
|
||||
struct nl_parsed_link link = {};
|
||||
if (!snl_parse_nlmsg(&ss, hdr, &link_parser, &link))
|
||||
continue;
|
||||
count++;
|
||||
}
|
||||
ATF_REQUIRE_MSG(count > 0, "Empty interface list");
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, snl_list_ifaces);
|
||||
|
||||
return (atf_no_error());
|
||||
}
|
Loading…
Reference in New Issue
Block a user