diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
commit | 19f4f86bfed21c5326ed2acebe1163f3a83e832b (patch) | |
tree | d59b9989ce55ed23693e80974d94c856f1c2c8b1 /src/libsystemd/sd-netlink | |
parent | Initial commit. (diff) | |
download | systemd-19f4f86bfed21c5326ed2acebe1163f3a83e832b.tar.xz systemd-19f4f86bfed21c5326ed2acebe1163f3a83e832b.zip |
Adding upstream version 241.upstream/241upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libsystemd/sd-netlink')
-rw-r--r-- | src/libsystemd/sd-netlink/generic-netlink.c | 97 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/local-addresses.c | 253 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/local-addresses.h | 17 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-internal.h | 149 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-message.c | 1042 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-slot.c | 202 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-slot.h | 14 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-socket.c | 458 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-types.c | 949 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-types.h | 88 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-util.c | 108 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-util.h | 60 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/rtnl-message.c | 963 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/sd-netlink.c | 907 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/test-local-addresses.c | 42 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/test-netlink.c | 567 |
16 files changed, 5916 insertions, 0 deletions
diff --git a/src/libsystemd/sd-netlink/generic-netlink.c b/src/libsystemd/sd-netlink/generic-netlink.c new file mode 100644 index 0000000..3445757 --- /dev/null +++ b/src/libsystemd/sd-netlink/generic-netlink.c @@ -0,0 +1,97 @@ +#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 }, + [SD_GENL_WIREGUARD] = { .name = "wireguard", .version = 1 }, + [SD_GENL_FOU] = { .name = "fou", .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 = TAKE_PTR(m); + + 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); +} diff --git a/src/libsystemd/sd-netlink/local-addresses.c b/src/libsystemd/sd-netlink/local-addresses.c new file mode 100644 index 0000000..5c37279 --- /dev/null +++ b/src/libsystemd/sd-netlink/local-addresses.c @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "local-addresses.h" +#include "macro.h" +#include "netlink-util.h" + +static int address_compare(const struct local_address *a, const struct local_address *b) { + int r; + + /* Order lowest scope first, IPv4 before IPv6, lowest interface index first */ + + if (a->family == AF_INET && b->family == AF_INET6) + return -1; + if (a->family == AF_INET6 && b->family == AF_INET) + return 1; + + r = CMP(a->scope, b->scope); + if (r != 0) + return r; + + r = CMP(a->metric, b->metric); + if (r != 0) + return r; + + r = CMP(a->ifindex, b->ifindex); + if (r != 0) + return r; + + return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); +} + +int local_addresses(sd_netlink *context, int ifindex, int af, struct local_address **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_free_ struct local_address *list = NULL; + size_t n_list = 0, n_allocated = 0; + sd_netlink_message *m; + int r; + + assert(ret); + + if (context) + rtnl = sd_netlink_ref(context); + else { + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, af); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (m = reply; m; m = sd_netlink_message_next(m)) { + struct local_address *a; + unsigned char flags; + uint16_t type; + int ifi, family; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWADDR) + continue; + + r = sd_rtnl_message_addr_get_ifindex(m, &ifi); + if (r < 0) + return r; + if (ifindex > 0 && ifi != ifindex) + continue; + + r = sd_rtnl_message_addr_get_family(m, &family); + if (r < 0) + return r; + if (af != AF_UNSPEC && af != family) + continue; + + r = sd_rtnl_message_addr_get_flags(m, &flags); + if (r < 0) + return r; + if (flags & IFA_F_DEPRECATED) + continue; + + if (!GREEDY_REALLOC0(list, n_allocated, n_list+1)) + return -ENOMEM; + + a = list + n_list; + + r = sd_rtnl_message_addr_get_scope(m, &a->scope); + if (r < 0) + return r; + + if (ifindex == 0 && IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) + continue; + + switch (family) { + + case AF_INET: + r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); + if (r < 0) { + r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); + if (r < 0) + continue; + } + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); + if (r < 0) { + r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); + if (r < 0) + continue; + } + break; + + default: + continue; + } + + a->ifindex = ifi; + a->family = family; + + n_list++; + }; + + typesafe_qsort(list, n_list, address_compare); + + *ret = TAKE_PTR(list); + + return (int) n_list; +} + +int local_gateways(sd_netlink *context, int ifindex, int af, struct local_address **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_free_ struct local_address *list = NULL; + sd_netlink_message *m = NULL; + size_t n_list = 0, n_allocated = 0; + int r; + + assert(ret); + + if (context) + rtnl = sd_netlink_ref(context); + else { + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (m = reply; m; m = sd_netlink_message_next(m)) { + struct local_address *a; + uint16_t type; + unsigned char dst_len, src_len; + uint32_t ifi; + int family; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWROUTE) + continue; + + /* We only care for default routes */ + r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len); + if (r < 0) + return r; + if (dst_len != 0) + continue; + + r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len); + if (r < 0) + return r; + if (src_len != 0) + continue; + + r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); + if (r == -ENODATA) /* Not all routes have an RTA_OIF attribute (for example nexthop ones) */ + continue; + if (r < 0) + return r; + if (ifindex > 0 && (int) ifi != ifindex) + continue; + + r = sd_rtnl_message_route_get_family(m, &family); + if (r < 0) + return r; + if (af != AF_UNSPEC && af != family) + continue; + + if (!GREEDY_REALLOC0(list, n_allocated, n_list + 1)) + return -ENOMEM; + + a = list + n_list; + + switch (family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(m, RTA_GATEWAY, &a->address.in); + if (r < 0) + continue; + + break; + case AF_INET6: + r = sd_netlink_message_read_in6_addr(m, RTA_GATEWAY, &a->address.in6); + if (r < 0) + continue; + + break; + default: + continue; + } + + sd_netlink_message_read_u32(m, RTA_PRIORITY, &a->metric); + + a->ifindex = ifi; + a->family = family; + + n_list++; + } + + typesafe_qsort(list, n_list, address_compare); + + *ret = TAKE_PTR(list); + + return (int) n_list; +} diff --git a/src/libsystemd/sd-netlink/local-addresses.h b/src/libsystemd/sd-netlink/local-addresses.h new file mode 100644 index 0000000..e88c5e5 --- /dev/null +++ b/src/libsystemd/sd-netlink/local-addresses.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sd-netlink.h" + +#include "in-addr-util.h" + +struct local_address { + int family, ifindex; + unsigned char scope; + uint32_t metric; + union in_addr_union address; +}; + +int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); + +int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); diff --git a/src/libsystemd/sd-netlink/netlink-internal.h b/src/libsystemd/sd-netlink/netlink-internal.h new file mode 100644 index 0000000..98de4f0 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-internal.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <linux/netlink.h> + +#include "sd-netlink.h" + +#include "list.h" +#include "netlink-types.h" +#include "prioq.h" +#include "refcnt.h" + +#define RTNL_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) + +#define RTNL_WQUEUE_MAX 1024 +#define RTNL_RQUEUE_MAX 64*1024 + +#define RTNL_CONTAINER_DEPTH 32 + +struct reply_callback { + sd_netlink_message_handler_t callback; + usec_t timeout; + uint64_t serial; + unsigned prioq_idx; +}; + +struct match_callback { + sd_netlink_message_handler_t callback; + uint16_t type; + + LIST_FIELDS(struct match_callback, match_callbacks); +}; + +typedef enum NetlinkSlotType { + NETLINK_REPLY_CALLBACK, + NETLINK_MATCH_CALLBACK, + _NETLINK_SLOT_INVALID = -1, +} NetlinkSlotType; + +struct sd_netlink_slot { + unsigned n_ref; + sd_netlink *netlink; + void *userdata; + sd_netlink_destroy_t destroy_callback; + NetlinkSlotType type:2; + + bool floating:1; + char *description; + + LIST_FIELDS(sd_netlink_slot, slots); + + union { + struct reply_callback reply_callback; + struct match_callback match_callback; + }; +}; + +struct sd_netlink { + RefCount n_ref; + + int fd; + + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } sockaddr; + + int protocol; + + Hashmap *broadcast_group_refs; + bool broadcast_group_dont_leave:1; /* until we can rely on 4.2 */ + + sd_netlink_message **rqueue; + unsigned rqueue_size; + size_t rqueue_allocated; + + sd_netlink_message **rqueue_partial; + unsigned rqueue_partial_size; + size_t rqueue_partial_allocated; + + struct nlmsghdr *rbuffer; + size_t rbuffer_allocated; + + bool processing:1; + + uint32_t serial; + + struct Prioq *reply_callbacks_prioq; + Hashmap *reply_callbacks; + + LIST_HEAD(struct match_callback, match_callbacks); + + LIST_HEAD(sd_netlink_slot, slots); + + pid_t original_pid; + + sd_event_source *io_event_source; + sd_event_source *time_event_source; + sd_event_source *exit_event_source; + sd_event *event; +}; + +struct netlink_attribute { + size_t offset; /* offset from hdr to attribute */ + bool nested:1; + bool net_byteorder:1; +}; + +struct netlink_container { + const struct NLTypeSystem *type_system; /* the type system of the container */ + size_t offset; /* offset from hdr to the start of the container */ + struct netlink_attribute *attributes; + unsigned short n_attributes; /* number of attributes in container */ +}; + +struct sd_netlink_message { + RefCount n_ref; + + sd_netlink *rtnl; + + int protocol; + + struct nlmsghdr *hdr; + struct netlink_container containers[RTNL_CONTAINER_DEPTH]; + unsigned n_containers; /* number of containers */ + bool sealed:1; + bool broadcast:1; + + sd_netlink_message *next; /* next in a chain of multi-part messages */ +}; + +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); +int socket_broadcast_group_unref(sd_netlink *nl, unsigned group); +int socket_write_message(sd_netlink *nl, sd_netlink_message *m); +int socket_read_message(sd_netlink *nl); + +int rtnl_rqueue_make_room(sd_netlink *rtnl); +int rtnl_rqueue_partial_make_room(sd_netlink *rtnl); + +/* Make sure callbacks don't destroy the rtnl connection */ +#define NETLINK_DONT_DESTROY(rtnl) \ + _cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##rtnl = sd_netlink_ref(rtnl) diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c new file mode 100644 index 0000000..5e9bc45 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -0,0 +1,1042 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <netinet/in.h> +#include <stdbool.h> +#include <unistd.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "format-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +#define GET_CONTAINER(m, i) ((i) < (m)->n_containers ? (struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset) : NULL) +#define PUSH_CONTAINER(m, new) (m)->container_offsets[(m)->n_containers++] = (uint8_t*)(new) - (uint8_t*)(m)->hdr; + +#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) +#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) + +int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) { + sd_netlink_message *m; + + assert_return(ret, -EINVAL); + + /* Note that 'rtnl' is currently unused, if we start using it internally + we must take care to avoid problems due to mutual references between + buses and their queued messages. See sd-bus. + */ + + m = new0(sd_netlink_message, 1); + if (!m) + return -ENOMEM; + + m->n_ref = REFCNT_INIT; + m->protocol = rtnl->protocol; + m->sealed = false; + + *ret = m; + + return 0; +} + +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; + + 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; + + if (type_get_type(nl_type) != NETLINK_TYPE_NESTED) + return -EINVAL; + + r = message_new_empty(rtnl, &m); + if (r < 0) + return r; + + size = NLMSG_SPACE(type_get_size(nl_type)); + + assert(size >= sizeof(struct nlmsghdr)); + m->hdr = malloc0(size); + if (!m->hdr) + return -ENOMEM; + + m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + type_get_type_system(nl_type, &m->containers[0].type_system); + m->hdr->nlmsg_len = size; + m->hdr->nlmsg_type = type; + + *ret = TAKE_PTR(m); + + return 0; +} + +int sd_netlink_message_request_dump(sd_netlink_message *m, int dump) { + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + + assert_return(IN_SET(m->hdr->nlmsg_type, RTM_GETLINK, RTM_GETADDR, RTM_GETROUTE, RTM_GETNEIGH, RTM_GETRULE, RTM_GETADDRLABEL), -EINVAL); + + SET_FLAG(m->hdr->nlmsg_flags, NLM_F_DUMP, dump); + + return 0; +} + +DEFINE_ATOMIC_REF_FUNC(sd_netlink_message, sd_netlink_message); + +sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m) { + sd_netlink_message *t; + + while (m && REFCNT_DEC(m->n_ref) == 0) { + unsigned i; + + free(m->hdr); + + for (i = 0; i <= m->n_containers; i++) + free(m->containers[i].attributes); + + t = m; + m = m->next; + free(t); + } + + return NULL; +} + +int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type) { + assert_return(m, -EINVAL); + assert_return(type, -EINVAL); + + *type = m->hdr->nlmsg_type; + + return 0; +} + +int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags) { + assert_return(m, -EINVAL); + assert_return(flags, -EINVAL); + + m->hdr->nlmsg_flags = flags; + + return 0; +} + +int sd_netlink_message_is_broadcast(sd_netlink_message *m) { + assert_return(m, -EINVAL); + + return m->broadcast; +} + +/* If successful the updated message will be correctly aligned, if + unsuccessful the old message is untouched. */ +static int add_rtattr(sd_netlink_message *m, unsigned short type, const void *data, size_t data_length) { + uint32_t rta_length; + size_t message_length, padding_length; + struct nlmsghdr *new_hdr; + struct rtattr *rta; + char *padding; + unsigned i; + int offset; + + assert(m); + assert(m->hdr); + assert(!m->sealed); + assert(NLMSG_ALIGN(m->hdr->nlmsg_len) == m->hdr->nlmsg_len); + assert(!data || data_length); + + /* get offset of the new attribute */ + offset = m->hdr->nlmsg_len; + + /* get the size of the new rta attribute (with padding at the end) */ + rta_length = RTA_LENGTH(data_length); + + /* 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) + return -ENOMEM; + m->hdr = new_hdr; + + /* get pointer to the attribute we are about to add */ + rta = (struct rtattr *) ((uint8_t *) m->hdr + offset); + + /* if we are inside containers, extend them */ + for (i = 0; i < m->n_containers; i++) + GET_CONTAINER(m, i)->rta_len += message_length - offset; + + /* fill in the attribute */ + rta->rta_type = type; + rta->rta_len = rta_length; + if (data) + /* we don't deal with the case where the user lies about the type + * and gives us too little data (so don't do that) + */ + padding = mempcpy(RTA_DATA(rta), data, data_length); + + else + /* if no data was passed, make sure we still initialize the padding + note that we can have data_length > 0 (used by some containers) */ + padding = RTA_DATA(rta); + + /* make sure also the padding at the end of the message is initialized */ + padding_length = (uint8_t*)m->hdr + message_length - (uint8_t*)padding; + memzero(padding, padding_length); + + /* update message size */ + m->hdr->nlmsg_len = message_length; + + return offset; +} + +static int message_attribute_has_type(sd_netlink_message *m, size_t *out_size, uint16_t attribute_type, uint16_t data_type) { + const NLType *type; + int r; + + assert(m); + + r = type_system_get_type(m->containers[m->n_containers].type_system, &type, attribute_type); + if (r < 0) + return r; + + if (type_get_type(type) != data_type) + return -EINVAL; + + if (out_size) + *out_size = type_get_size(type); + return 0; +} + +int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data) { + size_t length, size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + if (size) { + length = strnlen(data, size+1); + if (length > size) + return -EINVAL; + } else + length = strlen(data); + + r = add_rtattr(m, type, data, length + 1); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_FLAG); + if (r < 0) + return r; + + r = add_rtattr(m, type, NULL, 0); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint8_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint16_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint32_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = add_rtattr(m, type, data, len); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct in_addr)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct in6_addr)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_sockaddr_in(sd_netlink_message *m, unsigned short type, const struct sockaddr_in *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_SOCKADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct sockaddr_in)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_sockaddr_in6(sd_netlink_message *m, unsigned short type, const struct sockaddr_in6 *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_SOCKADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct sockaddr_in6)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, ETH_ALEN); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(info, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = add_rtattr(m, type, info, sizeof(struct ifa_cacheinfo)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -ERANGE); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_NESTED); + if (r < 0) { + const NLTypeSystemUnion *type_system_union; + int family; + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_UNION); + if (r < 0) + return r; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); + if (r < 0) + return r; + + r = type_system_union_protocol_get_type_system(type_system_union, + &m->containers[m->n_containers + 1].type_system, + family); + if (r < 0) + return r; + } else { + r = type_system_get_type_system(m->containers[m->n_containers].type_system, + &m->containers[m->n_containers + 1].type_system, + type); + if (r < 0) + return r; + } + + r = add_rtattr(m, type | NLA_F_NESTED, NULL, size); + if (r < 0) + return r; + + m->containers[m->n_containers++].offset = r; + + return 0; +} + +int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key) { + const NLTypeSystemUnion *type_system_union; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); + if (r < 0) + return r; + + r = type_system_union_get_type_system(type_system_union, + &m->containers[m->n_containers + 1].type_system, + key); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, type_system_union->match, key); + if (r < 0) + return r; + + /* do we ever need non-null size */ + r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0); + if (r < 0) + return r; + + m->containers[m->n_containers++].offset = r; + + 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; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(data, -EINVAL); + + assert(m->n_containers < RTNL_CONTAINER_DEPTH); + assert(m->containers[m->n_containers].attributes); + assert(type < m->containers[m->n_containers].n_attributes); + + attribute = &m->containers[m->n_containers].attributes[type]; + + if (attribute->offset == 0) + return -ENODATA; + + rta = (struct rtattr*)((uint8_t *) m->hdr + attribute->offset); + + *data = RTA_DATA(rta); + + if (net_byteorder) + *net_byteorder = attribute->net_byteorder; + + return RTA_PAYLOAD(rta); +} + +int sd_netlink_message_read(sd_netlink_message *m, unsigned short type, size_t size, void *data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + + if ((size_t) r < size) + return -EIO; + + if (data) + memcpy(data, attr_data, size); + + return 0; +} + +int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if (strnlen(attr_data, r) >= (size_t) r) + return -EIO; + + if (data) + *data = (const char *) attr_data; + + return 0; +} + +int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t) r < sizeof(uint8_t)) + return -EIO; + + if (data) + *data = *(uint8_t *) attr_data; + + return 0; +} + +int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); + if (r < 0) + return r; + else if ((size_t) r < sizeof(uint16_t)) + return -EIO; + + if (data) { + if (net_byteorder) + *data = be16toh(*(uint16_t *) attr_data); + else + *data = *(uint16_t *) attr_data; + } + + return 0; +} + +int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); + if (r < 0) + return r; + else if ((size_t)r < sizeof(uint32_t)) + return -EIO; + + if (data) { + if (net_byteorder) + *data = be32toh(*(uint32_t *) attr_data); + else + *data = *(uint32_t *) attr_data; + } + + return 0; +} + +int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct ether_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct ether_addr)); + + return 0; +} + +int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct ifa_cacheinfo)) + return -EIO; + + if (info) + memcpy(info, attr_data, sizeof(struct ifa_cacheinfo)); + + return 0; +} + +int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct in_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct in_addr)); + + return 0; +} + +int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct in6_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct in6_addr)); + + return 0; +} + +static int netlink_container_parse(sd_netlink_message *m, + struct netlink_container *container, + int count, + struct rtattr *rta, + unsigned rt_len) { + _cleanup_free_ struct netlink_attribute *attributes = NULL; + + attributes = new0(struct netlink_attribute, count); + if (!attributes) + return -ENOMEM; + + for (; RTA_OK(rta, rt_len); rta = RTA_NEXT(rta, rt_len)) { + unsigned short type; + + type = RTA_TYPE(rta); + + /* if the kernel is newer than the headers we used + when building, we ignore out-of-range attributes */ + if (type >= count) + continue; + + if (attributes[type].offset != 0) + log_debug("rtnl: message parse - overwriting repeated attribute"); + + attributes[type].offset = (uint8_t *) rta - (uint8_t *) m->hdr; + attributes[type].nested = RTA_FLAGS(rta) & NLA_F_NESTED; + attributes[type].net_byteorder = RTA_FLAGS(rta) & NLA_F_NET_BYTEORDER; + } + + container->attributes = TAKE_PTR(attributes); + container->n_attributes = count; + + return 0; +} + +int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type_id) { + const NLType *nl_type; + const NLTypeSystem *type_system; + void *container; + uint16_t type; + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -EINVAL); + + r = type_system_get_type(m->containers[m->n_containers].type_system, + &nl_type, + type_id); + if (r < 0) + return r; + + type = type_get_type(nl_type); + + if (type == NETLINK_TYPE_NESTED) { + r = type_system_get_type_system(m->containers[m->n_containers].type_system, + &type_system, + type_id); + if (r < 0) + return r; + } else if (type == NETLINK_TYPE_UNION) { + const NLTypeSystemUnion *type_system_union; + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, + &type_system_union, + type_id); + if (r < 0) + return r; + + switch (type_system_union->match_type) { + case NL_MATCH_SIBLING: + { + const char *key; + + r = sd_netlink_message_read_string(m, type_system_union->match, &key); + if (r < 0) + return r; + + r = type_system_union_get_type_system(type_system_union, + &type_system, + key); + if (r < 0) + return r; + + break; + } + case NL_MATCH_PROTOCOL: + { + int family; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + r = type_system_union_protocol_get_type_system(type_system_union, + &type_system, + family); + if (r < 0) + return r; + + break; + } + default: + assert_not_reached("sd-netlink: invalid type system union type"); + } + } else + return -EINVAL; + + r = netlink_message_read_internal(m, type_id, &container, NULL); + if (r < 0) + return r; + else + size = (size_t)r; + + m->n_containers++; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + type_system_get_count(type_system), + container, + size); + if (r < 0) { + m->n_containers--; + return r; + } + + m->containers[m->n_containers].type_system = type_system; + + return 0; +} + +int sd_netlink_message_exit_container(sd_netlink_message *m) { + assert_return(m, -EINVAL); + assert_return(m->sealed, -EINVAL); + assert_return(m->n_containers > 0, -EINVAL); + + m->containers[m->n_containers].attributes = mfree(m->containers[m->n_containers].attributes); + m->containers[m->n_containers].type_system = NULL; + + m->n_containers--; + + return 0; +} + +uint32_t rtnl_message_get_serial(sd_netlink_message *m) { + assert(m); + assert(m->hdr); + + return m->hdr->nlmsg_seq; +} + +int sd_netlink_message_is_error(sd_netlink_message *m) { + assert_return(m, 0); + assert_return(m->hdr, 0); + + return m->hdr->nlmsg_type == NLMSG_ERROR; +} + +int sd_netlink_message_get_errno(sd_netlink_message *m) { + struct nlmsgerr *err; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + + if (!sd_netlink_message_is_error(m)) + return 0; + + err = NLMSG_DATA(m->hdr); + + return err->error; +} + +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; + int r; + + assert_return(m, -EINVAL); + + /* don't allow appending to message once parsed */ + 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); + + m->n_containers = 0; + + if (m->containers[0].attributes) + /* top-level attributes have already been parsed */ + return 0; + + assert(m->hdr); + + r = type_system_get_type(type_system_root, &nl_type, m->hdr->nlmsg_type); + if (r < 0) + return r; + + type = type_get_type(nl_type); + size = type_get_size(nl_type); + + if (type == NETLINK_TYPE_NESTED) { + const NLTypeSystem *type_system; + + type_get_type_system(nl_type, &type_system); + + m->containers[0].type_system = type_system; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + type_system_get_count(type_system), + (struct rtattr*)((uint8_t*)NLMSG_DATA(m->hdr) + NLMSG_ALIGN(size)), + NLMSG_PAYLOAD(m->hdr, size)); + if (r < 0) + return r; + } + + return 0; +} + +void rtnl_message_seal(sd_netlink_message *m) { + assert(m); + assert(!m->sealed); + + m->sealed = true; +} + +sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m) { + assert_return(m, NULL); + + return m->next; +} diff --git a/src/libsystemd/sd-netlink/netlink-slot.c b/src/libsystemd/sd-netlink/netlink-slot.c new file mode 100644 index 0000000..2b8675d --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-slot.c @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "netlink-internal.h" +#include "netlink-slot.h" +#include "string-util.h" + +int netlink_slot_allocate( + sd_netlink *nl, + bool floating, + NetlinkSlotType type, + size_t extra, + void *userdata, + const char *description, + sd_netlink_slot **ret) { + + _cleanup_free_ sd_netlink_slot *slot = NULL; + + assert(nl); + assert(ret); + + slot = malloc0(offsetof(sd_netlink_slot, reply_callback) + extra); + if (!slot) + return -ENOMEM; + + slot->n_ref = 1; + slot->netlink = nl; + slot->userdata = userdata; + slot->type = type; + slot->floating = floating; + + if (description) { + slot->description = strdup(description); + if (!slot->description) + return -ENOMEM; + } + + if (!floating) + sd_netlink_ref(nl); + + LIST_PREPEND(slots, nl->slots, slot); + + *ret = TAKE_PTR(slot); + + return 0; +} + +void netlink_slot_disconnect(sd_netlink_slot *slot, bool unref) { + sd_netlink *nl; + + assert(slot); + + nl = slot->netlink; + if (!nl) + return; + + switch (slot->type) { + + case NETLINK_REPLY_CALLBACK: + (void) hashmap_remove(nl->reply_callbacks, &slot->reply_callback.serial); + + if (slot->reply_callback.timeout != 0) + prioq_remove(nl->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); + + break; + case NETLINK_MATCH_CALLBACK: + LIST_REMOVE(match_callbacks, nl->match_callbacks, &slot->match_callback); + + switch (slot->match_callback.type) { + case RTM_NEWLINK: + case RTM_DELLINK: + (void) socket_broadcast_group_unref(nl, RTNLGRP_LINK); + + break; + case RTM_NEWADDR: + case RTM_DELADDR: + (void) socket_broadcast_group_unref(nl, RTNLGRP_IPV4_IFADDR); + (void) socket_broadcast_group_unref(nl, RTNLGRP_IPV6_IFADDR); + + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + (void) socket_broadcast_group_unref(nl, RTNLGRP_IPV4_ROUTE); + (void) socket_broadcast_group_unref(nl, RTNLGRP_IPV6_ROUTE); + + break; + } + + break; + default: + assert_not_reached("Wut? Unknown slot type?"); + } + + slot->type = _NETLINK_SLOT_INVALID; + slot->netlink = NULL; + LIST_REMOVE(slots, nl->slots, slot); + + if (!slot->floating) + sd_netlink_unref(nl); + else if (unref) + sd_netlink_slot_unref(slot); +} + +static sd_netlink_slot* netlink_slot_free(sd_netlink_slot *slot) { + assert(slot); + + netlink_slot_disconnect(slot, false); + + if (slot->destroy_callback) + slot->destroy_callback(slot->userdata); + + free(slot->description); + return mfree(slot); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_netlink_slot, sd_netlink_slot, netlink_slot_free); + +sd_netlink *sd_netlink_slot_get_netlink(sd_netlink_slot *slot) { + assert_return(slot, NULL); + + return slot->netlink; +} + +void *sd_netlink_slot_get_userdata(sd_netlink_slot *slot) { + assert_return(slot, NULL); + + return slot->userdata; +} + +void *sd_netlink_slot_set_userdata(sd_netlink_slot *slot, void *userdata) { + void *ret; + + assert_return(slot, NULL); + + ret = slot->userdata; + slot->userdata = userdata; + + return ret; +} + +int sd_netlink_slot_get_destroy_callback(sd_netlink_slot *slot, sd_netlink_destroy_t *callback) { + assert_return(slot, -EINVAL); + + if (callback) + *callback = slot->destroy_callback; + + return !!slot->destroy_callback; +} + +int sd_netlink_slot_set_destroy_callback(sd_netlink_slot *slot, sd_netlink_destroy_t callback) { + assert_return(slot, -EINVAL); + + slot->destroy_callback = callback; + return 0; +} + +int sd_netlink_slot_get_floating(sd_netlink_slot *slot) { + assert_return(slot, -EINVAL); + + return slot->floating; +} + +int sd_netlink_slot_set_floating(sd_netlink_slot *slot, int b) { + assert_return(slot, -EINVAL); + + if (slot->floating == !!b) + return 0; + + if (!slot->netlink) /* Already disconnected */ + return -ESTALE; + + slot->floating = b; + + if (b) { + sd_netlink_slot_ref(slot); + sd_netlink_unref(slot->netlink); + } else { + sd_netlink_ref(slot->netlink); + sd_netlink_slot_unref(slot); + } + + return 1; +} + +int sd_netlink_slot_get_description(sd_netlink_slot *slot, const char **description) { + assert_return(slot, -EINVAL); + + if (description) + *description = slot->description; + + return !!slot->description; +} + +int sd_netlink_slot_set_description(sd_netlink_slot *slot, const char *description) { + assert_return(slot, -EINVAL); + + return free_and_strdup(&slot->description, description); +} diff --git a/src/libsystemd/sd-netlink/netlink-slot.h b/src/libsystemd/sd-netlink/netlink-slot.h new file mode 100644 index 0000000..2641ec6 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-slot.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sd-netlink.h" + +int netlink_slot_allocate( + sd_netlink *nl, + bool floating, + NetlinkSlotType type, + size_t extra, + void *userdata, + const char *description, + sd_netlink_slot **ret); +void netlink_slot_disconnect(sd_netlink_slot *slot, bool unref); diff --git a/src/libsystemd/sd-netlink/netlink-socket.c b/src/libsystemd/sd-netlink/netlink-socket.c new file mode 100644 index 0000000..432e8e8 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-socket.c @@ -0,0 +1,458 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <netinet/in.h> +#include <stdbool.h> +#include <unistd.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "io-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +int socket_open(int family) { + int fd; + + fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, family); + if (fd < 0) + return -errno; + + return fd_move_above_stdio(fd); +} + +static int broadcast_groups_get(sd_netlink *nl) { + _cleanup_free_ uint32_t *groups = NULL; + socklen_t len = 0, old_len; + unsigned i, j; + int r; + + assert(nl); + assert(nl->fd >= 0); + + r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len); + if (r < 0) { + if (errno == ENOPROTOOPT) { + nl->broadcast_group_dont_leave = true; + return 0; + } else + return -errno; + } + + if (len == 0) + return 0; + + groups = new0(uint32_t, len); + if (!groups) + return -ENOMEM; + + old_len = len; + + r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len); + if (r < 0) + return -errno; + + if (old_len != len) + return -EIO; + + r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); + if (r < 0) + return r; + + for (i = 0; i < len; i++) { + for (j = 0; j < sizeof(uint32_t) * 8; j++) { + uint32_t offset; + unsigned group; + + offset = 1U << j; + + if (!(groups[i] & offset)) + continue; + + group = i * sizeof(uint32_t) * 8 + j + 1; + + r = hashmap_put(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(1)); + if (r < 0) + return r; + } + } + + return 0; +} + +int socket_bind(sd_netlink *nl) { + socklen_t addrlen; + int r; + + r = setsockopt_int(nl->fd, SOL_NETLINK, NETLINK_PKTINFO, true); + if (r < 0) + return r; + + addrlen = sizeof(nl->sockaddr); + + r = bind(nl->fd, &nl->sockaddr.sa, addrlen); + /* ignore EINVAL to allow opening an already bound socket */ + if (r < 0 && errno != EINVAL) + return -errno; + + r = getsockname(nl->fd, &nl->sockaddr.sa, &addrlen); + if (r < 0) + return -errno; + + r = broadcast_groups_get(nl); + if (r < 0) + return r; + + return 0; +} + +static unsigned broadcast_group_get_ref(sd_netlink *nl, unsigned group) { + assert(nl); + + return PTR_TO_UINT(hashmap_get(nl->broadcast_group_refs, UINT_TO_PTR(group))); +} + +static int broadcast_group_set_ref(sd_netlink *nl, unsigned group, unsigned n_ref) { + int r; + + assert(nl); + + r = hashmap_replace(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(n_ref)); + if (r < 0) + return r; + + return 0; +} + +static int broadcast_group_join(sd_netlink *nl, unsigned group) { + int r; + + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); + if (r < 0) + return -errno; + + return 0; +} + +int socket_broadcast_group_ref(sd_netlink *nl, unsigned group) { + unsigned n_ref; + int r; + + assert(nl); + + n_ref = broadcast_group_get_ref(nl, group); + + n_ref++; + + r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); + if (r < 0) + return r; + + r = broadcast_group_set_ref(nl, group, n_ref); + if (r < 0) + return r; + + if (n_ref > 1) + /* not yet in the group */ + return 0; + + r = broadcast_group_join(nl, group); + if (r < 0) + return r; + + return 0; +} + +static int broadcast_group_leave(sd_netlink *nl, unsigned group) { + int r; + + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + if (nl->broadcast_group_dont_leave) + return 0; + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, &group, sizeof(group)); + if (r < 0) + return -errno; + + return 0; +} + +int socket_broadcast_group_unref(sd_netlink *nl, unsigned group) { + unsigned n_ref; + int r; + + assert(nl); + + n_ref = broadcast_group_get_ref(nl, group); + + assert(n_ref > 0); + + n_ref--; + + r = broadcast_group_set_ref(nl, group, n_ref); + if (r < 0) + return r; + + if (n_ref > 0) + /* still refs left */ + return 0; + + r = broadcast_group_leave(nl, group); + if (r < 0) + return r; + + return 0; +} + +/* returns the number of bytes sent, or a negative error code */ +int socket_write_message(sd_netlink *nl, sd_netlink_message *m) { + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } addr = { + .nl.nl_family = AF_NETLINK, + }; + ssize_t k; + + assert(nl); + assert(m); + assert(m->hdr); + + k = sendto(nl->fd, m->hdr, m->hdr->nlmsg_len, + 0, &addr.sa, sizeof(addr)); + if (k < 0) + return -errno; + + return k; +} + +static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) { + union sockaddr_union sender; + uint8_t cmsg_buffer[CMSG_SPACE(sizeof(struct nl_pktinfo))]; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = 1, + .msg_name = &sender, + .msg_namelen = sizeof(sender), + .msg_control = cmsg_buffer, + .msg_controllen = sizeof(cmsg_buffer), + }; + struct cmsghdr *cmsg; + uint32_t group = 0; + ssize_t n; + + assert(fd >= 0); + assert(iov); + + n = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); + if (n < 0) { + /* no data */ + if (errno == ENOBUFS) + log_debug("rtnl: kernel receive buffer overrun"); + else if (errno == EAGAIN) + log_debug("rtnl: no data in socket"); + + return IN_SET(errno, EAGAIN, EINTR) ? 0 : -errno; + } + + if (sender.nl.nl_pid != 0) { + /* not from the kernel, ignore */ + log_debug("rtnl: ignoring message from portid %"PRIu32, sender.nl.nl_pid); + + if (peek) { + /* drop the message */ + n = recvmsg(fd, &msg, 0); + if (n < 0) + return IN_SET(errno, EAGAIN, EINTR) ? 0 : -errno; + } + + return 0; + } + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_NETLINK && + cmsg->cmsg_type == NETLINK_PKTINFO && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) { + struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg); + + /* multi-cast group */ + group = pktinfo->group; + } + } + + if (_group) + *_group = group; + + return (int) n; +} + +/* On success, the number of bytes received is returned and *ret points to the received message + * which has a valid header and the correct size. + * If nothing useful was received 0 is returned. + * On failure, a negative error code is returned. + */ +int socket_read_message(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *first = NULL; + struct iovec iov = {}; + uint32_t group = 0; + bool multi_part = false, done = false; + struct nlmsghdr *new_msg; + 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) + return r; + else + len = (size_t) r; + + /* make room for the pending message */ + if (!greedy_realloc((void **)&rtnl->rbuffer, + &rtnl->rbuffer_allocated, + len, sizeof(uint8_t))) + return -ENOMEM; + + iov = IOVEC_MAKE(rtnl->rbuffer, rtnl->rbuffer_allocated); + + /* read the pending message */ + r = socket_recv_message(rtnl->fd, &iov, &group, false); + if (r <= 0) + return r; + else + len = (size_t) r; + + if (len > rtnl->rbuffer_allocated) + /* message did not fit in read buffer */ + return -EIO; + + if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) { + multi_part = true; + + for (i = 0; i < rtnl->rqueue_partial_size; i++) { + if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) == + rtnl->rbuffer->nlmsg_seq) { + first = rtnl->rqueue_partial[i]; + break; + } + } + } + + for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len) && !done; new_msg = NLMSG_NEXT(new_msg, len)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + const NLType *nl_type; + + if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid) + /* not broadcast and not for us */ + continue; + + if (new_msg->nlmsg_type == NLMSG_NOOP) + /* silently drop noop messages */ + continue; + + if (new_msg->nlmsg_type == NLMSG_DONE) { + /* finished reading multi-part message */ + done = true; + + /* if first is not defined, put NLMSG_DONE into the receive queue. */ + if (first) + continue; + } + + /* check that we support this message 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", + new_msg->nlmsg_type); + + continue; + } + + /* check that the size matches the message type */ + if (new_msg->nlmsg_len < NLMSG_LENGTH(type_get_size(nl_type))) { + log_debug("sd-netlink: message larger than expected, dropping"); + continue; + } + + r = message_new_empty(rtnl, &m); + if (r < 0) + return r; + + m->broadcast = !!group; + + m->hdr = memdup(new_msg, new_msg->nlmsg_len); + if (!m->hdr) + return -ENOMEM; + + /* seal and parse the top-level message */ + r = sd_netlink_message_rewind(m); + if (r < 0) + return r; + + /* push the message onto the multi-part message stack */ + if (first) + m->next = first; + first = TAKE_PTR(m); + } + + if (len > 0) + log_debug("sd-netlink: discarding %zu bytes of incoming message", len); + + if (!first) + return 0; + + if (!multi_part || done) { + /* we got a complete message, push it on the read queue */ + r = rtnl_rqueue_make_room(rtnl); + if (r < 0) + return r; + + rtnl->rqueue[rtnl->rqueue_size++] = TAKE_PTR(first); + + if (multi_part && (i < rtnl->rqueue_partial_size)) { + /* remove the message form the partial read queue */ + memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1, + sizeof(sd_netlink_message*) * (rtnl->rqueue_partial_size - i - 1)); + rtnl->rqueue_partial_size--; + } + + return 1; + } else { + /* we only got a partial multi-part message, push it on the + partial read queue */ + if (i < rtnl->rqueue_partial_size) + rtnl->rqueue_partial[i] = TAKE_PTR(first); + else { + r = rtnl_rqueue_partial_make_room(rtnl); + if (r < 0) + return r; + + rtnl->rqueue_partial[rtnl->rqueue_partial_size++] = TAKE_PTR(first); + } + + return 0; + } +} diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c new file mode 100644 index 0000000..9dcd3f2 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -0,0 +1,949 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <netinet/in.h> +#include <stdint.h> +#include <sys/socket.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/genetlink.h> +#include <linux/ip.h> +#include <linux/if.h> +#include <linux/can/netlink.h> +#include <linux/fib_rules.h> +#include <linux/if_addr.h> +#include <linux/if_addrlabel.h> +#include <linux/if_bridge.h> +#include <linux/if_link.h> +#include <linux/if_tunnel.h> +#include <linux/veth.h> + +#if HAVE_LINUX_FOU_H +#include <linux/fou.h> +#endif + +#if HAVE_LINUX_CAN_VXCAN_H +#include <linux/can/vxcan.h> +#endif + +#include "macro.h" +#include "missing.h" +#include "netlink-types.h" +#include "sd-netlink.h" +#include "string-table.h" +#include "util.h" +#include "wireguard-netlink.h" + +/* Maximum ARP IP target defined in kernel */ +#define BOND_MAX_ARP_TARGETS 16 + +typedef enum { + BOND_ARP_TARGETS_0, + BOND_ARP_TARGETS_1, + BOND_ARP_TARGETS_2, + BOND_ARP_TARGETS_3, + BOND_ARP_TARGETS_4, + BOND_ARP_TARGETS_5, + BOND_ARP_TARGETS_6, + BOND_ARP_TARGETS_7, + BOND_ARP_TARGETS_8, + BOND_ARP_TARGETS_9, + BOND_ARP_TARGETS_10, + BOND_ARP_TARGETS_11, + BOND_ARP_TARGETS_12, + BOND_ARP_TARGETS_13, + BOND_ARP_TARGETS_14, + BOND_ARP_TARGETS_MAX = BOND_MAX_ARP_TARGETS, +} BondArpTargets; + +struct NLType { + uint16_t type; + size_t size; + const NLTypeSystem *type_system; + const NLTypeSystemUnion *type_system_union; +}; + +struct NLTypeSystem { + uint16_t count; + const NLType *types; +}; + +static const NLTypeSystem rtnl_link_type_system; + +static const NLType empty_types[1] = { + /* fake array to avoid .types==NULL, which denotes invalid type-systems */ +}; + +static const NLTypeSystem empty_type_system = { + .count = 0, + .types = empty_types, +}; + +static const NLType rtnl_link_info_data_veth_types[] = { + [VETH_INFO_PEER] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, +}; + +static const NLType rtnl_link_info_data_vxcan_types[] = { + [VXCAN_INFO_PEER] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, +}; + +static const NLType rtnl_link_info_data_ipvlan_types[] = { + [IFLA_IPVLAN_MODE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_macvlan_types[] = { + [IFLA_MACVLAN_MODE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_bridge_types[] = { + [IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_MAX_AGE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_AGEING_TIME] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_STP_STATE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_PRIORITY] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_VLAN_FILTERING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_GROUP_FWD_MASK] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_ROOT_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_ROOT_PATH_COST] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_TOPOLOGY_CHANGE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_TOPOLOGY_CHANGE_DETECTED] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_HELLO_TIMER] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_TCN_TIMER] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_TOPOLOGY_CHANGE_TIMER] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_GC_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_GROUP_ADDR] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_FDB_FLUSH] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_MCAST_ROUTER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_SNOOPING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_QUERY_USE_IFADDR] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_QUERIER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_MCAST_HASH_ELASTICITY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_MCAST_HASH_MAX] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_MCAST_LAST_MEMBER_CNT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_MCAST_STARTUP_QUERY_CNT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BR_MCAST_LAST_MEMBER_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_MEMBERSHIP_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_QUERIER_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_QUERY_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_QUERY_RESPONSE_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_MCAST_STARTUP_QUERY_INTVL] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BR_NF_CALL_IPTABLES] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_NF_CALL_IP6TABLES] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_NF_CALL_ARPTABLES] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_vlan_types[] = { + [IFLA_VLAN_ID] = { .type = NETLINK_TYPE_U16 }, +/* + [IFLA_VLAN_FLAGS] = { .len = sizeof(struct ifla_vlan_flags) }, + [IFLA_VLAN_EGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, + [IFLA_VLAN_INGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, +*/ + [IFLA_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_vxlan_types[] = { + [IFLA_VXLAN_ID] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_GROUP] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_LEARNING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_AGEING] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_LIMIT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_PORT_RANGE] = { .type = NETLINK_TYPE_U32}, + [IFLA_VXLAN_PROXY] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_RSC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_L2MISS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_L3MISS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_VXLAN_GROUP6] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_LOCAL6] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_UDP_CSUM] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_UDP_ZERO_CSUM6_TX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_UDP_ZERO_CSUM6_RX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_REMCSUM_TX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_REMCSUM_RX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_GBP] = { .type = NETLINK_TYPE_FLAG }, + [IFLA_VXLAN_REMCSUM_NOPARTIAL] = { .type = NETLINK_TYPE_FLAG }, + [IFLA_VXLAN_COLLECT_METADATA] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_LABEL] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_GPE] = { .type = NETLINK_TYPE_FLAG }, +}; + +static const NLType rtnl_bond_arp_target_types[] = { + [BOND_ARP_TARGETS_0] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_1] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_2] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_3] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_4] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_5] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_6] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_7] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_8] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_9] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_10] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_11] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_12] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_13] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_14] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_MAX] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_bond_arp_type_system = { + .count = ELEMENTSOF(rtnl_bond_arp_target_types), + .types = rtnl_bond_arp_target_types, +}; + +static const NLType rtnl_link_info_data_bond_types[] = { + [IFLA_BOND_MODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ACTIVE_SLAVE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_MIIMON] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_UPDELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_DOWNDELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_USE_CARRIER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ARP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_ARP_IP_TARGET] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_bond_arp_type_system }, + [IFLA_BOND_ARP_VALIDATE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_ARP_ALL_TARGETS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PRIMARY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PRIMARY_RESELECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_FAIL_OVER_MAC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_XMIT_HASH_POLICY] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_RESEND_IGMP] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_NUM_PEER_NOTIF] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ALL_SLAVES_ACTIVE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_MIN_LINKS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_LP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PACKETS_PER_SLAVE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_AD_LACP_RATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_AD_SELECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_AD_INFO] = { .type = NETLINK_TYPE_NESTED }, + [IFLA_BOND_AD_ACTOR_SYS_PRIO] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BOND_AD_USER_PORT_KEY] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BOND_AD_ACTOR_SYSTEM] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_BOND_TLB_DYNAMIC_LB] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLType rtnl_link_info_data_iptun_types[] = { + [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_6RD_PREFIX] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_6RD_RELAY_PREFIX] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_6RD_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_6RD_RELAY_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_ipgre_types[] = { + [IFLA_GRE_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_IFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_OFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_IKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_OKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ERSPAN_INDEX] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLType rtnl_link_info_data_ipvti_types[] = { + [IFLA_VTI_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_IKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_OKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VTI_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, +}; + +static const NLType rtnl_link_info_data_ip6tnl_types[] = { + [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_ENCAP_LIMIT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLType rtnl_link_info_data_vrf_types[] = { + [IFLA_VRF_TABLE] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLType rtnl_link_info_data_geneve_types[] = { + [IFLA_GENEVE_ID] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GENEVE_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GENEVE_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GENEVE_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GENEVE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GENEVE_REMOTE6] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GENEVE_UDP_CSUM] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GENEVE_UDP_ZERO_CSUM6_TX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GENEVE_UDP_ZERO_CSUM6_RX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GENEVE_LABEL] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLType rtnl_link_info_data_can_types[] = { + [IFLA_CAN_BITTIMING] = { .size = sizeof(struct can_bittiming) }, + [IFLA_CAN_RESTART_MS] = { .type = NETLINK_TYPE_U32 }, +}; + +/* these strings must match the .kind entries in the kernel */ +static const char* const nl_union_link_info_data_table[] = { + [NL_UNION_LINK_INFO_DATA_BOND] = "bond", + [NL_UNION_LINK_INFO_DATA_BRIDGE] = "bridge", + [NL_UNION_LINK_INFO_DATA_VLAN] = "vlan", + [NL_UNION_LINK_INFO_DATA_VETH] = "veth", + [NL_UNION_LINK_INFO_DATA_DUMMY] = "dummy", + [NL_UNION_LINK_INFO_DATA_MACVLAN] = "macvlan", + [NL_UNION_LINK_INFO_DATA_MACVTAP] = "macvtap", + [NL_UNION_LINK_INFO_DATA_IPVLAN] = "ipvlan", + [NL_UNION_LINK_INFO_DATA_VXLAN] = "vxlan", + [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = "ipip", + [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = "gre", + [NL_UNION_LINK_INFO_DATA_ERSPAN] = "erspan", + [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = "gretap", + [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = "ip6gre", + [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = "ip6gretap", + [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = "sit", + [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = "vti", + [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = "vti6", + [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = "ip6tnl", + [NL_UNION_LINK_INFO_DATA_VRF] = "vrf", + [NL_UNION_LINK_INFO_DATA_VCAN] = "vcan", + [NL_UNION_LINK_INFO_DATA_GENEVE] = "geneve", + [NL_UNION_LINK_INFO_DATA_VXCAN] = "vxcan", + [NL_UNION_LINK_INFO_DATA_WIREGUARD] = "wireguard", + [NL_UNION_LINK_INFO_DATA_NETDEVSIM] = "netdevsim", + [NL_UNION_LINK_INFO_DATA_CAN] = "can", +}; + +DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); + +static const NLTypeSystem rtnl_link_info_data_type_systems[] = { + [NL_UNION_LINK_INFO_DATA_BOND] = { .count = ELEMENTSOF(rtnl_link_info_data_bond_types), + .types = rtnl_link_info_data_bond_types }, + [NL_UNION_LINK_INFO_DATA_BRIDGE] = { .count = ELEMENTSOF(rtnl_link_info_data_bridge_types), + .types = rtnl_link_info_data_bridge_types }, + [NL_UNION_LINK_INFO_DATA_VLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vlan_types), + .types = rtnl_link_info_data_vlan_types }, + [NL_UNION_LINK_INFO_DATA_VETH] = { .count = ELEMENTSOF(rtnl_link_info_data_veth_types), + .types = rtnl_link_info_data_veth_types }, + [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), + .types = rtnl_link_info_data_ipvlan_types }, + [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), + .types = rtnl_link_info_data_vxlan_types }, + [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_ERSPAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types), + .types = rtnl_link_info_data_ip6tnl_types }, + [NL_UNION_LINK_INFO_DATA_VRF] = { .count = ELEMENTSOF(rtnl_link_info_data_vrf_types), + .types = rtnl_link_info_data_vrf_types }, + [NL_UNION_LINK_INFO_DATA_GENEVE] = { .count = ELEMENTSOF(rtnl_link_info_data_geneve_types), + .types = rtnl_link_info_data_geneve_types }, + [NL_UNION_LINK_INFO_DATA_VXCAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxcan_types), + .types = rtnl_link_info_data_vxcan_types }, + [NL_UNION_LINK_INFO_DATA_CAN] = { .count = ELEMENTSOF(rtnl_link_info_data_can_types), + .types = rtnl_link_info_data_can_types }, +}; + +static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = { + .num = _NL_UNION_LINK_INFO_DATA_MAX, + .lookup = nl_union_link_info_data_from_string, + .type_systems = rtnl_link_info_data_type_systems, + .match_type = NL_MATCH_SIBLING, + .match = IFLA_INFO_KIND, +}; + +static const NLType rtnl_link_info_types[] = { + [IFLA_INFO_KIND] = { .type = NETLINK_TYPE_STRING }, + [IFLA_INFO_DATA] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_link_info_data_type_system_union}, +/* + [IFLA_INFO_XSTATS], + [IFLA_INFO_SLAVE_KIND] = { .type = NETLINK_TYPE_STRING }, + [IFLA_INFO_SLAVE_DATA] = { .type = NETLINK_TYPE_NESTED }, +*/ +}; + +static const NLTypeSystem rtnl_link_info_type_system = { + .count = ELEMENTSOF(rtnl_link_info_types), + .types = rtnl_link_info_types, +}; + +static const struct NLType rtnl_prot_info_bridge_port_types[] = { + [IFLA_BRPORT_STATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_COST] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BRPORT_PRIORITY] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROXYARP_WIFI] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_ROOT_ID] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_BRIDGE_ID] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_DESIGNATED_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_DESIGNATED_COST] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_ID] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_NO] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_TOPOLOGY_CHANGE_ACK] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_CONFIG_PENDING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MESSAGE_AGE_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BRPORT_FORWARD_DELAY_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BRPORT_HOLD_TIMER] = { .type = NETLINK_TYPE_U64 }, + [IFLA_BRPORT_FLUSH] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MULTICAST_ROUTER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PAD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MCAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_MCAST_TO_UCAST] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_VLAN_TUNNEL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_BCAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_GROUP_FWD_MASK] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_NEIGH_SUPPRESS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_ISOLATED] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_BACKUP_PORT] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_prot_info_type_systems[] = { + [AF_BRIDGE] = { .count = ELEMENTSOF(rtnl_prot_info_bridge_port_types), + .types = rtnl_prot_info_bridge_port_types }, +}; + +static const NLTypeSystemUnion rtnl_prot_info_type_system_union = { + .num = AF_MAX, + .type_systems = rtnl_prot_info_type_systems, + .match_type = NL_MATCH_PROTOCOL, +}; + +static const struct NLType rtnl_af_spec_inet6_types[] = { + [IFLA_INET6_FLAGS] = { .type = NETLINK_TYPE_U32 }, +/* + IFLA_INET6_CONF, + IFLA_INET6_STATS, + IFLA_INET6_MCAST, + IFLA_INET6_CACHEINFO, + IFLA_INET6_ICMP6STATS, +*/ + [IFLA_INET6_TOKEN] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_INET6_ADDR_GEN_MODE] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLTypeSystem rtnl_af_spec_inet6_type_system = { + .count = ELEMENTSOF(rtnl_af_spec_inet6_types), + .types = rtnl_af_spec_inet6_types, +}; + +static const NLType rtnl_af_spec_types[] = { + [AF_INET6] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_inet6_type_system }, +}; + +static const NLTypeSystem rtnl_af_spec_type_system = { + .count = ELEMENTSOF(rtnl_af_spec_types), + .types = rtnl_af_spec_types, +}; + +static const NLType rtnl_link_types[] = { + [IFLA_ADDRESS] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_BROADCAST] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, + [IFLA_MTU] = { .type = NETLINK_TYPE_U32 }, + [IFLA_LINK] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_QDISC], + [IFLA_STATS], + [IFLA_COST], + [IFLA_PRIORITY], +*/ + [IFLA_MASTER] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_WIRELESS], +*/ + [IFLA_PROTINFO] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_prot_info_type_system_union }, + [IFLA_TXQLEN] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_MAP] = { .len = sizeof(struct rtnl_link_ifmap) }, +*/ + [IFLA_WEIGHT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_OPERSTATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_LINKMODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_LINKINFO] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_info_type_system }, + [IFLA_NET_NS_PID] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IFALIAS] = { .type = NETLINK_TYPE_STRING, .size = IFALIASZ - 1 }, +/* + [IFLA_NUM_VF], + [IFLA_VFINFO_LIST] = {. type = NETLINK_TYPE_NESTED, }, + [IFLA_STATS64], + [IFLA_VF_PORTS] = { .type = NETLINK_TYPE_NESTED }, + [IFLA_PORT_SELF] = { .type = NETLINK_TYPE_NESTED }, +*/ + [IFLA_AF_SPEC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_type_system }, +/* + [IFLA_VF_PORTS], + [IFLA_PORT_SELF], + [IFLA_AF_SPEC], +*/ + [IFLA_GROUP] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NET_NS_FD] = { .type = NETLINK_TYPE_U32 }, + [IFLA_EXT_MASK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_PROMISCUITY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NUM_TX_QUEUES] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NUM_RX_QUEUES] = { .type = NETLINK_TYPE_U32 }, + [IFLA_CARRIER] = { .type = NETLINK_TYPE_U8 }, +/* + [IFLA_PHYS_PORT_ID] = { .type = NETLINK_TYPE_BINARY, .len = MAX_PHYS_PORT_ID_LEN }, +*/ +}; + +static const NLTypeSystem rtnl_link_type_system = { + .count = ELEMENTSOF(rtnl_link_types), + .types = rtnl_link_types, +}; + +/* IFA_FLAGS was defined in kernel 3.14, but we still support older + * kernels where IFA_MAX is lower. */ +static const NLType rtnl_address_types[] = { + [IFA_ADDRESS] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFA_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFA_LABEL] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, + [IFA_BROADCAST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [IFA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct ifa_cacheinfo) }, +/* + [IFA_ANYCAST], + [IFA_MULTICAST], +*/ + [IFA_FLAGS] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_address_type_system = { + .count = ELEMENTSOF(rtnl_address_types), + .types = rtnl_address_types, +}; + +/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */ + +static const NLType rtnl_route_metrics_types[] = { + [RTAX_MTU] = { .type = NETLINK_TYPE_U32 }, + [RTAX_WINDOW] = { .type = NETLINK_TYPE_U32 }, + [RTAX_RTT] = { .type = NETLINK_TYPE_U32 }, + [RTAX_RTTVAR] = { .type = NETLINK_TYPE_U32 }, + [RTAX_SSTHRESH] = { .type = NETLINK_TYPE_U32 }, + [RTAX_CWND] = { .type = NETLINK_TYPE_U32 }, + [RTAX_ADVMSS] = { .type = NETLINK_TYPE_U32 }, + [RTAX_REORDERING] = { .type = NETLINK_TYPE_U32 }, + [RTAX_HOPLIMIT] = { .type = NETLINK_TYPE_U32 }, + [RTAX_INITCWND] = { .type = NETLINK_TYPE_U32 }, + [RTAX_FEATURES] = { .type = NETLINK_TYPE_U32 }, + [RTAX_RTO_MIN] = { .type = NETLINK_TYPE_U32 }, + [RTAX_INITRWND] = { .type = NETLINK_TYPE_U32 }, + [RTAX_QUICKACK] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_route_metrics_type_system = { + .count = ELEMENTSOF(rtnl_route_metrics_types), + .types = rtnl_route_metrics_types, +}; + +static const NLType rtnl_route_types[] = { + [RTA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [RTA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [RTA_IIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_OIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, + [RTA_PRIORITY] = { .type = NETLINK_TYPE_U32 }, + [RTA_PREFSRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [RTA_METRICS] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_metrics_type_system}, +/* [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) }, +*/ + [RTA_FLOW] = { .type = NETLINK_TYPE_U32 }, /* 6? */ +/* + RTA_CACHEINFO, + RTA_TABLE, + RTA_MARK, + RTA_MFC_STATS, + RTA_VIA, + RTA_NEWDST, +*/ + [RTA_PREF] = { .type = NETLINK_TYPE_U8 }, +/* + RTA_ENCAP_TYPE, + RTA_ENCAP, + */ + [RTA_EXPIRES] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_route_type_system = { + .count = ELEMENTSOF(rtnl_route_types), + .types = rtnl_route_types, +}; + +static const NLType rtnl_neigh_types[] = { + [NDA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, + [NDA_LLADDR] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [NDA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct nda_cacheinfo) }, + [NDA_PROBES] = { .type = NETLINK_TYPE_U32 }, + [NDA_VLAN] = { .type = NETLINK_TYPE_U16 }, + [NDA_PORT] = { .type = NETLINK_TYPE_U16 }, + [NDA_VNI] = { .type = NETLINK_TYPE_U32 }, + [NDA_IFINDEX] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_neigh_type_system = { + .count = ELEMENTSOF(rtnl_neigh_types), + .types = rtnl_neigh_types, +}; + +static const NLType rtnl_addrlabel_types[] = { + [IFAL_ADDRESS] = { .type = NETLINK_TYPE_IN_ADDR, .size = sizeof(struct in6_addr) }, + [IFAL_LABEL] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_addrlabel_type_system = { + .count = ELEMENTSOF(rtnl_addrlabel_types), + .types = rtnl_addrlabel_types, +}; + +static const NLType rtnl_routing_policy_rule_types[] = { + [FRA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, + [FRA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, + [FRA_IIFNAME] = { .type = NETLINK_TYPE_STRING }, + [RTA_OIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, + [FRA_PRIORITY] = { .type = NETLINK_TYPE_U32 }, + [FRA_FWMARK] = { .type = NETLINK_TYPE_U32 }, + [FRA_FLOW] = { .type = NETLINK_TYPE_U32 }, + [FRA_TUN_ID] = { .type = NETLINK_TYPE_U32 }, + [FRA_SUPPRESS_IFGROUP] = { .type = NETLINK_TYPE_U32 }, + [FRA_SUPPRESS_PREFIXLEN] = { .type = NETLINK_TYPE_U32 }, + [FRA_TABLE] = { .type = NETLINK_TYPE_U32 }, + [FRA_FWMASK] = { .type = NETLINK_TYPE_U32 }, + [FRA_OIFNAME] = { .type = NETLINK_TYPE_STRING }, + [FRA_PAD] = { .type = NETLINK_TYPE_U32 }, + [FRA_L3MDEV] = { .type = NETLINK_TYPE_U64 }, + [FRA_UID_RANGE] = { .size = sizeof(struct fib_rule_uid_range) }, + [FRA_PROTOCOL] = { .type = NETLINK_TYPE_U8 }, + [FRA_IP_PROTO] = { .type = NETLINK_TYPE_U8 }, + [FRA_SPORT_RANGE] = { .size = sizeof(struct fib_rule_port_range) }, + [FRA_DPORT_RANGE] = { .size = sizeof(struct fib_rule_port_range) }, +}; + +static const NLTypeSystem rtnl_routing_policy_rule_type_system = { + .count = ELEMENTSOF(rtnl_routing_policy_rule_types), + .types = rtnl_routing_policy_rule_types, +}; + +static const NLType rtnl_types[] = { + [NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 }, + [NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) }, + [RTM_NEWLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_DELLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_GETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_SETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_NEWADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_DELADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_GETADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_NEWROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_DELROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_GETROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_NEWNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, + [RTM_DELNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, + [RTM_GETNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, + [RTM_NEWADDRLABEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_addrlabel_type_system, .size = sizeof(struct ifaddrlblmsg) }, + [RTM_DELADDRLABEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_addrlabel_type_system, .size = sizeof(struct ifaddrlblmsg) }, + [RTM_GETADDRLABEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_addrlabel_type_system, .size = sizeof(struct ifaddrlblmsg) }, + [RTM_NEWRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, + [RTM_DELRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, + [RTM_GETRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, +}; + +const NLTypeSystem rtnl_type_system_root = { + .count = ELEMENTSOF(rtnl_types), + .types = rtnl_types, +}; + +static const NLType genl_wireguard_allowedip_types[] = { + [WGALLOWEDIP_A_FAMILY] = { .type = NETLINK_TYPE_U16 }, + [WGALLOWEDIP_A_IPADDR] = { .type = NETLINK_TYPE_IN_ADDR }, + [WGALLOWEDIP_A_CIDR_MASK] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLTypeSystem genl_wireguard_allowedip_type_system = { + .count = ELEMENTSOF(genl_wireguard_allowedip_types), + .types = genl_wireguard_allowedip_types, +}; + +static const NLType genl_wireguard_peer_types[] = { + [WGPEER_A_PUBLIC_KEY] = { .size = WG_KEY_LEN }, + [WGPEER_A_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [WGPEER_A_PRESHARED_KEY] = { .size = WG_KEY_LEN }, + [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = { .type = NETLINK_TYPE_U16 }, + [WGPEER_A_ENDPOINT] = { .type = NETLINK_TYPE_SOCKADDR }, + [WGPEER_A_ALLOWEDIPS] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_allowedip_type_system }, +}; + +static const NLTypeSystem genl_wireguard_peer_type_system = { + .count = ELEMENTSOF(genl_wireguard_peer_types), + .types = genl_wireguard_peer_types, +}; + +static const NLType genl_wireguard_set_device_types[] = { + [WGDEVICE_A_IFINDEX] = { .type = NETLINK_TYPE_U32 }, + [WGDEVICE_A_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ-1 }, + [WGDEVICE_A_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [WGDEVICE_A_PRIVATE_KEY] = { .size = WG_KEY_LEN }, + [WGDEVICE_A_LISTEN_PORT] = { .type = NETLINK_TYPE_U16 }, + [WGDEVICE_A_FWMARK] = { .type = NETLINK_TYPE_U32 }, + [WGDEVICE_A_PEERS] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_peer_type_system }, +}; + +static const NLTypeSystem genl_wireguard_set_device_type_system = { + .count = ELEMENTSOF(genl_wireguard_set_device_types), + .types = genl_wireguard_set_device_types, +}; + +static const NLType genl_wireguard_cmds[] = { + [WG_CMD_SET_DEVICE] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_set_device_type_system }, +}; + +static const NLTypeSystem genl_wireguard_type_system = { + .count = ELEMENTSOF(genl_wireguard_cmds), + .types = genl_wireguard_cmds, +}; + +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_fou_types[] = { + [FOU_ATTR_PORT] = { .type = NETLINK_TYPE_U16 }, + [FOU_ATTR_AF] = { .type = NETLINK_TYPE_U8 }, + [FOU_ATTR_IPPROTO] = { .type = NETLINK_TYPE_U8 }, + [FOU_ATTR_TYPE] = { .type = NETLINK_TYPE_U8 }, + [FOU_ATTR_REMCSUM_NOPARTIAL] = { .type = NETLINK_TYPE_FLAG }, +}; + +static const NLTypeSystem genl_fou_type_system = { + .count = ELEMENTSOF(genl_fou_types), + .types = genl_fou_types, +}; + +static const NLType genl_fou_cmds[] = { + [FOU_CMD_ADD] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_type_system }, + [FOU_CMD_DEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_type_system }, + [FOU_CMD_GET] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_type_system }, +}; + +static const NLTypeSystem genl_fou_cmds_type_system = { + .count = ELEMENTSOF(genl_fou_cmds), + .types = genl_fou_cmds, +}; + +static const NLType genl_families[] = { + [SD_GENL_ID_CTRL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_ctrl_id_ctrl_type_system }, + [SD_GENL_WIREGUARD] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_type_system }, + [SD_GENL_FOU] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_cmds_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; +} + +size_t type_get_size(const NLType *type) { + assert(type); + return type->size; +} + +void type_get_type_system(const NLType *nl_type, const NLTypeSystem **ret) { + assert(nl_type); + assert(ret); + assert(nl_type->type == NETLINK_TYPE_NESTED); + assert(nl_type->type_system); + + *ret = nl_type->type_system; +} + +void type_get_type_system_union(const NLType *nl_type, const NLTypeSystemUnion **ret) { + assert(nl_type); + assert(ret); + assert(nl_type->type == NETLINK_TYPE_UNION); + assert(nl_type->type_system_union); + + *ret = nl_type->type_system_union; +} + +uint16_t type_system_get_count(const NLTypeSystem *type_system) { + assert(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; + + assert(ret); + assert(type_system); + assert(type_system->types); + + if (type >= type_system->count) + return -EOPNOTSUPP; + + nl_type = &type_system->types[type]; + + if (nl_type->type == NETLINK_TYPE_UNSPEC) + return -EOPNOTSUPP; + + *ret = nl_type; + + return 0; +} + +int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type) { + const NLType *nl_type; + int r; + + assert(ret); + + r = type_system_get_type(type_system, &nl_type, type); + if (r < 0) + return r; + + type_get_type_system(nl_type, ret); + return 0; +} + +int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type) { + const NLType *nl_type; + int r; + + assert(ret); + + r = type_system_get_type(type_system, &nl_type, type); + if (r < 0) + return r; + + type_get_type_system_union(nl_type, ret); + return 0; +} + +int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key) { + int type; + + assert(type_system_union); + assert(type_system_union->match_type == NL_MATCH_SIBLING); + assert(type_system_union->lookup); + assert(type_system_union->type_systems); + assert(ret); + assert(key); + + type = type_system_union->lookup(key); + if (type < 0) + return -EOPNOTSUPP; + + assert(type < type_system_union->num); + + *ret = &type_system_union->type_systems[type]; + + return 0; +} + +int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol) { + const NLTypeSystem *type_system; + + assert(type_system_union); + assert(type_system_union->type_systems); + assert(type_system_union->match_type == NL_MATCH_PROTOCOL); + assert(ret); + + if (protocol >= type_system_union->num) + return -EOPNOTSUPP; + + type_system = &type_system_union->type_systems[protocol]; + if (!type_system->types) + return -EOPNOTSUPP; + + *ret = type_system; + + return 0; +} diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h new file mode 100644 index 0000000..b84fa47 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "macro.h" + +enum { + NETLINK_TYPE_UNSPEC, + NETLINK_TYPE_U8, /* NLA_U8 */ + NETLINK_TYPE_U16, /* NLA_U16 */ + NETLINK_TYPE_U32, /* NLA_U32 */ + NETLINK_TYPE_U64, /* NLA_U64 */ + NETLINK_TYPE_STRING, /* NLA_STRING */ + NETLINK_TYPE_FLAG, /* NLA_FLAG */ + NETLINK_TYPE_IN_ADDR, + NETLINK_TYPE_ETHER_ADDR, + NETLINK_TYPE_CACHE_INFO, + NETLINK_TYPE_NESTED, /* NLA_NESTED */ + NETLINK_TYPE_UNION, + NETLINK_TYPE_SOCKADDR, +}; + +typedef enum NLMatchType { + NL_MATCH_SIBLING, + NL_MATCH_PROTOCOL, +} NLMatchType; + +typedef struct NLTypeSystemUnion NLTypeSystemUnion; +typedef struct NLTypeSystem NLTypeSystem; +typedef struct NLType NLType; + +struct NLTypeSystemUnion { + int num; + NLMatchType match_type; + uint16_t match; + int (*lookup)(const char *); + const NLTypeSystem *type_systems; +}; + +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); +int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type); +int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key); +int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol); + +typedef enum NLUnionLinkInfoData { + NL_UNION_LINK_INFO_DATA_BOND, + NL_UNION_LINK_INFO_DATA_BRIDGE, + NL_UNION_LINK_INFO_DATA_VLAN, + NL_UNION_LINK_INFO_DATA_VETH, + NL_UNION_LINK_INFO_DATA_DUMMY, + NL_UNION_LINK_INFO_DATA_MACVLAN, + NL_UNION_LINK_INFO_DATA_MACVTAP, + NL_UNION_LINK_INFO_DATA_IPVLAN, + NL_UNION_LINK_INFO_DATA_VXLAN, + NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL, + NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL, + NL_UNION_LINK_INFO_DATA_ERSPAN, + NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL, + NL_UNION_LINK_INFO_DATA_SIT_TUNNEL, + NL_UNION_LINK_INFO_DATA_VTI_TUNNEL, + NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL, + NL_UNION_LINK_INFO_DATA_VRF, + NL_UNION_LINK_INFO_DATA_VCAN, + NL_UNION_LINK_INFO_DATA_GENEVE, + NL_UNION_LINK_INFO_DATA_VXCAN, + NL_UNION_LINK_INFO_DATA_WIREGUARD, + NL_UNION_LINK_INFO_DATA_NETDEVSIM, + NL_UNION_LINK_INFO_DATA_CAN, + _NL_UNION_LINK_INFO_DATA_MAX, + _NL_UNION_LINK_INFO_DATA_INVALID = -1 +} NLUnionLinkInfoData; + +const char *nl_union_link_info_data_to_string(NLUnionLinkInfoData p) _const_; +NLUnionLinkInfoData nl_union_link_info_data_from_string(const char *p) _pure_; diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c new file mode 100644 index 0000000..3928dfb --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-util.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "sd-netlink.h" + +#include "netlink-internal.h" +#include "netlink-util.h" + +int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(name); + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, + const struct ether_addr *mac, uint32_t mtu) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + + if (!alias && !mac && mtu == 0) + return 0; + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) + return r; + + if (alias) { + r = sd_netlink_message_append_string(message, IFLA_IFALIAS, alias); + if (r < 0) + return r; + } + + if (mac) { + r = sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, mac); + if (r < 0) + return r; + } + + if (mtu != 0) { + r = sd_netlink_message_append_u32(message, IFLA_MTU, mtu); + if (r < 0) + return r; + } + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +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(rtnl, ret, NLMSG_ERROR); + if (r < 0) + return r; + + (*ret)->hdr->nlmsg_seq = serial; + + err = NLMSG_DATA((*ret)->hdr); + + err->error = error; + + return 0; +} + +int rtnl_log_parse_error(int r) { + return log_error_errno(r, "Failed to parse netlink message: %m"); +} + +int rtnl_log_create_error(int r) { + return log_error_errno(r, "Failed to create netlink message: %m"); +} diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h new file mode 100644 index 0000000..d272328 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sd-netlink.h" + +#include "util.h" + +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); + +static inline bool rtnl_message_type_is_neigh(uint16_t type) { + return IN_SET(type, RTM_NEWNEIGH, RTM_GETNEIGH, RTM_DELNEIGH); +} + +static inline bool rtnl_message_type_is_route(uint16_t type) { + return IN_SET(type, RTM_NEWROUTE, RTM_GETROUTE, RTM_DELROUTE); +} + +static inline bool rtnl_message_type_is_link(uint16_t type) { + return IN_SET(type, RTM_NEWLINK, RTM_SETLINK, RTM_GETLINK, RTM_DELLINK); +} + +static inline bool rtnl_message_type_is_addr(uint16_t type) { + return IN_SET(type, RTM_NEWADDR, RTM_GETADDR, RTM_DELADDR); +} + +static inline bool rtnl_message_type_is_addrlabel(uint16_t type) { + return IN_SET(type, RTM_NEWADDRLABEL, RTM_DELADDRLABEL, RTM_GETADDRLABEL); +} + +static inline bool rtnl_message_type_is_routing_policy_rule(uint16_t type) { + return IN_SET(type, RTM_NEWRULE, RTM_DELRULE, RTM_GETRULE); +} + +int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); +int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, uint32_t mtu); + +int rtnl_log_parse_error(int r); +int rtnl_log_create_error(int r); + +#define netlink_call_async(nl, ret_slot, message, callback, destroy_callback, userdata) \ + ({ \ + int (*_callback_)(sd_netlink *, sd_netlink_message *, typeof(userdata)) = callback; \ + void (*_destroy_)(typeof(userdata)) = destroy_callback; \ + sd_netlink_call_async(nl, ret_slot, message, \ + (sd_netlink_message_handler_t) _callback_, \ + (sd_netlink_destroy_t) _destroy_, \ + userdata, 0, __func__); \ + }) + +#define netlink_add_match(nl, ret_slot, metch, callback, destroy_callback, userdata) \ + ({ \ + int (*_callback_)(sd_netlink *, sd_netlink_message *, typeof(userdata)) = callback; \ + void (*_destroy_)(typeof(userdata)) = destroy_callback; \ + sd_netlink_add_match(nl, ret_slot, match, \ + (sd_netlink_message_handler_t) _callback_, \ + (sd_netlink_destroy_t) _destroy_, \ + userdata, __func__); \ + }) diff --git a/src/libsystemd/sd-netlink/rtnl-message.c b/src/libsystemd/sd-netlink/rtnl-message.c new file mode 100644 index 0000000..2d4d00e --- /dev/null +++ b/src/libsystemd/sd-netlink/rtnl-message.c @@ -0,0 +1,963 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <netinet/in.h> +#include <linux/if_addrlabel.h> +#include <stdbool.h> +#include <unistd.h> + +#include "sd-netlink.h" + +#include "format-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + if ((rtm->rtm_family == AF_INET && prefixlen > 32) || + (rtm->rtm_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + rtm->rtm_dst_len = prefixlen; + + return 0; +} + +int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + if ((rtm->rtm_family == AF_INET && prefixlen > 32) || + (rtm->rtm_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + rtm->rtm_src_len = prefixlen; + + return 0; +} + +int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_scope = scope; + + return 0; +} + +int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_flags = flags; + + return 0; +} + +int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *flags = rtm->rtm_flags; + + return 0; +} + +int sd_rtnl_message_route_set_table(sd_netlink_message *m, unsigned char table) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_table = table; + + return 0; +} + +int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + + return 0; +} + +int sd_rtnl_message_route_set_family(sd_netlink_message *m, int family) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_family = family; + + return 0; +} + +int sd_rtnl_message_route_get_type(sd_netlink_message *m, unsigned char *type) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(type, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *type = rtm->rtm_type; + + return 0; +} + +int sd_rtnl_message_route_set_type(sd_netlink_message *m, unsigned char type) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_type = type; + + return 0; +} + +int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(protocol, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *protocol = rtm->rtm_protocol; + + return 0; +} + +int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(scope, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *scope = rtm->rtm_scope; + + return 0; +} + +int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(tos, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *tos = rtm->rtm_tos; + + return 0; +} + +int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(table, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *table = rtm->rtm_table; + + return 0; +} + +int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(dst_len, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *dst_len = rtm->rtm_dst_len; + + return 0; +} + +int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(src_len, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *src_len = rtm->rtm_src_len; + + return 0; +} + +int sd_rtnl_message_new_route(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int rtm_family, + unsigned char rtm_protocol) { + struct rtmsg *rtm; + int r; + + assert_return(rtnl_message_type_is_route(nlmsg_type), -EINVAL); + assert_return((nlmsg_type == RTM_GETROUTE && rtm_family == AF_UNSPEC) || + IN_SET(rtm_family, AF_INET, AF_INET6), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWROUTE) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + rtm = NLMSG_DATA((*ret)->hdr); + + rtm->rtm_family = rtm_family; + rtm->rtm_scope = RT_SCOPE_UNIVERSE; + rtm->rtm_type = RTN_UNICAST; + rtm->rtm_table = RT_TABLE_MAIN; + rtm->rtm_protocol = rtm_protocol; + + return 0; +} + +int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + ndm->ndm_flags |= flags; + + return 0; +} + +int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + ndm->ndm_state |= state; + + return 0; +} + +int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + *flags = ndm->ndm_flags; + + return 0; +} + +int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + *state = ndm->ndm_state; + + return 0; +} + +int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + + *family = ndm->ndm_family; + + return 0; +} + +int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *index) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + assert_return(index, -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + + *index = ndm->ndm_ifindex; + + return 0; +} + +int sd_rtnl_message_new_neigh(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int index, int ndm_family) { + struct ndmsg *ndm; + int r; + + assert_return(rtnl_message_type_is_neigh(nlmsg_type), -EINVAL); + assert_return(IN_SET(ndm_family, AF_INET, AF_INET6, PF_BRIDGE), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWNEIGH) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + ndm = NLMSG_DATA((*ret)->hdr); + + ndm->ndm_family = ndm_family; + ndm->ndm_ifindex = index; + + return 0; +} + +int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(change, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_flags = flags; + ifi->ifi_change = change; + + return 0; +} + +int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_type = type; + + return 0; +} + +int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_family = family; + + return 0; +} + +int sd_rtnl_message_new_link(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int index) { + struct ifinfomsg *ifi; + int r; + + assert_return(rtnl_message_type_is_link(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWLINK) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + ifi = NLMSG_DATA((*ret)->hdr); + + ifi->ifi_family = AF_UNSPEC; + ifi->ifi_index = index; + + return 0; +} + +int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + if ((ifa->ifa_family == AF_INET && prefixlen > 32) || + (ifa->ifa_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + ifa->ifa_prefixlen = prefixlen; + + return 0; +} + +int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + ifa->ifa_flags = flags; + + return 0; +} + +int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + ifa->ifa_scope = scope; + + return 0; +} + +int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *family = ifa->ifa_family; + + return 0; +} + +int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(prefixlen, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *prefixlen = ifa->ifa_prefixlen; + + return 0; +} + +int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(scope, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *scope = ifa->ifa_scope; + + return 0; +} + +int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *flags = ifa->ifa_flags; + + return 0; +} + +int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(ifindex, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ifindex = ifa->ifa_index; + + return 0; +} + +int sd_rtnl_message_new_addr(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int index, + int family) { + struct ifaddrmsg *ifa; + int r; + + assert_return(rtnl_message_type_is_addr(nlmsg_type), -EINVAL); + assert_return((nlmsg_type == RTM_GETADDR && index == 0) || + index > 0, -EINVAL); + assert_return((nlmsg_type == RTM_GETADDR && family == AF_UNSPEC) || + IN_SET(family, AF_INET, AF_INET6), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_GETADDR) + (*ret)->hdr->nlmsg_flags |= NLM_F_DUMP; + + ifa = NLMSG_DATA((*ret)->hdr); + + ifa->ifa_index = index; + ifa->ifa_family = family; + if (family == AF_INET) + ifa->ifa_prefixlen = 32; + else if (family == AF_INET6) + ifa->ifa_prefixlen = 128; + + return 0; +} + +int sd_rtnl_message_new_addr_update(sd_netlink *rtnl, sd_netlink_message **ret, + int index, int family) { + int r; + + r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family); + if (r < 0) + return r; + + (*ret)->hdr->nlmsg_flags |= NLM_F_REPLACE; + + return 0; +} + +int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(ifindex, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *ifindex = ifi->ifi_index; + + return 0; +} + +int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *flags = ifi->ifi_flags; + + return 0; +} + +int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned short *type) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(type, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *type = ifi->ifi_type; + + return 0; +} + +int sd_rtnl_message_get_family(sd_netlink_message *m, int *family) { + assert_return(m, -EINVAL); + assert_return(family, -EINVAL); + + assert(m->hdr); + + if (rtnl_message_type_is_link(m->hdr->nlmsg_type)) { + struct ifinfomsg *ifi; + + ifi = NLMSG_DATA(m->hdr); + + *family = ifi->ifi_family; + + return 0; + } else if (rtnl_message_type_is_route(m->hdr->nlmsg_type)) { + struct rtmsg *rtm; + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + + return 0; + } else if (rtnl_message_type_is_neigh(m->hdr->nlmsg_type)) { + struct ndmsg *ndm; + + ndm = NLMSG_DATA(m->hdr); + + *family = ndm->ndm_family; + + return 0; + } else if (rtnl_message_type_is_addr(m->hdr->nlmsg_type)) { + struct ifaddrmsg *ifa; + + ifa = NLMSG_DATA(m->hdr); + + *family = ifa->ifa_family; + + return 0; + } else if (rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type)) { + struct rtmsg *rtm; + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + + return 0; + } + + return -EOPNOTSUPP; +} + +int sd_rtnl_message_new_addrlabel(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int ifindex, int ifal_family) { + struct ifaddrlblmsg *addrlabel; + int r; + + assert_return(rtnl_message_type_is_addrlabel(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWADDRLABEL) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + addrlabel = NLMSG_DATA((*ret)->hdr); + + addrlabel->ifal_family = ifal_family; + addrlabel->ifal_index = ifindex; + + return 0; +} + +int sd_rtnl_message_addrlabel_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct ifaddrlblmsg *addrlabel; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addrlabel(m->hdr->nlmsg_type), -EINVAL); + + addrlabel = NLMSG_DATA(m->hdr); + + if (prefixlen > 128) + return -ERANGE; + + addrlabel->ifal_prefixlen = prefixlen; + + return 0; +} + +int sd_rtnl_message_addrlabel_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen) { + struct ifaddrlblmsg *addrlabel; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addrlabel(m->hdr->nlmsg_type), -EINVAL); + + addrlabel = NLMSG_DATA(m->hdr); + + *prefixlen = addrlabel->ifal_prefixlen; + + return 0; +} + +int sd_rtnl_message_new_routing_policy_rule(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int ifal_family) { + struct rtmsg *rtm; + int r; + + assert_return(rtnl_message_type_is_routing_policy_rule(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWRULE) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + rtm = NLMSG_DATA((*ret)->hdr); + rtm->rtm_family = ifal_family; + rtm->rtm_protocol = RTPROT_BOOT; + rtm->rtm_scope = RT_SCOPE_UNIVERSE; + rtm->rtm_type = RTN_UNICAST; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_tos(sd_netlink_message *m, unsigned char tos) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_tos = tos; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_tos(sd_netlink_message *m, unsigned char *tos) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *tos = routing_policy_rule->rtm_tos; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_table(sd_netlink_message *m, unsigned char table) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_table = table; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_table(sd_netlink_message *m, unsigned char *table) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *table = routing_policy_rule->rtm_table; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_flags(sd_netlink_message *m, unsigned flags) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + routing_policy_rule->rtm_flags |= flags; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_flags(sd_netlink_message *m, unsigned *flags) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + *flags = routing_policy_rule->rtm_flags; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_rtm_type(sd_netlink_message *m, unsigned char type) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_type = type; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_rtm_type(sd_netlink_message *m, unsigned char *type) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *type = routing_policy_rule->rtm_type; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(sd_netlink_message *m, unsigned char len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_dst_len = len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(sd_netlink_message *m, unsigned char *len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *len = routing_policy_rule->rtm_dst_len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(sd_netlink_message *m, unsigned char len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_src_len = len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(sd_netlink_message *m, unsigned char *len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *len = routing_policy_rule->rtm_src_len; + + return 0; +} diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c new file mode 100644 index 0000000..d83952d --- /dev/null +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -0,0 +1,907 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <poll.h> +#include <sys/socket.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "macro.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-slot.h" +#include "netlink-util.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +static int sd_netlink_new(sd_netlink **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + + assert_return(ret, -EINVAL); + + rtnl = new(sd_netlink, 1); + if (!rtnl) + return -ENOMEM; + + *rtnl = (sd_netlink) { + .n_ref = REFCNT_INIT, + .fd = -1, + .sockaddr.nl.nl_family = AF_NETLINK, + .original_pid = getpid_cached(), + .protocol = -1, + + /* Change notification responses have sequence 0, so we must + * start our request sequence numbers at 1, or we may confuse our + * responses with notifications from the kernel */ + .serial = 1, + + }; + + /* We guarantee that the read buffer has at least space for + * a message header */ + if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, + sizeof(struct nlmsghdr), sizeof(uint8_t))) + return -ENOMEM; + + *ret = TAKE_PTR(rtnl); + + return 0; +} + +int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + socklen_t addrlen; + int r; + + assert_return(ret, -EINVAL); + + r = sd_netlink_new(&rtnl); + if (r < 0) + return r; + + addrlen = sizeof(rtnl->sockaddr); + + r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); + if (r < 0) + return -errno; + + if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) + return -EINVAL; + + rtnl->fd = fd; + + *ret = TAKE_PTR(rtnl); + + return 0; +} + +static bool rtnl_pid_changed(sd_netlink *rtnl) { + assert(rtnl); + + /* We don't support people creating an rtnl connection and + * keeping it around over a fork(). Let's complain. */ + + return rtnl->original_pid != getpid_cached(); +} + +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); + + r = sd_netlink_new(&rtnl); + 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; + } + + *ret = TAKE_PTR(rtnl); + + return 0; +} + +int netlink_open_family(sd_netlink **ret, int family) { + _cleanup_close_ int fd = -1; + int r; + + fd = socket_open(family); + if (fd < 0) + return fd; + + r = sd_netlink_open_fd(ret, fd); + if (r < 0) + return r; + + fd = -1; + + 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); + + return fd_inc_rcvbuf(rtnl->fd, size); +} + +static sd_netlink *netlink_free(sd_netlink *rtnl) { + sd_netlink_slot *s; + unsigned i; + + assert(rtnl); + + for (i = 0; i < rtnl->rqueue_size; i++) + sd_netlink_message_unref(rtnl->rqueue[i]); + free(rtnl->rqueue); + + for (i = 0; i < rtnl->rqueue_partial_size; i++) + sd_netlink_message_unref(rtnl->rqueue_partial[i]); + free(rtnl->rqueue_partial); + + free(rtnl->rbuffer); + + while ((s = rtnl->slots)) { + assert(s->floating); + netlink_slot_disconnect(s, true); + } + hashmap_free(rtnl->reply_callbacks); + prioq_free(rtnl->reply_callbacks_prioq); + + sd_event_source_unref(rtnl->io_event_source); + sd_event_source_unref(rtnl->time_event_source); + sd_event_unref(rtnl->event); + + hashmap_free(rtnl->broadcast_group_refs); + + safe_close(rtnl->fd); + return mfree(rtnl); +} + +DEFINE_ATOMIC_REF_UNREF_FUNC(sd_netlink, sd_netlink, netlink_free); + +static void rtnl_seal_message(sd_netlink *rtnl, sd_netlink_message *m) { + assert(rtnl); + assert(!rtnl_pid_changed(rtnl)); + assert(m); + assert(m->hdr); + + /* don't use seq == 0, as that is used for broadcasts, so we + would get confused by replies to such messages */ + m->hdr->nlmsg_seq = rtnl->serial++ ? : rtnl->serial++; + + rtnl_message_seal(m); + + return; +} + +int sd_netlink_send(sd_netlink *nl, + sd_netlink_message *message, + uint32_t *serial) { + int r; + + assert_return(nl, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + assert_return(message, -EINVAL); + assert_return(!message->sealed, -EPERM); + + rtnl_seal_message(nl, message); + + r = socket_write_message(nl, message); + if (r < 0) + return r; + + if (serial) + *serial = rtnl_message_get_serial(message); + + return 1; +} + +int rtnl_rqueue_make_room(sd_netlink *rtnl) { + assert(rtnl); + + if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), + "rtnl: exhausted the read queue size (%d)", + RTNL_RQUEUE_MAX); + + if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) + return -ENOMEM; + + return 0; +} + +int rtnl_rqueue_partial_make_room(sd_netlink *rtnl) { + assert(rtnl); + + if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), + "rtnl: exhausted the partial read queue size (%d)", + RTNL_RQUEUE_MAX); + + if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, + rtnl->rqueue_partial_size + 1)) + return -ENOMEM; + + return 0; +} + +static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { + int r; + + assert(rtnl); + assert(message); + + if (rtnl->rqueue_size <= 0) { + /* Try to read a new message */ + r = socket_read_message(rtnl); + if (r == -ENOBUFS) { /* FIXME: ignore buffer overruns for now */ + log_debug_errno(r, "Got ENOBUFS from netlink socket, ignoring."); + return 1; + } + if (r <= 0) + return r; + } + + /* Dispatch a queued message */ + *message = rtnl->rqueue[0]; + rtnl->rqueue_size--; + memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_netlink_message*) * rtnl->rqueue_size); + + return 1; +} + +static int process_timeout(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + struct reply_callback *c; + sd_netlink_slot *slot; + usec_t n; + int r; + + assert(rtnl); + + c = prioq_peek(rtnl->reply_callbacks_prioq); + if (!c) + return 0; + + n = now(CLOCK_MONOTONIC); + if (c->timeout > n) + return 0; + + r = rtnl_message_new_synthetic_error(rtnl, -ETIMEDOUT, c->serial, &m); + if (r < 0) + return r; + + assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); + c->timeout = 0; + hashmap_remove(rtnl->reply_callbacks, &c->serial); + + slot = container_of(c, sd_netlink_slot, reply_callback); + + r = c->callback(rtnl, m, slot->userdata); + if (r < 0) + log_debug_errno(r, "sd-netlink: timedout callback %s%s%sfailed: %m", + slot->description ? "'" : "", + strempty(slot->description), + slot->description ? "' " : ""); + + if (slot->floating) + netlink_slot_disconnect(slot, true); + + return 1; +} + +static int process_reply(sd_netlink *rtnl, sd_netlink_message *m) { + struct reply_callback *c; + sd_netlink_slot *slot; + uint64_t serial; + uint16_t type; + int r; + + assert(rtnl); + assert(m); + + serial = rtnl_message_get_serial(m); + c = hashmap_remove(rtnl->reply_callbacks, &serial); + if (!c) + return 0; + + if (c->timeout != 0) { + prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); + c->timeout = 0; + } + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + + if (type == NLMSG_DONE) + m = NULL; + + slot = container_of(c, sd_netlink_slot, reply_callback); + + r = c->callback(rtnl, m, slot->userdata); + if (r < 0) + log_debug_errno(r, "sd-netlink: reply callback %s%s%sfailed: %m", + slot->description ? "'" : "", + strempty(slot->description), + slot->description ? "' " : ""); + + if (slot->floating) + netlink_slot_disconnect(slot, true); + + return 1; +} + +static int process_match(sd_netlink *rtnl, sd_netlink_message *m) { + struct match_callback *c; + sd_netlink_slot *slot; + uint16_t type; + int r; + + assert(rtnl); + assert(m); + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + + LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { + if (type == c->type) { + slot = container_of(c, sd_netlink_slot, match_callback); + + r = c->callback(rtnl, m, slot->userdata); + if (r != 0) { + if (r < 0) + log_debug_errno(r, "sd-netlink: match callback %s%s%sfailed: %m", + slot->description ? "'" : "", + strempty(slot->description), + slot->description ? "' " : ""); + + break; + } + } + } + + return 1; +} + +static int process_running(sd_netlink *rtnl, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rtnl); + + r = process_timeout(rtnl); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(rtnl, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + if (sd_netlink_message_is_broadcast(m)) { + r = process_match(rtnl, m); + if (r != 0) + goto null_message; + } else { + r = process_reply(rtnl, m); + if (r != 0) + goto null_message; + } + + if (ret) { + *ret = TAKE_PTR(m); + + return 1; + } + + return 1; + +null_message: + if (r >= 0 && ret) + *ret = NULL; + + return r; +} + +int sd_netlink_process(sd_netlink *rtnl, sd_netlink_message **ret) { + NETLINK_DONT_DESTROY(rtnl); + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + assert_return(!rtnl->processing, -EBUSY); + + rtnl->processing = true; + r = process_running(rtnl, ret); + rtnl->processing = false; + + return r; +} + +static usec_t calc_elapse(uint64_t usec) { + if (usec == (uint64_t) -1) + return 0; + + if (usec == 0) + usec = RTNL_DEFAULT_TIMEOUT; + + return now(CLOCK_MONOTONIC) + usec; +} + +static int rtnl_poll(sd_netlink *rtnl, bool need_more, uint64_t timeout_usec) { + struct pollfd p[1] = {}; + struct timespec ts; + usec_t m = USEC_INFINITY; + int r, e; + + assert(rtnl); + + e = sd_netlink_get_events(rtnl); + if (e < 0) + return e; + + if (need_more) + /* Caller wants more data, and doesn't care about + * what's been read or any other timeouts. */ + e |= POLLIN; + else { + usec_t until; + /* Caller wants to process if there is something to + * process, but doesn't care otherwise */ + + r = sd_netlink_get_timeout(rtnl, &until); + if (r < 0) + return r; + if (r > 0) { + usec_t nw; + nw = now(CLOCK_MONOTONIC); + m = until > nw ? until - nw : 0; + } + } + + if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) + m = timeout_usec; + + p[0].fd = rtnl->fd; + p[0].events = e; + + r = ppoll(p, 1, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); + if (r < 0) + return -errno; + + return r > 0 ? 1 : 0; +} + +int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { + assert_return(nl, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + if (nl->rqueue_size > 0) + return 0; + + return rtnl_poll(nl, false, timeout_usec); +} + +static int timeout_compare(const void *a, const void *b) { + const struct reply_callback *x = a, *y = b; + + if (x->timeout != 0 && y->timeout == 0) + return -1; + + if (x->timeout == 0 && y->timeout != 0) + return 1; + + return CMP(x->timeout, y->timeout); +} + +int sd_netlink_call_async( + sd_netlink *nl, + sd_netlink_slot **ret_slot, + sd_netlink_message *m, + sd_netlink_message_handler_t callback, + sd_netlink_destroy_t destroy_callback, + void *userdata, + uint64_t usec, + const char *description) { + _cleanup_free_ sd_netlink_slot *slot = NULL; + uint32_t s; + int r, k; + + assert_return(nl, -EINVAL); + assert_return(m, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + r = hashmap_ensure_allocated(&nl->reply_callbacks, &uint64_hash_ops); + if (r < 0) + return r; + + if (usec != (uint64_t) -1) { + r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); + if (r < 0) + return r; + } + + r = netlink_slot_allocate(nl, !ret_slot, NETLINK_REPLY_CALLBACK, sizeof(struct reply_callback), userdata, description, &slot); + if (r < 0) + return r; + + slot->reply_callback.callback = callback; + slot->reply_callback.timeout = calc_elapse(usec); + + k = sd_netlink_send(nl, m, &s); + if (k < 0) + return k; + + slot->reply_callback.serial = s; + + r = hashmap_put(nl->reply_callbacks, &slot->reply_callback.serial, &slot->reply_callback); + if (r < 0) + return r; + + if (slot->reply_callback.timeout != 0) { + r = prioq_put(nl->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); + if (r < 0) { + (void) hashmap_remove(nl->reply_callbacks, &slot->reply_callback.serial); + return r; + } + } + + /* Set this at last. Otherwise, some failures in above call the destroy callback but some do not. */ + slot->destroy_callback = destroy_callback; + + if (ret_slot) + *ret_slot = slot; + + TAKE_PTR(slot); + + return k; +} + +int sd_netlink_call(sd_netlink *rtnl, + sd_netlink_message *message, + uint64_t usec, + sd_netlink_message **ret) { + usec_t timeout; + uint32_t serial; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + assert_return(message, -EINVAL); + + r = sd_netlink_send(rtnl, message, &serial); + if (r < 0) + return r; + + timeout = calc_elapse(usec); + + for (;;) { + usec_t left; + unsigned i; + + for (i = 0; i < rtnl->rqueue_size; i++) { + uint32_t received_serial; + + received_serial = rtnl_message_get_serial(rtnl->rqueue[i]); + + if (received_serial == serial) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; + uint16_t type; + + incoming = rtnl->rqueue[i]; + + /* found a match, remove from rqueue and return it */ + memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, + sizeof(sd_netlink_message*) * (rtnl->rqueue_size - i - 1)); + rtnl->rqueue_size--; + + r = sd_netlink_message_get_errno(incoming); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(incoming, &type); + if (r < 0) + return r; + + if (type == NLMSG_DONE) { + *ret = NULL; + return 0; + } + + if (ret) + *ret = TAKE_PTR(incoming); + + return 1; + } + } + + r = socket_read_message(rtnl); + if (r < 0) + return r; + if (r > 0) + /* received message, so try to process straight away */ + continue; + + if (timeout > 0) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (n >= timeout) + return -ETIMEDOUT; + + left = timeout - n; + } else + left = (uint64_t) -1; + + r = rtnl_poll(rtnl, true, left); + if (r < 0) + return r; + else if (r == 0) + return -ETIMEDOUT; + } +} + +int sd_netlink_get_events(sd_netlink *rtnl) { + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + if (rtnl->rqueue_size == 0) + return POLLIN; + else + return 0; +} + +int sd_netlink_get_timeout(sd_netlink *rtnl, uint64_t *timeout_usec) { + struct reply_callback *c; + + assert_return(rtnl, -EINVAL); + assert_return(timeout_usec, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + if (rtnl->rqueue_size > 0) { + *timeout_usec = 0; + return 1; + } + + c = prioq_peek(rtnl->reply_callbacks_prioq); + if (!c) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + *timeout_usec = c->timeout; + + return 1; +} + +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_netlink *rtnl = userdata; + int r; + + assert(rtnl); + + r = sd_netlink_process(rtnl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { + sd_netlink *rtnl = userdata; + int r; + + assert(rtnl); + + r = sd_netlink_process(rtnl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int prepare_callback(sd_event_source *s, void *userdata) { + sd_netlink *rtnl = userdata; + int r, e; + usec_t until; + + assert(s); + assert(rtnl); + + e = sd_netlink_get_events(rtnl); + if (e < 0) + return e; + + r = sd_event_source_set_io_events(rtnl->io_event_source, e); + if (r < 0) + return r; + + r = sd_netlink_get_timeout(rtnl, &until); + if (r < 0) + return r; + if (r > 0) { + int j; + + j = sd_event_source_set_time(rtnl->time_event_source, until); + if (j < 0) + return j; + } + + r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); + if (r < 0) + return r; + + return 1; +} + +int sd_netlink_attach_event(sd_netlink *rtnl, sd_event *event, int64_t priority) { + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl->event, -EBUSY); + + assert(!rtnl->io_event_source); + assert(!rtnl->time_event_source); + + if (event) + rtnl->event = sd_event_ref(event); + else { + r = sd_event_default(&rtnl->event); + if (r < 0) + return r; + } + + r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(rtnl->io_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); + if (r < 0) + goto fail; + + r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); + if (r < 0) + goto fail; + + r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(rtnl->time_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_netlink_detach_event(rtnl); + return r; +} + +int sd_netlink_detach_event(sd_netlink *rtnl) { + assert_return(rtnl, -EINVAL); + assert_return(rtnl->event, -ENXIO); + + rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); + + rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); + + rtnl->event = sd_event_unref(rtnl->event); + + return 0; +} + +int sd_netlink_add_match( + sd_netlink *rtnl, + sd_netlink_slot **ret_slot, + uint16_t type, + sd_netlink_message_handler_t callback, + sd_netlink_destroy_t destroy_callback, + void *userdata, + const char *description) { + _cleanup_free_ sd_netlink_slot *slot = NULL; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + r = netlink_slot_allocate(rtnl, !ret_slot, NETLINK_MATCH_CALLBACK, sizeof(struct match_callback), userdata, description, &slot); + if (r < 0) + return r; + + slot->match_callback.callback = callback; + slot->match_callback.type = type; + + switch (type) { + case RTM_NEWLINK: + case RTM_DELLINK: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_LINK); + if (r < 0) + return r; + + break; + case RTM_NEWADDR: + case RTM_DELADDR: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_IFADDR); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_IFADDR); + if (r < 0) + return r; + + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_ROUTE); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_ROUTE); + if (r < 0) + return r; + break; + case RTM_NEWRULE: + case RTM_DELRULE: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_RULE); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_RULE); + if (r < 0) + return r; + break; + default: + return -EOPNOTSUPP; + } + + LIST_PREPEND(match_callbacks, rtnl->match_callbacks, &slot->match_callback); + + /* Set this at last. Otherwise, some failures in above call the destroy callback but some do not. */ + slot->destroy_callback = destroy_callback; + + if (ret_slot) + *ret_slot = slot; + + TAKE_PTR(slot); + + return 0; +} diff --git a/src/libsystemd/sd-netlink/test-local-addresses.c b/src/libsystemd/sd-netlink/test-local-addresses.c new file mode 100644 index 0000000..1711426 --- /dev/null +++ b/src/libsystemd/sd-netlink/test-local-addresses.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "af-list.h" +#include "alloc-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "tests.h" + +static void print_local_addresses(struct local_address *a, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + _cleanup_free_ char *b = NULL; + + assert_se(in_addr_to_string(a[i].family, &a[i].address, &b) >= 0); + printf("%s if%i scope=%i metric=%u address=%s\n", af_to_name(a[i].family), a[i].ifindex, a[i].scope, a[i].metric, b); + } +} + +int main(int argc, char *argv[]) { + struct local_address *a; + int n; + + test_setup_logging(LOG_DEBUG); + + a = NULL; + n = local_addresses(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + + printf("Local Addresses:\n"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_gateways(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + + printf("Local Gateways:\n"); + print_local_addresses(a, (unsigned) n); + free(a); + + return 0; +} diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c new file mode 100644 index 0000000..b13fa22 --- /dev/null +++ b/src/libsystemd/sd-netlink/test-netlink.c @@ -0,0 +1,567 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <net/if.h> +#include <netinet/ether.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "macro.h" +#include "missing.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +static void test_message_link_bridge(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + uint32_t cost; + + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); + assert_se(sd_rtnl_message_link_set_family(message, PF_BRIDGE) >= 0); + assert_se(sd_netlink_message_open_container(message, IFLA_PROTINFO) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFLA_BRPORT_COST, 10) >= 0); + assert_se(sd_netlink_message_close_container(message) >= 0); + + assert_se(sd_netlink_message_rewind(message) >= 0); + + assert_se(sd_netlink_message_enter_container(message, IFLA_PROTINFO) >= 0); + assert_se(sd_netlink_message_read_u32(message, IFLA_BRPORT_COST, &cost) >= 0); + assert_se(cost == 10); + assert_se(sd_netlink_message_exit_container(message) >= 0); +} + +static void test_link_configure(sd_netlink *rtnl, int ifindex) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + const char *mac = "98:fe:94:3f:c6:18", *name = "test"; + char buffer[ETHER_ADDR_TO_STRING_MAX]; + uint32_t mtu = 1450, mtu_out; + const char *name_out; + struct ether_addr mac_out; + + /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, name) >= 0); + assert_se(sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, ether_aton(mac)) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFLA_MTU, mtu) >= 0); + + assert_se(sd_netlink_call(rtnl, message, 0, NULL) == 1); + assert_se(sd_netlink_message_rewind(message) >= 0); + + assert_se(sd_netlink_message_read_string(message, IFLA_IFNAME, &name_out) >= 0); + assert_se(streq(name, name_out)); + + assert_se(sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &mac_out) >= 0); + assert_se(streq(mac, ether_addr_to_string(&mac_out, buffer))); + + assert_se(sd_netlink_message_read_u32(message, IFLA_MTU, &mtu_out) >= 0); + assert_se(mtu == mtu_out); +} + +static void test_link_get(sd_netlink *rtnl, int ifindex) { + sd_netlink_message *m; + sd_netlink_message *r; + uint32_t mtu = 1500; + const char *str_data; + uint8_t u8_data; + uint32_t u32_data; + struct ether_addr eth_data; + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + assert_se(m); + + /* u8 test cases */ + assert_se(sd_netlink_message_append_u8(m, IFLA_CARRIER, 0) >= 0); + assert_se(sd_netlink_message_append_u8(m, IFLA_OPERSTATE, 0) >= 0); + assert_se(sd_netlink_message_append_u8(m, IFLA_LINKMODE, 0) >= 0); + + /* u32 test cases */ + assert_se(sd_netlink_message_append_u32(m, IFLA_MTU, mtu) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_GROUP, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_TXQLEN, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_TX_QUEUES, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_RX_QUEUES, 0) >= 0); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); + + assert_se(sd_netlink_message_read_string(r, IFLA_IFNAME, &str_data) == 0); + + assert_se(sd_netlink_message_read_u8(r, IFLA_CARRIER, &u8_data) == 0); + assert_se(sd_netlink_message_read_u8(r, IFLA_OPERSTATE, &u8_data) == 0); + assert_se(sd_netlink_message_read_u8(r, IFLA_LINKMODE, &u8_data) == 0); + + assert_se(sd_netlink_message_read_u32(r, IFLA_MTU, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_GROUP, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_TXQLEN, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_TX_QUEUES, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_RX_QUEUES, &u32_data) == 0); + + assert_se(sd_netlink_message_read_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); +} + +static void test_address_get(sd_netlink *rtnl, int ifindex) { + sd_netlink_message *m; + sd_netlink_message *r; + struct in_addr in_data; + struct ifa_cacheinfo cache; + const char *label; + + assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); + assert_se(m); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); + + assert_se(sd_netlink_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0); + assert_se(sd_netlink_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0); + assert_se(sd_netlink_message_read_string(r, IFA_LABEL, &label) == 0); + assert_se(sd_netlink_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + +} + +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(rtnl, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC); + if (r < 0) { + log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); + return; + } + + addr.s_addr = htonl(INADDR_LOOPBACK); + + r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr); + if (r < 0) { + log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); + return; + } + + r = sd_netlink_message_append_u32(req, RTA_OIF, index); + if (r < 0) { + log_error_errno(r, "Could not append RTA_OIF attribute: %m"); + return; + } + + assert_se(sd_netlink_message_rewind(req) >= 0); + + assert_se(sd_netlink_message_read_in_addr(req, RTA_GATEWAY, &addr_data) >= 0); + assert_se(addr_data.s_addr == addr.s_addr); + + assert_se(sd_netlink_message_read_u32(req, RTA_OIF, &u32_data) >= 0); + assert_se(u32_data == index); + + assert_se((req = sd_netlink_message_unref(req)) == NULL); +} + +static void test_multiple(void) { + sd_netlink *rtnl1, *rtnl2; + + assert_se(sd_netlink_open(&rtnl1) >= 0); + assert_se(sd_netlink_open(&rtnl2) >= 0); + + rtnl1 = sd_netlink_unref(rtnl1); + rtnl2 = sd_netlink_unref(rtnl2); +} + +static int link_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + char *ifname = userdata; + const char *data; + + assert_se(rtnl); + assert_se(m); + assert_se(userdata); + + log_info("%s: got link info about %s", __func__, ifname); + free(ifname); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); + assert_se(streq(data, "lo")); + + return 1; +} + +static void test_event_loop(int ifindex) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + char *ifname; + + ifname = strdup("lo2"); + assert_se(ifname); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + + assert_se(sd_netlink_call_async(rtnl, NULL, m, link_handler, NULL, ifname, 0, NULL) >= 0); + + assert_se(sd_event_default(&event) >= 0); + + assert_se(sd_netlink_attach_event(rtnl, event, 0) >= 0); + + assert_se(sd_event_run(event, 0) >= 0); + + assert_se(sd_netlink_detach_event(rtnl) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_async_destroy(void *userdata) { +} + +static void test_async(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; + sd_netlink_destroy_t destroy_callback; + const char *description; + char *ifname; + + ifname = strdup("lo"); + assert_se(ifname); + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + + assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, test_async_destroy, ifname, 0, "hogehoge") >= 0); + + assert_se(sd_netlink_slot_get_netlink(slot) == rtnl); + assert_se(sd_netlink_slot_get_userdata(slot) == ifname); + assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); + assert_se(destroy_callback == test_async_destroy); + assert_se(sd_netlink_slot_get_floating(slot) == 0); + assert_se(sd_netlink_slot_get_description(slot, &description) == 1); + assert_se(streq(description, "hogehoge")); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, &r) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_slot_set(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; + sd_netlink_destroy_t destroy_callback; + const char *description; + char *ifname; + + ifname = strdup("lo"); + assert_se(ifname); + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + + assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, NULL, NULL, 0, NULL) >= 0); + + assert_se(sd_netlink_slot_get_netlink(slot) == rtnl); + assert_se(!sd_netlink_slot_get_userdata(slot)); + assert_se(!sd_netlink_slot_set_userdata(slot, ifname)); + assert_se(sd_netlink_slot_get_userdata(slot) == ifname); + assert_se(sd_netlink_slot_get_destroy_callback(slot, NULL) == 0); + assert_se(sd_netlink_slot_set_destroy_callback(slot, test_async_destroy) >= 0); + assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); + assert_se(destroy_callback == test_async_destroy); + assert_se(sd_netlink_slot_get_floating(slot) == 0); + assert_se(sd_netlink_slot_set_floating(slot, 1) == 1); + assert_se(sd_netlink_slot_get_floating(slot) == 1); + assert_se(sd_netlink_slot_get_description(slot, NULL) == 0); + assert_se(sd_netlink_slot_set_description(slot, "hogehoge") >= 0); + assert_se(sd_netlink_slot_get_description(slot, &description) == 1); + assert_se(streq(description, "hogehoge")); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, &r) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +struct test_async_object { + unsigned n_ref; + char *ifname; +}; + +static struct test_async_object *test_async_object_free(struct test_async_object *t) { + assert(t); + + free(t->ifname); + return mfree(t); +} + +DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(struct test_async_object, test_async_object, test_async_object_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct test_async_object *, test_async_object_unref); + +static int link_handler2(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + struct test_async_object *t = userdata; + const char *data; + + assert_se(rtnl); + assert_se(m); + assert_se(userdata); + + log_info("%s: got link info about %s", __func__, t->ifname); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); + assert_se(streq(data, "lo")); + + return 1; +} + +static void test_async_object_destroy(void *userdata) { + struct test_async_object *t = userdata; + + assert(userdata); + + log_info("%s: n_ref=%u", __func__, t->n_ref); + test_async_object_unref(t); +} + +static void test_async_destroy_callback(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; + _cleanup_(test_async_object_unrefp) struct test_async_object *t = NULL; + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; + char *ifname; + + assert_se(t = new(struct test_async_object, 1)); + assert_se(ifname = strdup("lo")); + *t = (struct test_async_object) { + .n_ref = 1, + .ifname = ifname, + }; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + /* destroy callback is called after processing message */ + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_netlink_call_async(rtnl, NULL, m, link_handler2, test_async_object_destroy, t, 0, NULL) >= 0); + + assert_se(t->n_ref == 1); + assert_se(test_async_object_ref(t)); + assert_se(t->n_ref == 2); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, &r) == 1); + assert_se(t->n_ref == 1); + + assert_se(!sd_netlink_message_unref(m)); + + /* destroy callback is called when asynchronous call is cancelled, that is, slot is freed. */ + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler2, test_async_object_destroy, t, 0, NULL) >= 0); + + assert_se(t->n_ref == 1); + assert_se(test_async_object_ref(t)); + assert_se(t->n_ref == 2); + + assert_se(!(slot = sd_netlink_slot_unref(slot))); + assert_se(t->n_ref == 1); + + assert_se(!sd_netlink_message_unref(m)); + + /* destroy callback is also called by sd_netlink_unref() */ + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_netlink_call_async(rtnl, NULL, m, link_handler2, test_async_object_destroy, t, 0, NULL) >= 0); + + assert_se(t->n_ref == 1); + assert_se(test_async_object_ref(t)); + assert_se(t->n_ref == 2); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + assert_se(t->n_ref == 1); +} + +static int pipe_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + int *counter = userdata; + int r; + + (*counter)--; + + r = sd_netlink_message_get_errno(m); + + log_info_errno(r, "%d left in pipe. got reply: %m", *counter); + + assert_se(r >= 0); + + return 1; +} + +static void test_pipe(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m1 = NULL, *m2 = NULL; + int counter = 0; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m1, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m2, RTM_GETLINK, ifindex) >= 0); + + counter++; + assert_se(sd_netlink_call_async(rtnl, NULL, m1, pipe_handler, NULL, &counter, 0, NULL) >= 0); + + counter++; + assert_se(sd_netlink_call_async(rtnl, NULL, m2, pipe_handler, NULL, &counter, 0, NULL) >= 0); + + while (counter > 0) { + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, NULL) >= 0); + } + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +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(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); + assert_se(sd_netlink_message_append_u16(m, IFLA_VLAN_ID, 100) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_INFO_KIND, "vlan") >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + assert_se(sd_netlink_message_close_container(m) == -EINVAL); + + assert_se(sd_netlink_message_rewind(m) >= 0); + + assert_se(sd_netlink_message_enter_container(m, IFLA_LINKINFO) >= 0); + assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); + assert_se(streq("vlan", string_data)); + + assert_se(sd_netlink_message_enter_container(m, IFLA_INFO_DATA) >= 0); + assert_se(sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &u16_data) >= 0); + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); + assert_se(streq("vlan", string_data)); + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_u32(m, IFLA_LINKINFO, &u32_data) < 0); + + assert_se(sd_netlink_message_exit_container(m) == -EINVAL); +} + +static void test_match(void) { + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *s1 = NULL, *s2 = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_netlink_add_match(rtnl, &s1, RTM_NEWLINK, link_handler, NULL, NULL, NULL) >= 0); + assert_se(sd_netlink_add_match(rtnl, &s2, RTM_NEWLINK, link_handler, NULL, NULL, NULL) >= 0); + assert_se(sd_netlink_add_match(rtnl, NULL, RTM_NEWLINK, link_handler, NULL, NULL, NULL) >= 0); + + assert_se(!(s1 = sd_netlink_slot_unref(s1))); + assert_se(!(s2 = sd_netlink_slot_unref(s2))); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_get_addresses(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *m; + + assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); + + assert_se(sd_netlink_call(rtnl, req, 0, &reply) >= 0); + + for (m = reply; m; m = sd_netlink_message_next(m)) { + uint16_t type; + unsigned char scope, flags; + int family, ifindex; + + assert_se(sd_netlink_message_get_type(m, &type) >= 0); + assert_se(type == RTM_NEWADDR); + + assert_se(sd_rtnl_message_addr_get_ifindex(m, &ifindex) >= 0); + assert_se(sd_rtnl_message_addr_get_family(m, &family) >= 0); + assert_se(sd_rtnl_message_addr_get_scope(m, &scope) >= 0); + assert_se(sd_rtnl_message_addr_get_flags(m, &flags) >= 0); + + assert_se(ifindex > 0); + assert_se(IN_SET(family, AF_INET, AF_INET6)); + + log_info("got IPv%u address on ifindex %i", family == AF_INET ? 4: 6, ifindex); + } +} + +static void test_message(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + + assert_se(rtnl_message_new_synthetic_error(rtnl, -ETIMEDOUT, 1, &m) >= 0); + assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); +} + +int main(void) { + sd_netlink *rtnl; + sd_netlink_message *m; + sd_netlink_message *r; + const char *string_data; + int if_loopback; + uint16_t type; + + test_match(); + test_multiple(); + + 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); + + test_async(if_loopback); + test_slot_set(if_loopback); + test_async_destroy_callback(if_loopback); + test_pipe(if_loopback); + test_event_loop(if_loopback); + test_link_configure(rtnl, if_loopback); + + test_get_addresses(rtnl); + test_message_link_bridge(rtnl); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, if_loopback) >= 0); + assert_se(m); + + assert_se(sd_netlink_message_get_type(m, &type) >= 0); + assert_se(type == RTM_GETLINK); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &string_data) == -EPERM); + + assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); + assert_se(sd_netlink_message_get_type(r, &type) >= 0); + assert_se(type == RTM_NEWLINK); + + assert_se((r = sd_netlink_message_unref(r)) == NULL); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == -EPERM); + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + + test_link_get(rtnl, if_loopback); + test_address_get(rtnl, if_loopback); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + + return EXIT_SUCCESS; +} |