sd-netlink: add generic netlink support

This also adds the ability to incorporate arrays into netlink messages
and to determine when a netlink message is too big, used by some generic
netlink protocols.
This commit is contained in:
Jörg Thalheim 2017-12-18 15:17:06 +01:00 committed by Zbigniew Jędrzejewski-Szmek
parent 8481e3e71e
commit 05d0c2e3cf
15 changed files with 299 additions and 28 deletions

View file

@ -74,6 +74,7 @@ libsystemd_internal_sources = files('''
sd-id128/id128-util.c
sd-id128/id128-util.h
sd-id128/sd-id128.c
sd-netlink/generic-netlink.c
sd-netlink/local-addresses.c
sd-netlink/local-addresses.h
sd-netlink/netlink-internal.h

View file

@ -0,0 +1,96 @@
#include <linux/genetlink.h>
#include "sd-netlink.h"
#include "netlink-internal.h"
#include "alloc-util.h"
typedef struct {
const char* name;
uint8_t version;
} genl_family;
static const genl_family genl_families[] = {
[SD_GENL_ID_CTRL] = { .name = "", .version = 1 },
};
int sd_genl_socket_open(sd_netlink **ret) {
return netlink_open_family(ret, NETLINK_GENERIC);
}
static int lookup_id(sd_netlink *nl, sd_genl_family family, uint16_t *id);
static int genl_message_new(sd_netlink *nl, sd_genl_family family, uint16_t nlmsg_type, uint8_t cmd, sd_netlink_message **ret) {
int r;
struct genlmsghdr *genl;
const NLType *genl_cmd_type, *nl_type;
const NLTypeSystem *type_system;
size_t size;
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
assert_return(nl->protocol == NETLINK_GENERIC, -EINVAL);
r = type_system_get_type(&genl_family_type_system_root, &genl_cmd_type, family);
if (r < 0)
return r;
r = message_new_empty(nl, &m);
if (r < 0)
return r;
size = NLMSG_SPACE(sizeof(struct genlmsghdr));
m->hdr = malloc0(size);
if (!m->hdr)
return -ENOMEM;
m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
type_get_type_system(genl_cmd_type, &type_system);
r = type_system_get_type(type_system, &nl_type, cmd);
if (r < 0)
return r;
m->hdr->nlmsg_len = size;
m->hdr->nlmsg_type = nlmsg_type;
type_get_type_system(nl_type, &m->containers[0].type_system);
genl = NLMSG_DATA(m->hdr);
genl->cmd = cmd;
genl->version = genl_families[family].version;
*ret = m;
m = NULL;
return 0;
}
int sd_genl_message_new(sd_netlink *nl, sd_genl_family family, uint8_t cmd, sd_netlink_message **ret) {
int r;
uint16_t id = GENL_ID_CTRL;
if (family != SD_GENL_ID_CTRL) {
r = lookup_id(nl, family, &id);
if (r < 0)
return r;
}
return genl_message_new(nl, family, id, cmd, ret);
}
static int lookup_id(sd_netlink *nl, sd_genl_family family, uint16_t *id) {
int r;
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
r = sd_genl_message_new(nl, SD_GENL_ID_CTRL, CTRL_CMD_GETFAMILY, &req);
if (r < 0)
return r;
r = sd_netlink_message_append_string(req, CTRL_ATTR_FAMILY_NAME, genl_families[family].name);
if (r < 0)
return r;
r = sd_netlink_call(nl, req, 0, &reply);
if (r < 0)
return r;
return sd_netlink_message_read_u16(reply, CTRL_ATTR_FAMILY_ID, id);
}

View file

@ -62,6 +62,8 @@ struct sd_netlink {
struct sockaddr_nl nl;
} sockaddr;
int protocol;
Hashmap *broadcast_group_refs;
bool broadcast_group_dont_leave:1; /* until we can rely on 4.2 */
@ -111,6 +113,8 @@ struct sd_netlink_message {
sd_netlink *rtnl;
int protocol;
struct nlmsghdr *hdr;
struct netlink_container containers[RTNL_CONTAINER_DEPTH];
unsigned n_containers; /* number of containers */
@ -123,6 +127,8 @@ struct sd_netlink_message {
int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type);
int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret);
int netlink_open_family(sd_netlink **ret, int family);
int socket_open(int family);
int socket_bind(sd_netlink *nl);
int socket_broadcast_group_ref(sd_netlink *nl, unsigned group);

View file

@ -55,7 +55,7 @@ int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) {
return -ENOMEM;
m->n_ref = REFCNT_INIT;
m->protocol = rtnl->protocol;
m->sealed = false;
*ret = m;
@ -66,10 +66,15 @@ int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) {
int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
const NLType *nl_type;
const NLTypeSystem *type_system_root;
size_t size;
int r;
r = type_system_get_type(&type_system_root, &nl_type, type);
assert_return(rtnl, -EINVAL);
type_system_root = type_system_get_root(rtnl->protocol);
r = type_system_get_type(type_system_root, &nl_type, type);
if (r < 0)
return r;
@ -186,6 +191,10 @@ static int add_rtattr(sd_netlink_message *m, unsigned short type, const void *da
/* get the new message size (with padding at the end) */
message_length = offset + RTA_ALIGN(rta_length);
/* buffer should be smaller than both one page or 8K to be accepted by the kernel */
if (message_length > MIN(page_size(), 8192UL))
return -ENOBUFS;
/* realloc to fit the new attribute */
new_hdr = realloc(m->hdr, message_length);
if (!new_hdr)
@ -490,7 +499,7 @@ int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned shor
if (r < 0)
return r;
/* do we evere need non-null size */
/* do we ever need non-null size */
r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0);
if (r < 0)
return r;
@ -500,18 +509,57 @@ int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned shor
return 0;
}
int sd_netlink_message_close_container(sd_netlink_message *m) {
assert_return(m, -EINVAL);
assert_return(!m->sealed, -EPERM);
assert_return(m->n_containers > 0, -EINVAL);
m->containers[m->n_containers].type_system = NULL;
m->containers[m->n_containers].offset = 0;
m->n_containers--;
return 0;
}
int sd_netlink_message_open_array(sd_netlink_message *m, uint16_t type) {
int r;
assert_return(m, -EINVAL);
assert_return(!m->sealed, -EPERM);
assert_return(m->n_containers > 0, -EINVAL);
r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0);
if (r < 0)
return r;
m->containers[m->n_containers].offset = r;
m->n_containers++;
m->containers[m->n_containers].type_system = m->containers[m->n_containers - 1].type_system;
return 0;
}
int sd_netlink_message_cancel_array(sd_netlink_message *m) {
unsigned i;
uint32_t rta_len;
assert_return(m, -EINVAL);
assert_return(!m->sealed, -EPERM);
assert_return(m->n_containers > 1, -EINVAL);
rta_len = GET_CONTAINER(m, (m->n_containers - 1))->rta_len;
for (i = 0; i < m->n_containers; i++)
GET_CONTAINER(m, i)->rta_len -= rta_len;
m->hdr->nlmsg_len -= rta_len;
m->n_containers--;
m->containers[m->n_containers].type_system = NULL;
return 0;
}
static int netlink_message_read_internal(sd_netlink_message *m, unsigned short type, void **data, bool *net_byteorder) {
struct netlink_attribute *attribute;
struct rtattr *rta;
@ -899,6 +947,7 @@ int sd_netlink_message_get_errno(sd_netlink_message *m) {
int sd_netlink_message_rewind(sd_netlink_message *m) {
const NLType *nl_type;
const NLTypeSystem *type_system_root;
uint16_t type;
size_t size;
unsigned i;
@ -910,6 +959,8 @@ int sd_netlink_message_rewind(sd_netlink_message *m) {
if (!m->sealed)
rtnl_message_seal(m);
type_system_root = type_system_get_root(m->protocol);
for (i = 1; i <= m->n_containers; i++)
m->containers[i].attributes = mfree(m->containers[i].attributes);
@ -921,7 +972,7 @@ int sd_netlink_message_rewind(sd_netlink_message *m) {
assert(m->hdr);
r = type_system_get_type(&type_system_root, &nl_type, m->hdr->nlmsg_type);
r = type_system_get_type(type_system_root, &nl_type, m->hdr->nlmsg_type);
if (r < 0)
return r;

View file

@ -330,11 +330,14 @@ int socket_read_message(sd_netlink *rtnl) {
size_t len;
int r;
unsigned i = 0;
const NLTypeSystem *type_system_root;
assert(rtnl);
assert(rtnl->rbuffer);
assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr));
type_system_root = type_system_get_root(rtnl->protocol);
/* read nothing, just get the pending message size */
r = socket_recv_message(rtnl->fd, &iov, NULL, true);
if (r <= 0)
@ -396,7 +399,8 @@ int socket_read_message(sd_netlink *rtnl) {
}
/* check that we support this message type */
r = type_system_get_type(&type_system_root, &nl_type, new_msg->nlmsg_type);
r = type_system_get_type(type_system_root, &nl_type, new_msg->nlmsg_type);
if (r < 0) {
if (r == -EOPNOTSUPP)
log_debug("sd-netlink: ignored message with unknown type: %i",

View file

@ -36,6 +36,7 @@
#include <linux/if_link.h>
#include <linux/if_tunnel.h>
#include <linux/fib_rules.h>
#include <linux/genetlink.h>
#if HAVE_VXCAN_INFO_PEER
#include <linux/can/vxcan.h>
@ -46,6 +47,7 @@
#include "netlink-types.h"
#include "string-table.h"
#include "util.h"
#include "sd-netlink.h"
/* Maximum ARP IP target defined in kernel */
#define BOND_MAX_ARP_TARGETS 16
@ -665,11 +667,49 @@ static const NLType rtnl_types[] = {
[RTM_GETRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) },
};
const NLTypeSystem type_system_root = {
const NLTypeSystem rtnl_type_system_root = {
.count = ELEMENTSOF(rtnl_types),
.types = rtnl_types,
};
static const NLType genl_get_family_types[] = {
[CTRL_ATTR_FAMILY_NAME] = { .type = NETLINK_TYPE_STRING },
[CTRL_ATTR_FAMILY_ID] = { .type = NETLINK_TYPE_U16 },
};
static const NLTypeSystem genl_get_family_type_system = {
.count = ELEMENTSOF(genl_get_family_types),
.types = genl_get_family_types,
};
static const NLType genl_ctrl_id_ctrl_cmds[] = {
[CTRL_CMD_GETFAMILY] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_get_family_type_system },
};
static const NLTypeSystem genl_ctrl_id_ctrl_type_system = {
.count = ELEMENTSOF(genl_ctrl_id_ctrl_cmds),
.types = genl_ctrl_id_ctrl_cmds,
};
static const NLType genl_families[] = {
[SD_GENL_ID_CTRL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_ctrl_id_ctrl_type_system },
};
const NLTypeSystem genl_family_type_system_root = {
.count = ELEMENTSOF(genl_families),
.types = genl_families,
};
static const NLType genl_types[] = {
[GENL_ID_CTRL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_get_family_type_system, .size = sizeof(struct genlmsghdr) },
};
const NLTypeSystem genl_type_system_root = {
.count = ELEMENTSOF(genl_types),
.types = genl_types,
};
uint16_t type_get_type(const NLType *type) {
assert(type);
return type->type;
@ -703,6 +743,15 @@ uint16_t type_system_get_count(const NLTypeSystem *type_system) {
return type_system->count;
}
const NLTypeSystem *type_system_get_root(int protocol) {
switch (protocol) {
case NETLINK_GENERIC:
return &genl_type_system_root;
default: /* NETLINK_ROUTE: */
return &rtnl_type_system_root;
}
}
int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type) {
const NLType *nl_type;

View file

@ -54,13 +54,16 @@ struct NLTypeSystemUnion {
const NLTypeSystem *type_systems;
};
extern const NLTypeSystem type_system_root;
extern const NLTypeSystem rtnl_type_system_root;
extern const NLTypeSystem genl_type_system_root;
extern const NLTypeSystem genl_family_type_system_root;
uint16_t type_get_type(const NLType *type);
size_t type_get_size(const NLType *type);
void type_get_type_system(const NLType *type, const NLTypeSystem **ret);
void type_get_type_system_union(const NLType *type, const NLTypeSystemUnion **ret);
const NLTypeSystem* type_system_get_root(int protocol);
uint16_t type_system_get_count(const NLTypeSystem *type_system);
int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type);
int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type);

View file

@ -98,13 +98,13 @@ int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias,
return 0;
}
int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret) {
int rtnl_message_new_synthetic_error(sd_netlink *rtnl, int error, uint32_t serial, sd_netlink_message **ret) {
struct nlmsgerr *err;
int r;
assert(error <= 0);
r = message_new(NULL, ret, NLMSG_ERROR);
r = message_new(rtnl, ret, NLMSG_ERROR);
if (r < 0)
return r;

View file

@ -24,7 +24,7 @@
#include "util.h"
int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret);
int rtnl_message_new_synthetic_error(sd_netlink *rtnl, int error, uint32_t serial, sd_netlink_message **ret);
uint32_t rtnl_message_get_serial(sd_netlink_message *m);
void rtnl_message_seal(sd_netlink_message *m);

View file

@ -46,6 +46,7 @@ static int sd_netlink_new(sd_netlink **ret) {
rtnl->fd = -1;
rtnl->sockaddr.nl.nl_family = AF_NETLINK;
rtnl->original_pid = getpid_cached();
rtnl->protocol = -1;
LIST_HEAD_INIT(rtnl->match_callbacks);
@ -106,6 +107,8 @@ static bool rtnl_pid_changed(sd_netlink *rtnl) {
int sd_netlink_open_fd(sd_netlink **ret, int fd) {
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
int r;
int protocol;
socklen_t l;
assert_return(ret, -EINVAL);
assert_return(fd >= 0, -EBADF);
@ -114,11 +117,18 @@ int sd_netlink_open_fd(sd_netlink **ret, int fd) {
if (r < 0)
return r;
l = sizeof(protocol);
r = getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &protocol, &l);
if (r < 0)
return r;
rtnl->fd = fd;
rtnl->protocol = protocol;
r = socket_bind(rtnl);
if (r < 0) {
rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */
rtnl->protocol = -1;
return r;
}
@ -128,11 +138,11 @@ int sd_netlink_open_fd(sd_netlink **ret, int fd) {
return 0;
}
int sd_netlink_open(sd_netlink **ret) {
int netlink_open_family(sd_netlink **ret, int family) {
_cleanup_close_ int fd = -1;
int r;
fd = socket_open(NETLINK_ROUTE);
fd = socket_open(family);
if (fd < 0)
return fd;
@ -145,6 +155,10 @@ int sd_netlink_open(sd_netlink **ret) {
return 0;
}
int sd_netlink_open(sd_netlink **ret) {
return netlink_open_family(ret, NETLINK_ROUTE);
}
int sd_netlink_inc_rcvbuf(sd_netlink *rtnl, size_t size) {
assert_return(rtnl, -EINVAL);
assert_return(!rtnl_pid_changed(rtnl), -ECHILD);
@ -309,7 +323,7 @@ static int process_timeout(sd_netlink *rtnl) {
if (c->timeout > n)
return 0;
r = rtnl_message_new_synthetic_error(-ETIMEDOUT, c->serial, &m);
r = rtnl_message_new_synthetic_error(rtnl, -ETIMEDOUT, c->serial, &m);
if (r < 0)
return r;

View file

@ -143,13 +143,13 @@ static void test_address_get(sd_netlink *rtnl, int ifindex) {
}
static void test_route(void) {
static void test_route(sd_netlink *rtnl) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req;
struct in_addr addr, addr_data;
uint32_t index = 2, u32_data;
int r;
r = sd_rtnl_message_new_route(NULL, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC);
r = sd_rtnl_message_new_route(rtnl, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC);
if (r < 0) {
log_error_errno(r, "Could not create RTM_NEWROUTE message: %m");
return;
@ -291,13 +291,13 @@ static void test_pipe(int ifindex) {
assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL);
}
static void test_container(void) {
static void test_container(sd_netlink *rtnl) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
uint16_t u16_data;
uint32_t u32_data;
const char *string_data;
assert_se(sd_rtnl_message_new_link(NULL, &m, RTM_NEWLINK, 0) >= 0);
assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0) >= 0);
assert_se(sd_netlink_message_open_container(m, IFLA_LINKINFO) >= 0);
assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "vlan") >= 0);
@ -369,10 +369,10 @@ static void test_get_addresses(sd_netlink *rtnl) {
}
}
static void test_message(void) {
static void test_message(sd_netlink *rtnl) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
assert_se(rtnl_message_new_synthetic_error(-ETIMEDOUT, 1, &m) >= 0);
assert_se(rtnl_message_new_synthetic_error(rtnl, -ETIMEDOUT, 1, &m) >= 0);
assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT);
}
@ -384,19 +384,19 @@ int main(void) {
int if_loopback;
uint16_t type;
test_message();
test_match();
test_multiple();
test_route();
test_container();
assert_se(sd_netlink_open(&rtnl) >= 0);
assert_se(rtnl);
test_route(rtnl);
test_message(rtnl);
test_container(rtnl);
if_loopback = (int) if_nametoindex("lo");
assert_se(if_loopback > 0);

View file

@ -114,7 +114,7 @@ static void netdev_cancel_callbacks(NetDev *netdev) {
if (!netdev)
return;
rtnl_message_new_synthetic_error(-ENODEV, 0, &m);
rtnl_message_new_synthetic_error(netdev->manager->rtnl, -ENODEV, 0, &m);
while ((callback = netdev->callbacks)) {
if (m) {
@ -322,7 +322,7 @@ int netdev_enslave(NetDev *netdev, Link *link, sd_netlink_message_handler_t call
} else if (IN_SET(netdev->state, NETDEV_STATE_LINGER, NETDEV_STATE_FAILED)) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
r = rtnl_message_new_synthetic_error(-ENODEV, 0, &m);
r = rtnl_message_new_synthetic_error(netdev->manager->rtnl, -ENODEV, 0, &m);
if (r >= 0)
callback(netdev->manager->rtnl, m, link);
} else {

View file

@ -912,6 +912,26 @@ static int systemd_netlink_fd(void) {
return rtnl_fd;
}
static int manager_connect_genl(Manager *m) {
int r;
assert(m);
r = sd_genl_socket_open(&m->genl);
if (r < 0)
return r;
r = sd_netlink_inc_rcvbuf(m->genl, RCVBUF_SIZE);
if (r < 0)
return r;
r = sd_netlink_attach_event(m->genl, m->event, 0);
if (r < 0)
return r;
return 0;
}
static int manager_connect_rtnl(Manager *m) {
int fd, r;
@ -1256,6 +1276,10 @@ int manager_new(Manager **ret, sd_event *event) {
if (r < 0)
return r;
r = manager_connect_genl(m);
if (r < 0)
return r;
r = manager_connect_udev(m);
if (r < 0)
return r;
@ -1266,6 +1290,14 @@ int manager_new(Manager **ret, sd_event *event) {
LIST_HEAD_INIT(m->networks);
r = sd_resolve_default(&m->resolve);
if (r < 0)
return r;
r = sd_resolve_attach_event(m->resolve, m->event, 0);
if (r < 0)
return r;
r = setup_default_address_pool(m);
if (r < 0)
return r;
@ -1315,6 +1347,8 @@ void manager_free(Manager *m) {
sd_netlink_unref(m->rtnl);
sd_event_unref(m->event);
sd_resolve_unref(m->resolve);
sd_event_source_unref(m->udev_event_source);
udev_monitor_unref(m->udev_monitor);
udev_unref(m->udev);

View file

@ -25,6 +25,7 @@
#include "sd-bus.h"
#include "sd-event.h"
#include "sd-netlink.h"
#include "sd-resolve.h"
#include "udev.h"
#include "dhcp-identifier.h"
@ -39,7 +40,10 @@ extern const char* const network_dirs[];
struct Manager {
sd_netlink *rtnl;
/* lazy initialized */
sd_netlink *genl;
sd_event *event;
sd_resolve *resolve;
sd_event_source *bus_retry_event_source;
sd_bus *bus;
sd_bus_slot *prepare_for_sleep_slot;

View file

@ -34,7 +34,9 @@
_SD_BEGIN_DECLARATIONS;
typedef struct sd_netlink sd_netlink;
typedef struct sd_genl_socket sd_genl_socket;
typedef struct sd_netlink_message sd_netlink_message;
typedef enum {SD_GENL_ID_CTRL} sd_genl_family;
/* callback */
@ -94,6 +96,9 @@ int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type,
int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type);
int sd_netlink_message_exit_container(sd_netlink_message *m);
int sd_netlink_message_open_array(sd_netlink_message *m, uint16_t type);
int sd_netlink_message_cancel_array(sd_netlink_message *m);
int sd_netlink_message_rewind(sd_netlink_message *m);
sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m);
@ -177,6 +182,10 @@ int sd_rtnl_message_routing_policy_rule_get_rtm_type(sd_netlink_message *m, unsi
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink, sd_netlink_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink_message, sd_netlink_message_unref);
/* genl */
int sd_genl_socket_open(sd_netlink **nl);
int sd_genl_message_new(sd_netlink *nl, sd_genl_family family, uint8_t cmd, sd_netlink_message **m);
_SD_END_DECLARATIONS;
#endif