diff options
Diffstat (limited to 'src/libsystemd/sd-netlink')
19 files changed, 8856 insertions, 0 deletions
diff --git a/src/libsystemd/sd-netlink/netlink-genl.c b/src/libsystemd/sd-netlink/netlink-genl.c new file mode 100644 index 0000000..1dc62e8 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-genl.c @@ -0,0 +1,488 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/genetlink.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "netlink-genl.h" +#include "netlink-internal.h" +#include "netlink-types.h" + +typedef struct GenericNetlinkFamily { + sd_netlink *genl; + + const NLAPolicySet *policy_set; + + uint16_t id; /* a.k.a nlmsg_type */ + char *name; + uint32_t version; + uint32_t additional_header_size; + Hashmap *multicast_group_by_name; +} GenericNetlinkFamily; + +static const GenericNetlinkFamily nlctrl_static = { + .id = GENL_ID_CTRL, + .name = (char*) CTRL_GENL_NAME, + .version = 0x01, +}; + +static GenericNetlinkFamily *genl_family_free(GenericNetlinkFamily *f) { + if (!f) + return NULL; + + if (f->genl) { + if (f->id > 0) + hashmap_remove(f->genl->genl_family_by_id, UINT_TO_PTR(f->id)); + if (f->name) + hashmap_remove(f->genl->genl_family_by_name, f->name); + } + + free(f->name); + hashmap_free(f->multicast_group_by_name); + + return mfree(f); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(GenericNetlinkFamily*, genl_family_free); + +void genl_clear_family(sd_netlink *nl) { + assert(nl); + + nl->genl_family_by_name = hashmap_free_with_destructor(nl->genl_family_by_name, genl_family_free); + nl->genl_family_by_id = hashmap_free_with_destructor(nl->genl_family_by_id, genl_family_free); +} + +static int genl_family_new_unsupported( + sd_netlink *nl, + const char *family_name, + const NLAPolicySet *policy_set) { + + _cleanup_(genl_family_freep) GenericNetlinkFamily *f = NULL; + int r; + + assert(nl); + assert(family_name); + assert(policy_set); + + /* Kernel does not support the genl family? To prevent from resolving the family name again, + * let's store the family with zero id to indicate that. */ + + f = new(GenericNetlinkFamily, 1); + if (!f) + return -ENOMEM; + + *f = (GenericNetlinkFamily) { + .policy_set = policy_set, + }; + + f->name = strdup(family_name); + if (!f->name) + return -ENOMEM; + + r = hashmap_ensure_put(&nl->genl_family_by_name, &string_hash_ops, f->name, f); + if (r < 0) + return r; + + f->genl = nl; + TAKE_PTR(f); + return 0; +} + +static int genl_family_new( + sd_netlink *nl, + const char *expected_family_name, + const NLAPolicySet *policy_set, + sd_netlink_message *message, + const GenericNetlinkFamily **ret) { + + _cleanup_(genl_family_freep) GenericNetlinkFamily *f = NULL; + const char *family_name; + uint8_t cmd; + int r; + + assert(nl); + assert(expected_family_name); + assert(policy_set); + assert(message); + assert(ret); + + f = new(GenericNetlinkFamily, 1); + if (!f) + return -ENOMEM; + + *f = (GenericNetlinkFamily) { + .policy_set = policy_set, + }; + + r = sd_genl_message_get_family_name(nl, message, &family_name); + if (r < 0) + return r; + + if (!streq(family_name, CTRL_GENL_NAME)) + return -EINVAL; + + r = sd_genl_message_get_command(nl, message, &cmd); + if (r < 0) + return r; + + if (cmd != CTRL_CMD_NEWFAMILY) + return -EINVAL; + + r = sd_netlink_message_read_u16(message, CTRL_ATTR_FAMILY_ID, &f->id); + if (r < 0) + return r; + + r = sd_netlink_message_read_string_strdup(message, CTRL_ATTR_FAMILY_NAME, &f->name); + if (r < 0) + return r; + + if (!streq(f->name, expected_family_name)) + return -EINVAL; + + r = sd_netlink_message_read_u32(message, CTRL_ATTR_VERSION, &f->version); + if (r < 0) + return r; + + r = sd_netlink_message_read_u32(message, CTRL_ATTR_HDRSIZE, &f->additional_header_size); + if (r < 0) + return r; + + r = sd_netlink_message_enter_container(message, CTRL_ATTR_MCAST_GROUPS); + if (r >= 0) { + for (uint16_t i = 0; i < UINT16_MAX; i++) { + _cleanup_free_ char *group_name = NULL; + uint32_t group_id; + + r = sd_netlink_message_enter_array(message, i + 1); + if (r == -ENODATA) + break; + if (r < 0) + return r; + + r = sd_netlink_message_read_u32(message, CTRL_ATTR_MCAST_GRP_ID, &group_id); + if (r < 0) + return r; + + r = sd_netlink_message_read_string_strdup(message, CTRL_ATTR_MCAST_GRP_NAME, &group_name); + if (r < 0) + return r; + + r = sd_netlink_message_exit_container(message); + if (r < 0) + return r; + + if (group_id == 0) { + log_debug("sd-netlink: received multicast group '%s' for generic netlink family '%s' with id == 0, ignoring", + group_name, f->name); + continue; + } + + r = hashmap_ensure_put(&f->multicast_group_by_name, &string_hash_ops_free, group_name, UINT32_TO_PTR(group_id)); + if (r < 0) + return r; + + TAKE_PTR(group_name); + } + + r = sd_netlink_message_exit_container(message); + if (r < 0) + return r; + } + + r = hashmap_ensure_put(&nl->genl_family_by_id, NULL, UINT_TO_PTR(f->id), f); + if (r < 0) + return r; + + r = hashmap_ensure_put(&nl->genl_family_by_name, &string_hash_ops, f->name, f); + if (r < 0) { + hashmap_remove(nl->genl_family_by_id, UINT_TO_PTR(f->id)); + return r; + } + + f->genl = nl; + *ret = TAKE_PTR(f); + return 0; +} + +static const NLAPolicySet *genl_family_get_policy_set(const GenericNetlinkFamily *family) { + assert(family); + + if (family->policy_set) + return family->policy_set; + + return genl_get_policy_set_by_name(family->name); +} + +static int genl_message_new( + sd_netlink *nl, + const GenericNetlinkFamily *family, + uint8_t cmd, + sd_netlink_message **ret) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + const NLAPolicySet *policy_set; + int r; + + assert(nl); + assert(nl->protocol == NETLINK_GENERIC); + assert(family); + assert(ret); + + policy_set = genl_family_get_policy_set(family); + if (!policy_set) + return -EOPNOTSUPP; + + r = message_new_full(nl, family->id, policy_set, + sizeof(struct genlmsghdr) + family->additional_header_size, &m); + if (r < 0) + return r; + + *(struct genlmsghdr *) NLMSG_DATA(m->hdr) = (struct genlmsghdr) { + .cmd = cmd, + .version = family->version, + }; + + *ret = TAKE_PTR(m); + return 0; +} + +static int genl_family_get_by_name_internal( + sd_netlink *nl, + const GenericNetlinkFamily *ctrl, + const char *name, + const GenericNetlinkFamily **ret) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + const NLAPolicySet *policy_set; + int r; + + assert(nl); + assert(nl->protocol == NETLINK_GENERIC); + assert(ctrl); + assert(name); + assert(ret); + + policy_set = genl_get_policy_set_by_name(name); + if (!policy_set) + return -EOPNOTSUPP; + + r = genl_message_new(nl, ctrl, CTRL_CMD_GETFAMILY, &req); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(req, CTRL_ATTR_FAMILY_NAME, name); + if (r < 0) + return r; + + if (sd_netlink_call(nl, req, 0, &reply) < 0) { + (void) genl_family_new_unsupported(nl, name, policy_set); + return -EOPNOTSUPP; + } + + return genl_family_new(nl, name, policy_set, reply, ret); +} + +static int genl_family_get_by_name(sd_netlink *nl, const char *name, const GenericNetlinkFamily **ret) { + const GenericNetlinkFamily *f, *ctrl; + int r; + + assert(nl); + assert(nl->protocol == NETLINK_GENERIC); + assert(name); + assert(ret); + + f = hashmap_get(nl->genl_family_by_name, name); + if (f) { + if (f->id == 0) /* kernel does not support the family. */ + return -EOPNOTSUPP; + + *ret = f; + return 0; + } + + if (streq(name, CTRL_GENL_NAME)) + return genl_family_get_by_name_internal(nl, &nlctrl_static, CTRL_GENL_NAME, ret); + + ctrl = hashmap_get(nl->genl_family_by_name, CTRL_GENL_NAME); + if (!ctrl) { + r = genl_family_get_by_name_internal(nl, &nlctrl_static, CTRL_GENL_NAME, &ctrl); + if (r < 0) + return r; + } + + return genl_family_get_by_name_internal(nl, ctrl, name, ret); +} + +static int genl_family_get_by_id(sd_netlink *nl, uint16_t id, const GenericNetlinkFamily **ret) { + const GenericNetlinkFamily *f; + + assert(nl); + assert(nl->protocol == NETLINK_GENERIC); + assert(ret); + + f = hashmap_get(nl->genl_family_by_id, UINT_TO_PTR(id)); + if (f) { + *ret = f; + return 0; + } + + if (id == GENL_ID_CTRL) { + *ret = &nlctrl_static; + return 0; + } + + return -ENOENT; +} + +int genl_get_policy_set_and_header_size( + sd_netlink *nl, + uint16_t id, + const NLAPolicySet **ret_policy_set, + size_t *ret_header_size) { + + const GenericNetlinkFamily *f; + int r; + + assert(nl); + assert(nl->protocol == NETLINK_GENERIC); + + r = genl_family_get_by_id(nl, id, &f); + if (r < 0) + return r; + + if (ret_policy_set) { + const NLAPolicySet *p; + + p = genl_family_get_policy_set(f); + if (!p) + return -EOPNOTSUPP; + + *ret_policy_set = p; + } + if (ret_header_size) + *ret_header_size = sizeof(struct genlmsghdr) + f->additional_header_size; + return 0; +} + +int sd_genl_message_new(sd_netlink *nl, const char *family_name, uint8_t cmd, sd_netlink_message **ret) { + const GenericNetlinkFamily *family; + int r; + + assert_return(nl, -EINVAL); + assert_return(nl->protocol == NETLINK_GENERIC, -EINVAL); + assert_return(family_name, -EINVAL); + assert_return(ret, -EINVAL); + + r = genl_family_get_by_name(nl, family_name, &family); + if (r < 0) + return r; + + return genl_message_new(nl, family, cmd, ret); +} + +int sd_genl_message_get_family_name(sd_netlink *nl, sd_netlink_message *m, const char **ret) { + const GenericNetlinkFamily *family; + uint16_t nlmsg_type; + int r; + + assert_return(nl, -EINVAL); + assert_return(nl->protocol == NETLINK_GENERIC, -EINVAL); + assert_return(m, -EINVAL); + assert_return(ret, -EINVAL); + + r = sd_netlink_message_get_type(m, &nlmsg_type); + if (r < 0) + return r; + + r = genl_family_get_by_id(nl, nlmsg_type, &family); + if (r < 0) + return r; + + *ret = family->name; + return 0; +} + +int sd_genl_message_get_command(sd_netlink *nl, sd_netlink_message *m, uint8_t *ret) { + struct genlmsghdr *h; + uint16_t nlmsg_type; + size_t size; + int r; + + assert_return(nl, -EINVAL); + assert_return(nl->protocol == NETLINK_GENERIC, -EINVAL); + assert_return(m, -EINVAL); + assert_return(m->protocol == NETLINK_GENERIC, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(ret, -EINVAL); + + r = sd_netlink_message_get_type(m, &nlmsg_type); + if (r < 0) + return r; + + r = genl_get_policy_set_and_header_size(nl, nlmsg_type, NULL, &size); + if (r < 0) + return r; + + if (m->hdr->nlmsg_len < NLMSG_LENGTH(size)) + return -EBADMSG; + + h = NLMSG_DATA(m->hdr); + + *ret = h->cmd; + return 0; +} + +static int genl_family_get_multicast_group_id_by_name(const GenericNetlinkFamily *f, const char *name, uint32_t *ret) { + void *p; + + assert(f); + assert(name); + + p = hashmap_get(f->multicast_group_by_name, name); + if (!p) + return -ENOENT; + + if (ret) + *ret = PTR_TO_UINT32(p); + return 0; +} + +int sd_genl_add_match( + sd_netlink *nl, + sd_netlink_slot **ret_slot, + const char *family_name, + const char *multicast_group_name, + uint8_t command, + sd_netlink_message_handler_t callback, + sd_netlink_destroy_t destroy_callback, + void *userdata, + const char *description) { + + const GenericNetlinkFamily *f; + uint32_t multicast_group_id; + int r; + + assert_return(nl, -EINVAL); + assert_return(nl->protocol == NETLINK_GENERIC, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(family_name, -EINVAL); + assert_return(multicast_group_name, -EINVAL); + + /* If command == 0, then all commands belonging to the multicast group trigger the callback. */ + + r = genl_family_get_by_name(nl, family_name, &f); + if (r < 0) + return r; + + r = genl_family_get_multicast_group_id_by_name(f, multicast_group_name, &multicast_group_id); + if (r < 0) + return r; + + return netlink_add_match_internal(nl, ret_slot, &multicast_group_id, 1, f->id, command, + callback, destroy_callback, userdata, description); +} + +int sd_genl_socket_open(sd_netlink **ret) { + return netlink_open_family(ret, NETLINK_GENERIC); +} diff --git a/src/libsystemd/sd-netlink/netlink-genl.h b/src/libsystemd/sd-netlink/netlink-genl.h new file mode 100644 index 0000000..b06be05 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-genl.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-netlink.h" + +#define CTRL_GENL_NAME "nlctrl" + +void genl_clear_family(sd_netlink *nl); diff --git a/src/libsystemd/sd-netlink/netlink-internal.h b/src/libsystemd/sd-netlink/netlink-internal.h new file mode 100644 index 0000000..514f225 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-internal.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <linux/netlink.h> + +#include "sd-netlink.h" + +#include "list.h" +#include "netlink-types.h" +#include "prioq.h" +#include "time-util.h" + +#define NETLINK_DEFAULT_TIMEOUT_USEC ((usec_t) (25 * USEC_PER_SEC)) + +#define NETLINK_RQUEUE_MAX 64*1024 + +#define NETLINK_CONTAINER_DEPTH 32 + +struct reply_callback { + sd_netlink_message_handler_t callback; + usec_t timeout; + uint32_t serial; + unsigned prioq_idx; +}; + +struct match_callback { + sd_netlink_message_handler_t callback; + uint32_t *groups; + size_t n_groups; + uint16_t type; + uint8_t cmd; /* used by genl */ + + LIST_FIELDS(struct match_callback, match_callbacks); +}; + +typedef enum NetlinkSlotType { + NETLINK_REPLY_CALLBACK, + NETLINK_MATCH_CALLBACK, + _NETLINK_SLOT_INVALID = -EINVAL, +} NetlinkSlotType; + +struct sd_netlink_slot { + unsigned n_ref; + NetlinkSlotType type:8; + bool floating; + sd_netlink *netlink; + void *userdata; + sd_netlink_destroy_t destroy_callback; + + char *description; + + LIST_FIELDS(sd_netlink_slot, slots); + + union { + struct reply_callback reply_callback; + struct match_callback match_callback; + }; +}; + +struct sd_netlink { + unsigned 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; + + sd_netlink_message **rqueue_partial; + unsigned rqueue_partial_size; + + struct nlmsghdr *rbuffer; + + 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; + + Hashmap *genl_family_by_name; + Hashmap *genl_family_by_id; +}; + +struct netlink_attribute { + size_t offset; /* offset from hdr to attribute */ + bool nested:1; + bool net_byteorder:1; +}; + +struct netlink_container { + const struct NLAPolicySet *policy_set; /* the policy set of the container */ + size_t offset; /* offset from hdr to the start of the container */ + struct netlink_attribute *attributes; + uint16_t max_attribute; /* the maximum attribute in container */ +}; + +struct sd_netlink_message { + unsigned n_ref; + + int protocol; + + struct nlmsghdr *hdr; + struct netlink_container containers[NETLINK_CONTAINER_DEPTH]; + unsigned n_containers; /* number of containers */ + uint32_t multicast_group; + bool sealed:1; + + sd_netlink_message *next; /* next in a chain of multi-part messages */ +}; + +int message_new_empty(sd_netlink *nl, sd_netlink_message **ret); +int message_new_full( + sd_netlink *nl, + uint16_t nlmsg_type, + const NLAPolicySet *policy_set, + size_t header_size, + sd_netlink_message **ret); +int message_new(sd_netlink *nl, sd_netlink_message **ret, uint16_t type); +int message_new_synthetic_error(sd_netlink *nl, int error, uint32_t serial, sd_netlink_message **ret); + +static inline uint32_t message_get_serial(sd_netlink_message *m) { + assert(m); + return ASSERT_PTR(m->hdr)->nlmsg_seq; +} + +void message_seal(sd_netlink_message *m); + +int netlink_open_family(sd_netlink **ret, int family); +bool netlink_pid_changed(sd_netlink *nl); +int netlink_rqueue_make_room(sd_netlink *nl); +int netlink_rqueue_partial_make_room(sd_netlink *nl); + +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 netlink_add_match_internal( + sd_netlink *nl, + sd_netlink_slot **ret_slot, + const uint32_t *groups, + size_t n_groups, + uint16_t type, + uint8_t cmd, + sd_netlink_message_handler_t callback, + sd_netlink_destroy_t destroy_callback, + void *userdata, + const char *description); + +/* Make sure callbacks don't destroy the netlink connection */ +#define NETLINK_DONT_DESTROY(nl) \ + _cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##nl = sd_netlink_ref(nl) + +/* nfnl */ +/* TODO: to be exported later */ +int sd_nfnl_socket_open(sd_netlink **ret); +int sd_nfnl_send_batch( + sd_netlink *nfnl, + sd_netlink_message **messages, + size_t msgcount, + uint32_t **ret_serials); +int sd_nfnl_call_batch( + sd_netlink *nfnl, + sd_netlink_message **messages, + size_t n_messages, + uint64_t usec, + sd_netlink_message ***ret_messages); +int sd_nfnl_message_new( + sd_netlink *nfnl, + sd_netlink_message **ret, + int nfproto, + uint16_t subsys, + uint16_t msg_type, + uint16_t flags); +int sd_nfnl_nft_message_new_table(sd_netlink *nfnl, sd_netlink_message **ret, + int nfproto, const char *table); +int sd_nfnl_nft_message_new_basechain(sd_netlink *nfnl, sd_netlink_message **ret, + int nfproto, const char *table, const char *chain, + const char *type, uint8_t hook, int prio); +int sd_nfnl_nft_message_new_rule(sd_netlink *nfnl, sd_netlink_message **ret, + int nfproto, const char *table, const char *chain); +int sd_nfnl_nft_message_new_set(sd_netlink *nfnl, sd_netlink_message **ret, + int nfproto, const char *table, const char *set_name, + uint32_t setid, uint32_t klen); +int sd_nfnl_nft_message_new_setelems(sd_netlink *nfnl, sd_netlink_message **ret, + int add, int nfproto, const char *table, const char *set_name); +int sd_nfnl_nft_message_append_setelem(sd_netlink_message *m, + uint32_t index, + const void *key, size_t key_len, + const void *data, size_t data_len, + uint32_t flags); diff --git a/src/libsystemd/sd-netlink/netlink-message-nfnl.c b/src/libsystemd/sd-netlink/netlink-message-nfnl.c new file mode 100644 index 0000000..55287d4 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-message-nfnl.c @@ -0,0 +1,420 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nf_tables.h> +#include <linux/netfilter.h> + +#include "sd-netlink.h" + +#include "io-util.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" + +static bool nfproto_is_valid(int nfproto) { + return IN_SET(nfproto, + NFPROTO_UNSPEC, + NFPROTO_INET, + NFPROTO_IPV4, + NFPROTO_ARP, + NFPROTO_NETDEV, + NFPROTO_BRIDGE, + NFPROTO_IPV6, + NFPROTO_DECNET); +} + +int sd_nfnl_message_new(sd_netlink *nfnl, sd_netlink_message **ret, int nfproto, uint16_t subsys, uint16_t msg_type, uint16_t flags) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert_return(nfnl, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(nfproto_is_valid(nfproto), -EINVAL); + assert_return(NFNL_MSG_TYPE(msg_type) == msg_type, -EINVAL); + + r = message_new(nfnl, &m, subsys << 8 | msg_type); + if (r < 0) + return r; + + m->hdr->nlmsg_flags |= flags; + + *(struct nfgenmsg*) NLMSG_DATA(m->hdr) = (struct nfgenmsg) { + .nfgen_family = nfproto, + .version = NFNETLINK_V0, + }; + + *ret = TAKE_PTR(m); + return 0; +} + +static int nfnl_message_set_res_id(sd_netlink_message *m, uint16_t res_id) { + struct nfgenmsg *nfgen; + + assert(m); + assert(m->hdr); + + nfgen = NLMSG_DATA(m->hdr); + nfgen->res_id = htobe16(res_id); + + return 0; +} + +static int nfnl_message_get_subsys(sd_netlink_message *m, uint16_t *ret) { + uint16_t t; + int r; + + assert(m); + assert(ret); + + r = sd_netlink_message_get_type(m, &t); + if (r < 0) + return r; + + *ret = NFNL_SUBSYS_ID(t); + return 0; +} + +static int nfnl_message_new_batch(sd_netlink *nfnl, sd_netlink_message **ret, uint16_t subsys, uint16_t msg_type) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert_return(nfnl, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(NFNL_MSG_TYPE(msg_type) == msg_type, -EINVAL); + + r = sd_nfnl_message_new(nfnl, &m, NFPROTO_UNSPEC, NFNL_SUBSYS_NONE, msg_type, 0); + if (r < 0) + return r; + + r = nfnl_message_set_res_id(m, subsys); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; +} + +int sd_nfnl_send_batch( + sd_netlink *nfnl, + sd_netlink_message **messages, + size_t n_messages, + uint32_t **ret_serials) { + + /* iovs refs batch_begin and batch_end, hence, free iovs first, then free batch_begin and batch_end. */ + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *batch_begin = NULL, *batch_end = NULL; + _cleanup_free_ struct iovec *iovs = NULL; + _cleanup_free_ uint32_t *serials = NULL; + uint16_t subsys; + ssize_t k; + size_t c = 0; + int r; + + assert_return(nfnl, -EINVAL); + assert_return(!netlink_pid_changed(nfnl), -ECHILD); + assert_return(messages, -EINVAL); + assert_return(n_messages > 0, -EINVAL); + + iovs = new(struct iovec, n_messages + 2); + if (!iovs) + return -ENOMEM; + + if (ret_serials) { + serials = new(uint32_t, n_messages); + if (!serials) + return -ENOMEM; + } + + r = nfnl_message_get_subsys(messages[0], &subsys); + if (r < 0) + return r; + + r = nfnl_message_new_batch(nfnl, &batch_begin, subsys, NFNL_MSG_BATCH_BEGIN); + if (r < 0) + return r; + + netlink_seal_message(nfnl, batch_begin); + iovs[c++] = IOVEC_MAKE(batch_begin->hdr, batch_begin->hdr->nlmsg_len); + + for (size_t i = 0; i < n_messages; i++) { + uint16_t s; + + r = nfnl_message_get_subsys(messages[i], &s); + if (r < 0) + return r; + + if (s != subsys) + return -EINVAL; + + netlink_seal_message(nfnl, messages[i]); + if (serials) + serials[i] = message_get_serial(messages[i]); + + /* It seems that the kernel accepts an arbitrary number. Let's set the lower 16 bits of the + * serial of the first message. */ + nfnl_message_set_res_id(messages[i], (uint16_t) (message_get_serial(batch_begin) & UINT16_MAX)); + + iovs[c++] = IOVEC_MAKE(messages[i]->hdr, messages[i]->hdr->nlmsg_len); + } + + r = nfnl_message_new_batch(nfnl, &batch_end, subsys, NFNL_MSG_BATCH_END); + if (r < 0) + return r; + + netlink_seal_message(nfnl, batch_end); + iovs[c++] = IOVEC_MAKE(batch_end->hdr, batch_end->hdr->nlmsg_len); + + assert(c == n_messages + 2); + k = writev(nfnl->fd, iovs, n_messages + 2); + if (k < 0) + return -errno; + + if (ret_serials) + *ret_serials = TAKE_PTR(serials); + + return 0; +} + +int sd_nfnl_call_batch( + sd_netlink *nfnl, + sd_netlink_message **messages, + size_t n_messages, + uint64_t usec, + sd_netlink_message ***ret_messages) { + + _cleanup_free_ sd_netlink_message **replies = NULL; + _cleanup_free_ uint32_t *serials = NULL; + int k, r; + + assert_return(nfnl, -EINVAL); + assert_return(!netlink_pid_changed(nfnl), -ECHILD); + assert_return(messages, -EINVAL); + assert_return(n_messages > 0, -EINVAL); + + if (ret_messages) { + replies = new0(sd_netlink_message*, n_messages); + if (!replies) + return -ENOMEM; + } + + r = sd_nfnl_send_batch(nfnl, messages, n_messages, &serials); + if (r < 0) + return r; + + for (size_t i = 0; i < n_messages; i++) { + k = sd_netlink_read(nfnl, serials[i], usec, ret_messages ? replies + i : NULL); + if (k < 0 && r >= 0) + r = k; + } + if (r < 0) + return r; + + if (ret_messages) + *ret_messages = TAKE_PTR(replies); + + return 0; +} + +int sd_nfnl_nft_message_new_basechain( + sd_netlink *nfnl, + sd_netlink_message **ret, + int nfproto, + const char *table, + const char *chain, + const char *type, + uint8_t hook, + int prio) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWCHAIN, NLM_F_CREATE); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_CHAIN_TABLE, table); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_CHAIN_NAME, chain); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_CHAIN_TYPE, type); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_CHAIN_HOOK); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_HOOK_HOOKNUM, htobe32(hook)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_HOOK_PRIORITY, htobe32(prio)); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; +} + +int sd_nfnl_nft_message_new_table( + sd_netlink *nfnl, + sd_netlink_message **ret, + int nfproto, + const char *table) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWTABLE, NLM_F_CREATE | NLM_F_EXCL); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_TABLE_NAME, table); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return r; +} + +int sd_nfnl_nft_message_new_rule( + sd_netlink *nfnl, + sd_netlink_message **ret, + int nfproto, + const char *table, + const char *chain) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWRULE, NLM_F_CREATE); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_RULE_TABLE, table); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_RULE_CHAIN, chain); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return r; +} + +int sd_nfnl_nft_message_new_set( + sd_netlink *nfnl, + sd_netlink_message **ret, + int nfproto, + const char *table, + const char *set_name, + uint32_t set_id, + uint32_t klen) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSET, NLM_F_CREATE); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_SET_TABLE, table); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_SET_NAME, set_name); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_SET_ID, ++set_id); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_SET_KEY_LEN, htobe32(klen)); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return r; +} + +int sd_nfnl_nft_message_new_setelems( + sd_netlink *nfnl, + sd_netlink_message **ret, + int add, /* boolean */ + int nfproto, + const char *table, + const char *set_name) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + if (add) + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSETELEM, NLM_F_CREATE); + else + r = sd_nfnl_message_new(nfnl, &m, nfproto, NFNL_SUBSYS_NFTABLES, NFT_MSG_DELSETELEM, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_SET_ELEM_LIST_TABLE, table); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_SET_ELEM_LIST_SET, set_name); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return r; +} + +int sd_nfnl_nft_message_append_setelem( + sd_netlink_message *m, + uint32_t index, + const void *key, + size_t key_len, + const void *data, + size_t data_len, + uint32_t flags) { + + int r; + + r = sd_netlink_message_open_array(m, index); + if (r < 0) + return r; + + r = sd_netlink_message_append_container_data(m, NFTA_SET_ELEM_KEY, NFTA_DATA_VALUE, key, key_len); + if (r < 0) + goto cancel; + + if (data) { + r = sd_netlink_message_append_container_data(m, NFTA_SET_ELEM_DATA, NFTA_DATA_VALUE, data, data_len); + if (r < 0) + goto cancel; + } + + if (flags != 0) { + r = sd_netlink_message_append_u32(m, NFTA_SET_ELEM_FLAGS, htobe32(flags)); + if (r < 0) + goto cancel; + } + + return sd_netlink_message_close_container(m); /* array */ + +cancel: + (void) sd_netlink_message_cancel_array(m); + return r; +} + +int sd_nfnl_socket_open(sd_netlink **ret) { + return netlink_open_family(ret, NETLINK_NETFILTER); +} diff --git a/src/libsystemd/sd-netlink/netlink-message-rtnl.c b/src/libsystemd/sd-netlink/netlink-message-rtnl.c new file mode 100644 index 0000000..109f3ee --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-message-rtnl.c @@ -0,0 +1,1205 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/fib_rules.h> +#include <linux/if_addrlabel.h> +#include <linux/if_bridge.h> +#include <linux/nexthop.h> +#include <stdbool.h> +#include <unistd.h> + +#include "sd-netlink.h" + +#include "format-util.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "util.h" + +static bool rtnl_message_type_is_neigh(uint16_t type) { + return IN_SET(type, RTM_NEWNEIGH, RTM_GETNEIGH, RTM_DELNEIGH); +} + +static bool rtnl_message_type_is_route(uint16_t type) { + return IN_SET(type, RTM_NEWROUTE, RTM_GETROUTE, RTM_DELROUTE); +} + +static bool rtnl_message_type_is_nexthop(uint16_t type) { + return IN_SET(type, RTM_NEWNEXTHOP, RTM_GETNEXTHOP, RTM_DELNEXTHOP); +} + +static bool rtnl_message_type_is_link(uint16_t type) { + return IN_SET(type, + RTM_NEWLINK, RTM_SETLINK, RTM_GETLINK, RTM_DELLINK, + RTM_NEWLINKPROP, RTM_DELLINKPROP, RTM_GETLINKPROP); +} + +static bool rtnl_message_type_is_addr(uint16_t type) { + return IN_SET(type, RTM_NEWADDR, RTM_GETADDR, RTM_DELADDR); +} + +static bool rtnl_message_type_is_addrlabel(uint16_t type) { + return IN_SET(type, RTM_NEWADDRLABEL, RTM_DELADDRLABEL, RTM_GETADDRLABEL); +} + +static bool rtnl_message_type_is_routing_policy_rule(uint16_t type) { + return IN_SET(type, RTM_NEWRULE, RTM_DELRULE, RTM_GETRULE); +} + +static bool rtnl_message_type_is_traffic_control(uint16_t type) { + return IN_SET(type, + RTM_NEWQDISC, RTM_DELQDISC, RTM_GETQDISC, + RTM_NEWTCLASS, RTM_DELTCLASS, RTM_GETTCLASS); +} + +static bool rtnl_message_type_is_mdb(uint16_t type) { + return IN_SET(type, RTM_NEWMDB, RTM_DELMDB, RTM_GETMDB); +} + +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_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, uint8_t *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_protocol = rtm_protocol; + + return 0; +} + +int sd_rtnl_message_new_nexthop(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int nh_family, + unsigned char nh_protocol) { + struct nhmsg *nhm; + int r; + + assert_return(rtnl_message_type_is_nexthop(nlmsg_type), -EINVAL); + switch (nlmsg_type) { + case RTM_DELNEXTHOP: + assert_return(nh_family == AF_UNSPEC, -EINVAL); + _fallthrough_; + case RTM_GETNEXTHOP: + assert_return(nh_protocol == RTPROT_UNSPEC, -EINVAL); + break; + case RTM_NEWNEXTHOP: + assert_return(IN_SET(nh_family, AF_UNSPEC, AF_INET, AF_INET6), -EINVAL); + break; + default: + assert_not_reached(); + } + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWNEXTHOP) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + nhm = NLMSG_DATA((*ret)->hdr); + + nhm->nh_family = nh_family; + nhm->nh_scope = RT_SCOPE_UNIVERSE; + nhm->nh_protocol = nh_protocol; + + return 0; +} + +int sd_rtnl_message_nexthop_set_flags(sd_netlink_message *m, uint8_t flags) { + struct nhmsg *nhm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(m->hdr->nlmsg_type == RTM_NEWNEXTHOP, -EINVAL); + + nhm = NLMSG_DATA(m->hdr); + nhm->nh_flags = flags; + + return 0; +} + +int sd_rtnl_message_nexthop_get_flags(sd_netlink_message *m, uint8_t *ret) { + struct nhmsg *nhm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_nexthop(m->hdr->nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + nhm = NLMSG_DATA(m->hdr); + *ret = nhm->nh_flags; + + return 0; +} + +int sd_rtnl_message_nexthop_get_family(sd_netlink_message *m, uint8_t *family) { + struct nhmsg *nhm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_nexthop(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + nhm = NLMSG_DATA(m->hdr); + *family = nhm->nh_family; + + return 0; +} + +int sd_rtnl_message_nexthop_get_protocol(sd_netlink_message *m, uint8_t *protocol) { + struct nhmsg *nhm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_nexthop(m->hdr->nlmsg_type), -EINVAL); + assert_return(protocol, -EINVAL); + + nhm = NLMSG_DATA(m->hdr); + *protocol = nhm->nh_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_UNSPEC, AF_INET, AF_INET6, AF_BRIDGE), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWNEIGH) { + if (ndm_family == AF_BRIDGE) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + else + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + } + + 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 != 0, -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; + else if (nlmsg_type == RTM_NEWLINKPROP) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL | NLM_F_APPEND; + + 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 *ret_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(ret_family, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ret_family = ifa->ifa_family; + + return 0; +} + +int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *ret_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(ret_prefixlen, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ret_prefixlen = ifa->ifa_prefixlen; + + return 0; +} + +int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *ret_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(ret_scope, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ret_scope = ifa->ifa_scope; + + return 0; +} + +int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *ret_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(ret_flags, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ret_flags = ifa->ifa_flags; + + return 0; +} + +int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ret_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(ret_ifindex, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ret_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; + + ifa = NLMSG_DATA((*ret)->hdr); + + ifa->ifa_index = index; + ifa->ifa_family = family; + + 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; + } else if (rtnl_message_type_is_nexthop(m->hdr->nlmsg_type)) { + struct nhmsg *nhm; + + nhm = NLMSG_DATA(m->hdr); + + *family = nhm->nh_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 fib_rule_hdr *frh; + 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; + + frh = NLMSG_DATA((*ret)->hdr); + frh->family = ifal_family; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_tos(sd_netlink_message *m, uint8_t tos) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + frh->tos = tos; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_tos(sd_netlink_message *m, uint8_t *tos) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + *tos = frh->tos; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_table(sd_netlink_message *m, uint8_t table) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + frh->table = table; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_table(sd_netlink_message *m, uint8_t *table) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + *table = frh->table; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_flags(sd_netlink_message *m, uint32_t flags) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + frh->flags = flags; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_flags(sd_netlink_message *m, uint32_t *flags) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + *flags = frh->flags; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_fib_type(sd_netlink_message *m, uint8_t type) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + frh->action = type; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_fib_type(sd_netlink_message *m, uint8_t *type) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + *type = frh->action; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_fib_dst_prefixlen(sd_netlink_message *m, uint8_t len) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + frh->dst_len = len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_fib_dst_prefixlen(sd_netlink_message *m, uint8_t *len) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + *len = frh->dst_len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_fib_src_prefixlen(sd_netlink_message *m, uint8_t len) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + frh->src_len = len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_fib_src_prefixlen(sd_netlink_message *m, uint8_t *len) { + struct fib_rule_hdr *frh; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + frh = NLMSG_DATA(m->hdr); + + *len = frh->src_len; + + return 0; +} + +int sd_rtnl_message_new_traffic_control( + sd_netlink *rtnl, + sd_netlink_message **ret, + uint16_t nlmsg_type, + int ifindex, + uint32_t handle, + uint32_t parent) { + + struct tcmsg *tcm; + int r; + + assert_return(rtnl_message_type_is_traffic_control(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (IN_SET(nlmsg_type, RTM_NEWQDISC, RTM_NEWTCLASS)) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + tcm = NLMSG_DATA((*ret)->hdr); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = handle; + tcm->tcm_parent = parent; + + return 0; +} + +int sd_rtnl_message_traffic_control_get_ifindex(sd_netlink_message *m, int *ret) { + struct tcmsg *tcm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_traffic_control(m->hdr->nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + tcm = NLMSG_DATA(m->hdr); + *ret = tcm->tcm_ifindex; + + return 0; +} + +int sd_rtnl_message_traffic_control_get_handle(sd_netlink_message *m, uint32_t *ret) { + struct tcmsg *tcm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_traffic_control(m->hdr->nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + tcm = NLMSG_DATA(m->hdr); + *ret = tcm->tcm_handle; + + return 0; +} + +int sd_rtnl_message_traffic_control_get_parent(sd_netlink_message *m, uint32_t *ret) { + struct tcmsg *tcm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_traffic_control(m->hdr->nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + tcm = NLMSG_DATA(m->hdr); + *ret = tcm->tcm_parent; + + return 0; +} + +int sd_rtnl_message_new_mdb( + sd_netlink *rtnl, + sd_netlink_message **ret, + uint16_t nlmsg_type, + int mdb_ifindex) { + + struct br_port_msg *bpm; + int r; + + assert_return(rtnl_message_type_is_mdb(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWMDB) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + bpm = NLMSG_DATA((*ret)->hdr); + bpm->family = AF_BRIDGE; + bpm->ifindex = mdb_ifindex; + + return 0; +} diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c new file mode 100644 index 0000000..000a50e --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -0,0 +1,1421 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <stdbool.h> +#include <unistd.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "format-util.h" +#include "memory-util.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "strv.h" + +#define GET_CONTAINER(m, i) ((struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset)) + +#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 *nl, sd_netlink_message **ret) { + sd_netlink_message *m; + + assert(nl); + assert(ret); + + /* Note that 'nl' 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 = new(sd_netlink_message, 1); + if (!m) + return -ENOMEM; + + *m = (sd_netlink_message) { + .n_ref = 1, + .protocol = nl->protocol, + .sealed = false, + }; + + *ret = m; + return 0; +} + +int message_new_full( + sd_netlink *nl, + uint16_t nlmsg_type, + const NLAPolicySet *policy_set, + size_t header_size, + sd_netlink_message **ret) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + size_t size; + int r; + + assert(nl); + assert(policy_set); + assert(ret); + + size = NLMSG_SPACE(header_size); + assert(size >= sizeof(struct nlmsghdr)); + + r = message_new_empty(nl, &m); + if (r < 0) + return r; + + m->containers[0].policy_set = policy_set; + + m->hdr = malloc0(size); + if (!m->hdr) + return -ENOMEM; + + m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + m->hdr->nlmsg_len = size; + m->hdr->nlmsg_type = nlmsg_type; + + *ret = TAKE_PTR(m); + return 0; +} + +int message_new(sd_netlink *nl, sd_netlink_message **ret, uint16_t nlmsg_type) { + const NLAPolicySet *policy_set; + size_t size; + int r; + + assert_return(nl, -EINVAL); + assert_return(ret, -EINVAL); + + r = netlink_get_policy_set_and_header_size(nl, nlmsg_type, &policy_set, &size); + if (r < 0) + return r; + + return message_new_full(nl, nlmsg_type, policy_set, size, ret); +} + +int message_new_synthetic_error(sd_netlink *nl, int error, uint32_t serial, sd_netlink_message **ret) { + struct nlmsgerr *err; + int r; + + assert(error <= 0); + + r = message_new(nl, ret, NLMSG_ERROR); + if (r < 0) + return r; + + message_seal(*ret); + (*ret)->hdr->nlmsg_seq = serial; + + err = NLMSG_DATA((*ret)->hdr); + err->error = error; + + return 0; +} + +int sd_netlink_message_set_request_dump(sd_netlink_message *m, int dump) { + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(m->protocol != NETLINK_ROUTE || + IN_SET(m->hdr->nlmsg_type, + RTM_GETLINK, RTM_GETLINKPROP, RTM_GETADDR, RTM_GETROUTE, RTM_GETNEIGH, + RTM_GETRULE, RTM_GETADDRLABEL, RTM_GETNEXTHOP, RTM_GETQDISC, RTM_GETTCLASS), + -EINVAL); + + SET_FLAG(m->hdr->nlmsg_flags, NLM_F_DUMP, dump); + + return 0; +} + +DEFINE_TRIVIAL_REF_FUNC(sd_netlink_message, sd_netlink_message); + +sd_netlink_message* sd_netlink_message_unref(sd_netlink_message *m) { + while (m && --m->n_ref == 0) { + unsigned i; + + free(m->hdr); + + for (i = 0; i <= m->n_containers; i++) + free(m->containers[i].attributes); + + sd_netlink_message *t = m; + m = m->next; + free(t); + } + + return NULL; +} + +int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *ret) { + assert_return(m, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = 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 != 0, -EINVAL); + + m->hdr->nlmsg_flags = flags; + + return 0; +} + +int sd_netlink_message_is_broadcast(sd_netlink_message *m) { + assert_return(m, -EINVAL); + + return m->multicast_group != 0; +} + +/* If successful the updated message will be correctly aligned, if unsuccessful the old message is untouched. */ +static int add_rtattr(sd_netlink_message *m, uint16_t attr_type, const void *data, size_t data_length) { + size_t message_length; + struct nlmsghdr *new_hdr; + struct rtattr *rta; + 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 > 0); + + /* get the new message size (with padding at the end) */ + message_length = m->hdr->nlmsg_len + RTA_SPACE(data_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 + m->hdr->nlmsg_len); + + rtattr_append_attribute_internal(rta, attr_type, data, data_length); + + /* if we are inside containers, extend them */ + for (unsigned i = 0; i < m->n_containers; i++) + GET_CONTAINER(m, i)->rta_len += RTA_SPACE(data_length); + + /* update message size */ + offset = m->hdr->nlmsg_len; + m->hdr->nlmsg_len = message_length; + + /* return old message size */ + return offset; +} + +static int message_attribute_has_type(sd_netlink_message *m, size_t *ret_size, uint16_t attr_type, NLAType type) { + const NLAPolicy *policy; + + assert(m); + + policy = policy_set_get_policy(m->containers[m->n_containers].policy_set, attr_type); + if (!policy) + return -EOPNOTSUPP; + + if (policy_get_type(policy) != type) + return -EINVAL; + + if (ret_size) + *ret_size = policy_get_size(policy); + return 0; +} + +int sd_netlink_message_append_string(sd_netlink_message *m, uint16_t attr_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, attr_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, attr_type, data, length + 1); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_strv(sd_netlink_message *m, uint16_t attr_type, const char* const *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, attr_type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + STRV_FOREACH(p, data) { + if (size) { + length = strnlen(*p, size+1); + if (length > size) + return -EINVAL; + } else + length = strlen(*p); + + r = add_rtattr(m, attr_type, *p, length + 1); + if (r < 0) + return r; + } + + return 0; +} + +int sd_netlink_message_append_flag(sd_netlink_message *m, uint16_t attr_type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, &size, attr_type, NETLINK_TYPE_FLAG); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, NULL, 0); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u8(sd_netlink_message *m, uint16_t attr_type, uint8_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(uint8_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u16(sd_netlink_message *m, uint16_t attr_type, uint16_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(uint16_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u32(sd_netlink_message *m, uint16_t attr_type, uint32_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(uint32_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u64(sd_netlink_message *m, uint16_t attr_type, uint64_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U64); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(uint64_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_s8(sd_netlink_message *m, uint16_t attr_type, int8_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_S8); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(int8_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_s16(sd_netlink_message *m, uint16_t attr_type, int16_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_S16); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(int16_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_s32(sd_netlink_message *m, uint16_t attr_type, int32_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_S32); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(int32_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_s64(sd_netlink_message *m, uint16_t attr_type, int64_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_S64); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, &data, sizeof(int64_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_data(sd_netlink_message *m, uint16_t attr_type, const void *data, size_t len) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = add_rtattr(m, attr_type, data, len); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_container_data( + sd_netlink_message *m, + uint16_t container_type, + uint16_t attr_type, + const void *data, + size_t len) { + + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = sd_netlink_message_open_container(m, container_type); + if (r < 0) + return r; + + r = sd_netlink_message_append_data(m, attr_type, data, len); + if (r < 0) + return r; + + return sd_netlink_message_close_container(m); +} + +int netlink_message_append_in_addr_union(sd_netlink_message *m, uint16_t attr_type, int family, const union in_addr_union *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + assert_return(IN_SET(family, AF_INET, AF_INET6), -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, data, FAMILY_ADDRESS_SIZE(family)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_in_addr(sd_netlink_message *m, uint16_t attr_type, const struct in_addr *data) { + return netlink_message_append_in_addr_union(m, attr_type, AF_INET, (const union in_addr_union *) data); +} + +int sd_netlink_message_append_in6_addr(sd_netlink_message *m, uint16_t attr_type, const struct in6_addr *data) { + return netlink_message_append_in_addr_union(m, attr_type, AF_INET6, (const union in_addr_union *) data); +} + +int netlink_message_append_sockaddr_union(sd_netlink_message *m, uint16_t attr_type, const union sockaddr_union *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + assert_return(IN_SET(data->sa.sa_family, AF_INET, AF_INET6), -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_SOCKADDR); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, data, data->sa.sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_sockaddr_in(sd_netlink_message *m, uint16_t attr_type, const struct sockaddr_in *data) { + return netlink_message_append_sockaddr_union(m, attr_type, (const union sockaddr_union *) data); +} + +int sd_netlink_message_append_sockaddr_in6(sd_netlink_message *m, uint16_t attr_type, const struct sockaddr_in6 *data) { + return netlink_message_append_sockaddr_union(m, attr_type, (const union sockaddr_union *) data); +} + +int sd_netlink_message_append_ether_addr(sd_netlink_message *m, uint16_t attr_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, attr_type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, data, ETH_ALEN); + if (r < 0) + return r; + + return 0; +} + +int netlink_message_append_hw_addr(sd_netlink_message *m, uint16_t attr_type, const struct hw_addr_data *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + assert_return(data->length > 0, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, data->bytes, data->length); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_cache_info(sd_netlink_message *m, uint16_t attr_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, attr_type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = add_rtattr(m, attr_type, info, sizeof(struct ifa_cacheinfo)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_open_container(sd_netlink_message *m, uint16_t attr_type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + /* m->containers[m->n_containers + 1] is accessed both in read and write. Prevent access out of bound */ + assert_return(m->n_containers < (NETLINK_CONTAINER_DEPTH - 1), -ERANGE); + + r = message_attribute_has_type(m, &size, attr_type, NETLINK_TYPE_NESTED); + if (r < 0) { + const NLAPolicySetUnion *policy_set_union; + int family; + + r = message_attribute_has_type(m, &size, attr_type, NETLINK_TYPE_NESTED_UNION_BY_FAMILY); + if (r < 0) + return r; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + policy_set_union = policy_set_get_policy_set_union( + m->containers[m->n_containers].policy_set, + attr_type); + if (!policy_set_union) + return -EOPNOTSUPP; + + m->containers[m->n_containers + 1].policy_set = + policy_set_union_get_policy_set_by_family( + policy_set_union, + family); + } else + m->containers[m->n_containers + 1].policy_set = + policy_set_get_policy_set( + m->containers[m->n_containers].policy_set, + attr_type); + if (!m->containers[m->n_containers + 1].policy_set) + return -EOPNOTSUPP; + + r = add_rtattr(m, attr_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, uint16_t attr_type, const char *key) { + const NLAPolicySetUnion *policy_set_union; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers < (NETLINK_CONTAINER_DEPTH - 1), -ERANGE); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_NESTED_UNION_BY_STRING); + if (r < 0) + return r; + + policy_set_union = policy_set_get_policy_set_union( + m->containers[m->n_containers].policy_set, + attr_type); + if (!policy_set_union) + return -EOPNOTSUPP; + + m->containers[m->n_containers + 1].policy_set = + policy_set_union_get_policy_set_by_string( + policy_set_union, + key); + if (!m->containers[m->n_containers + 1].policy_set) + return -EOPNOTSUPP; + + r = sd_netlink_message_append_string(m, policy_set_union_get_match_attribute(policy_set_union), key); + if (r < 0) + return r; + + /* do we ever need non-null size */ + r = add_rtattr(m, attr_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].policy_set = 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 attr_type) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers < (NETLINK_CONTAINER_DEPTH - 1), -ERANGE); + + r = add_rtattr(m, attr_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].policy_set = m->containers[m->n_containers - 1].policy_set; + + return 0; +} + +int sd_netlink_message_cancel_array(sd_netlink_message *m) { + 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 (unsigned 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].policy_set = NULL; + + return 0; +} + +static int netlink_message_read_internal( + sd_netlink_message *m, + uint16_t attr_type, + void **ret_data, + bool *ret_net_byteorder) { + + struct netlink_attribute *attribute; + struct rtattr *rta; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + assert(m->n_containers < NETLINK_CONTAINER_DEPTH); + + if (!m->containers[m->n_containers].attributes) + return -ENODATA; + + if (attr_type > m->containers[m->n_containers].max_attribute) + return -ENODATA; + + attribute = &m->containers[m->n_containers].attributes[attr_type]; + + if (attribute->offset == 0) + return -ENODATA; + + rta = (struct rtattr*)((uint8_t *) m->hdr + attribute->offset); + + if (ret_data) + *ret_data = RTA_DATA(rta); + + if (ret_net_byteorder) + *ret_net_byteorder = attribute->net_byteorder; + + return RTA_PAYLOAD(rta); +} + +int sd_netlink_message_read(sd_netlink_message *m, uint16_t attr_type, size_t size, void *data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if ((size_t) r > size) + return -ENOBUFS; + + if (data) + memcpy(data, attr_data, r); + + return r; +} + +int sd_netlink_message_read_data(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if (ret_data) { + void *data; + + data = memdup(attr_data, r); + if (!data) + return -ENOMEM; + + *ret_data = data; + } + + if (ret_size) + *ret_size = r; + + return r; +} + +int sd_netlink_message_read_data_suffix0(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if (ret_data) { + void *data; + + data = memdup_suffix0(attr_data, r); + if (!data) + return -ENOMEM; + + *ret_data = data; + } + + if (ret_size) + *ret_size = r; + + return r; +} + +int sd_netlink_message_read_string_strdup(sd_netlink_message *m, uint16_t attr_type, char **data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if (data) { + char *str; + + str = strndup(attr_data, r); + if (!str) + return -ENOMEM; + + *data = str; + } + + return 0; +} + +int sd_netlink_message_read_string(sd_netlink_message *m, uint16_t attr_type, const char **data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + 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, uint16_t attr_type, uint8_t *data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + 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, uint16_t attr_type, uint16_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, &net_byteorder); + if (r < 0) + return r; + + 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, uint16_t attr_type, uint32_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, &net_byteorder); + if (r < 0) + return r; + + 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, uint16_t attr_type, struct ether_addr *data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if ((size_t) r < sizeof(struct ether_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct ether_addr)); + + return 0; +} + +int netlink_message_read_hw_addr(sd_netlink_message *m, uint16_t attr_type, struct hw_addr_data *data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if (r > HW_ADDR_MAX_SIZE) + return -EIO; + + if (data) { + memcpy(data->bytes, attr_data, r); + data->length = r; + } + + return 0; +} + +int sd_netlink_message_read_cache_info(sd_netlink_message *m, uint16_t attr_type, struct ifa_cacheinfo *info) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if ((size_t) r < sizeof(struct ifa_cacheinfo)) + return -EIO; + + if (info) + memcpy(info, attr_data, sizeof(struct ifa_cacheinfo)); + + return 0; +} + +int netlink_message_read_in_addr_union(sd_netlink_message *m, uint16_t attr_type, int family, union in_addr_union *data) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + assert_return(IN_SET(family, AF_INET, AF_INET6), -EINVAL); + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r < 0) + return r; + + if ((size_t) r < FAMILY_ADDRESS_SIZE(family)) + return -EIO; + + if (data) + memcpy(data, attr_data, FAMILY_ADDRESS_SIZE(family)); + + return 0; +} + +int sd_netlink_message_read_in_addr(sd_netlink_message *m, uint16_t attr_type, struct in_addr *data) { + union in_addr_union u; + int r; + + r = netlink_message_read_in_addr_union(m, attr_type, AF_INET, &u); + if (r >= 0 && data) + *data = u.in; + + return r; +} + +int sd_netlink_message_read_in6_addr(sd_netlink_message *m, uint16_t attr_type, struct in6_addr *data) { + union in_addr_union u; + int r; + + r = netlink_message_read_in_addr_union(m, attr_type, AF_INET6, &u); + if (r >= 0 && data) + *data = u.in6; + + return r; +} + +int sd_netlink_message_has_flag(sd_netlink_message *m, uint16_t attr_type) { + void *attr_data; + int r; + + assert_return(m, -EINVAL); + + /* This returns 1 when the flag is set, 0 when not set, negative errno on error. */ + + r = message_attribute_has_type(m, NULL, attr_type, NETLINK_TYPE_FLAG); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + return 1; +} + +int sd_netlink_message_read_strv(sd_netlink_message *m, uint16_t container_type, uint16_t attr_type, char ***ret) { + _cleanup_strv_free_ char **s = NULL; + const NLAPolicySet *policy_set; + const NLAPolicy *policy; + struct rtattr *rta; + void *container; + size_t rt_len; + int r; + + assert_return(m, -EINVAL); + assert_return(m->n_containers < NETLINK_CONTAINER_DEPTH, -EINVAL); + + policy = policy_set_get_policy( + m->containers[m->n_containers].policy_set, + container_type); + if (!policy) + return -EOPNOTSUPP; + + if (policy_get_type(policy) != NETLINK_TYPE_NESTED) + return -EINVAL; + + policy_set = policy_set_get_policy_set( + m->containers[m->n_containers].policy_set, + container_type); + if (!policy_set) + return -EOPNOTSUPP; + + policy = policy_set_get_policy(policy_set, attr_type); + if (!policy) + return -EOPNOTSUPP; + + if (policy_get_type(policy) != NETLINK_TYPE_STRING) + return -EINVAL; + + r = netlink_message_read_internal(m, container_type, &container, NULL); + if (r < 0) + return r; + + rt_len = (size_t) r; + rta = container; + + /* RTA_OK() macro compares with rta->rt_len, which is unsigned short, and + * LGTM.com analysis does not like the type difference. Hence, here we + * introduce an unsigned short variable as a workaround. */ + unsigned short len = rt_len; + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + uint16_t type; + + type = RTA_TYPE(rta); + if (type != attr_type) + continue; + + r = strv_extend(&s, RTA_DATA(rta)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(s); + return 0; +} + +static int netlink_container_parse( + sd_netlink_message *m, + struct netlink_container *container, + struct rtattr *rta, + size_t rt_len) { + + _cleanup_free_ struct netlink_attribute *attributes = NULL; + uint16_t max_attr = 0; + + /* RTA_OK() macro compares with rta->rt_len, which is unsigned short, and + * LGTM.com analysis does not like the type difference. Hence, here we + * introduce an unsigned short variable as a workaround. */ + unsigned short len = rt_len; + for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + uint16_t attr; + + attr = RTA_TYPE(rta); + max_attr = MAX(max_attr, attr); + + if (!GREEDY_REALLOC0(attributes, (size_t) max_attr + 1)) + return -ENOMEM; + + if (attributes[attr].offset != 0) + log_debug("sd-netlink: message parse - overwriting repeated attribute"); + + attributes[attr].offset = (uint8_t *) rta - (uint8_t *) m->hdr; + attributes[attr].nested = RTA_FLAGS(rta) & NLA_F_NESTED; + attributes[attr].net_byteorder = RTA_FLAGS(rta) & NLA_F_NET_BYTEORDER; + } + + container->attributes = TAKE_PTR(attributes); + container->max_attribute = max_attr; + + return 0; +} + +int sd_netlink_message_enter_container(sd_netlink_message *m, uint16_t attr_type) { + const NLAPolicy *policy; + const NLAPolicySet *policy_set; + void *container; + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(m->n_containers < (NETLINK_CONTAINER_DEPTH - 1), -EINVAL); + + policy = policy_set_get_policy( + m->containers[m->n_containers].policy_set, + attr_type); + if (!policy) + return -EOPNOTSUPP; + + switch (policy_get_type(policy)) { + case NETLINK_TYPE_NESTED: + policy_set = policy_set_get_policy_set( + m->containers[m->n_containers].policy_set, + attr_type); + break; + + case NETLINK_TYPE_NESTED_UNION_BY_STRING: { + const NLAPolicySetUnion *policy_set_union; + const char *key; + + policy_set_union = policy_get_policy_set_union(policy); + if (!policy_set_union) + return -EOPNOTSUPP; + + r = sd_netlink_message_read_string( + m, + policy_set_union_get_match_attribute(policy_set_union), + &key); + if (r < 0) + return r; + + policy_set = policy_set_union_get_policy_set_by_string( + policy_set_union, + key); + break; + } + case NETLINK_TYPE_NESTED_UNION_BY_FAMILY: { + const NLAPolicySetUnion *policy_set_union; + int family; + + policy_set_union = policy_get_policy_set_union(policy); + if (!policy_set_union) + return -EOPNOTSUPP; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + policy_set = policy_set_union_get_policy_set_by_family( + policy_set_union, + family); + break; + } + default: + assert_not_reached(); + } + if (!policy_set) + return -EOPNOTSUPP; + + r = netlink_message_read_internal(m, attr_type, &container, NULL); + if (r < 0) + return r; + + size = (size_t) r; + m->n_containers++; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + container, + size); + if (r < 0) { + m->n_containers--; + return r; + } + + m->containers[m->n_containers].policy_set = policy_set; + + return 0; +} + +int sd_netlink_message_enter_array(sd_netlink_message *m, uint16_t attr_type) { + void *container; + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(m->n_containers < (NETLINK_CONTAINER_DEPTH - 1), -EINVAL); + + r = netlink_message_read_internal(m, attr_type, &container, NULL); + if (r < 0) + return r; + + size = (size_t) r; + m->n_containers++; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + container, + size); + if (r < 0) { + m->n_containers--; + return r; + } + + m->containers[m->n_containers].policy_set = m->containers[m->n_containers - 1].policy_set; + + 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].max_attribute = 0; + m->containers[m->n_containers].policy_set = NULL; + + m->n_containers--; + + return 0; +} + +int sd_netlink_message_get_max_attribute(sd_netlink_message *m, uint16_t *ret) { + assert_return(m, -EINVAL); + assert_return(m->sealed, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = m->containers[m->n_containers].max_attribute; + return 0; +} + +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; +} + +static int netlink_message_parse_error(sd_netlink_message *m) { + struct nlmsgerr *err = NLMSG_DATA(m->hdr); + size_t hlen = sizeof(struct nlmsgerr); + + /* no TLVs, nothing to do here */ + if (!(m->hdr->nlmsg_flags & NLM_F_ACK_TLVS)) + return 0; + + /* if NLM_F_CAPPED is set then the inner err msg was capped */ + if (!(m->hdr->nlmsg_flags & NLM_F_CAPPED)) + hlen += err->msg.nlmsg_len - sizeof(struct nlmsghdr); + + if (m->hdr->nlmsg_len <= NLMSG_SPACE(hlen)) + return 0; + + return netlink_container_parse(m, + &m->containers[m->n_containers], + (struct rtattr*)((uint8_t*) NLMSG_DATA(m->hdr) + hlen), + NLMSG_PAYLOAD(m->hdr, hlen)); +} + +int sd_netlink_message_rewind(sd_netlink_message *m, sd_netlink *nl) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(nl, -EINVAL); + + /* don't allow appending to message once parsed */ + message_seal(m); + + for (unsigned 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 = netlink_get_policy_set_and_header_size(nl, m->hdr->nlmsg_type, + &m->containers[0].policy_set, &size); + if (r < 0) + return r; + + if (sd_netlink_message_is_error(m)) + return netlink_message_parse_error(m); + + return netlink_container_parse(m, + &m->containers[0], + (struct rtattr*)((uint8_t*) NLMSG_DATA(m->hdr) + NLMSG_ALIGN(size)), + NLMSG_PAYLOAD(m->hdr, size)); +} + +void message_seal(sd_netlink_message *m) { + assert(m); + + 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..34f527d --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-slot.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#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); + + for (size_t i = 0; i < slot->match_callback.n_groups; i++) + (void) socket_broadcast_group_unref(nl, slot->match_callback.groups[i]); + + slot->match_callback.n_groups = 0; + slot->match_callback.groups = mfree(slot->match_callback.groups); + + break; + default: + assert_not_reached(); + } + + 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..79de817 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-slot.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#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..605e809 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-socket.c @@ -0,0 +1,383 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <malloc.h> +#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 "netlink-internal.h" +#include "netlink-types.h" +#include "socket-util.h" +#include "util.h" + +static int broadcast_groups_get(sd_netlink *nl) { + _cleanup_free_ uint32_t *groups = NULL; + socklen_t len = 0, old_len; + int r; + + assert(nl); + assert(nl->fd >= 0); + + if (getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len) < 0) { + if (errno != ENOPROTOOPT) + return -errno; + + nl->broadcast_group_dont_leave = true; + return 0; + } + + if (len == 0) + return 0; + + groups = new0(uint32_t, len); + if (!groups) + return -ENOMEM; + + old_len = len; + + if (getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len) < 0) + return -errno; + + if (old_len != len) + return -EIO; + + for (unsigned i = 0; i < len; i++) + for (unsigned j = 0; j < sizeof(uint32_t) * 8; j++) + if (groups[i] & (1U << j)) { + unsigned group = i * sizeof(uint32_t) * 8 + j + 1; + + r = hashmap_ensure_put(&nl->broadcast_group_refs, NULL, 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); + + /* ignore EINVAL to allow binding an already bound socket */ + if (bind(nl->fd, &nl->sockaddr.sa, addrlen) < 0 && errno != EINVAL) + return -errno; + + if (getsockname(nl->fd, &nl->sockaddr.sa, &addrlen) < 0) + return -errno; + + return broadcast_groups_get(nl); +} + +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_ensure_allocated(&nl->broadcast_group_refs, NULL); + if (r < 0) + return r; + + return hashmap_replace(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(n_ref)); +} + +static int broadcast_group_join(sd_netlink *nl, unsigned group) { + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + /* group is "unsigned", but netlink(7) says the argument for NETLINK_ADD_MEMBERSHIP is "int" */ + return setsockopt_int(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, group); +} + +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 = broadcast_group_set_ref(nl, group, n_ref); + if (r < 0) + return r; + + if (n_ref > 1) + /* already in the group */ + return 0; + + return broadcast_group_join(nl, group); +} + +static int broadcast_group_leave(sd_netlink *nl, unsigned group) { + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + if (nl->broadcast_group_dont_leave) + return 0; + + /* group is "unsigned", but netlink(7) says the argument for NETLINK_DROP_MEMBERSHIP is "int" */ + return setsockopt_int(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, group); +} + +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); + if (n_ref == 0) + return 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; + + return broadcast_group_leave(nl, group); +} + +/* returns the number of bytes sent, or a negative error code */ +int socket_write_message(sd_netlink *nl, sd_netlink_message *m) { + union sockaddr_union 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 *ret_mcast_group, bool peek) { + union sockaddr_union sender; + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct nl_pktinfo))) control; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = 1, + .msg_name = &sender, + .msg_namelen = sizeof(sender), + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + ssize_t n; + + assert(fd >= 0); + assert(iov); + + n = recvmsg_safe(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); + if (n < 0) { + if (n == -ENOBUFS) + return log_debug_errno(n, "sd-netlink: kernel receive buffer overrun"); + if (ERRNO_IS_TRANSIENT(n)) + return 0; + return (int) n; + } + + if (sender.nl.nl_pid != 0) { + /* not from the kernel, ignore */ + log_debug("sd-netlink: ignoring message from PID %"PRIu32, sender.nl.nl_pid); + + if (peek) { + /* drop the message */ + n = recvmsg_safe(fd, &msg, 0); + if (n < 0) + return (int) n; + } + + return 0; + } + + if (ret_mcast_group) { + struct nl_pktinfo *pi; + + pi = CMSG_FIND_DATA(&msg, SOL_NETLINK, NETLINK_PKTINFO, struct nl_pktinfo); + if (pi) + *ret_mcast_group = pi->group; + else + *ret_mcast_group = 0; + } + + 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 *nl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *first = NULL; + bool multi_part = false, done = false; + size_t len, allocated; + struct iovec iov = {}; + uint32_t group = 0; + unsigned i = 0; + int r; + + assert(nl); + assert(nl->rbuffer); + + /* read nothing, just get the pending message size */ + r = socket_recv_message(nl->fd, &iov, NULL, true); + if (r <= 0) + return r; + else + len = (size_t) r; + + /* make room for the pending message */ + if (!greedy_realloc((void**) &nl->rbuffer, len, sizeof(uint8_t))) + return -ENOMEM; + + allocated = MALLOC_SIZEOF_SAFE(nl->rbuffer); + iov = IOVEC_MAKE(nl->rbuffer, allocated); + + /* read the pending message */ + r = socket_recv_message(nl->fd, &iov, &group, false); + if (r <= 0) + return r; + else + len = (size_t) r; + + if (len > allocated) + /* message did not fit in read buffer */ + return -EIO; + + if (NLMSG_OK(nl->rbuffer, len) && nl->rbuffer->nlmsg_flags & NLM_F_MULTI) { + multi_part = true; + + for (i = 0; i < nl->rqueue_partial_size; i++) + if (message_get_serial(nl->rqueue_partial[i]) == + nl->rbuffer->nlmsg_seq) { + first = nl->rqueue_partial[i]; + break; + } + } + + for (struct nlmsghdr *new_msg = nl->rbuffer; NLMSG_OK(new_msg, len) && !done; new_msg = NLMSG_NEXT(new_msg, len)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + size_t size; + + if (group == 0 && new_msg->nlmsg_pid != nl->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 = netlink_get_policy_set_and_header_size(nl, new_msg->nlmsg_type, NULL, &size); + 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(size)) { + log_debug("sd-netlink: message is shorter than expected, dropping"); + continue; + } + + r = message_new_empty(nl, &m); + if (r < 0) + return r; + + m->multicast_group = 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, nl); + 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 = netlink_rqueue_make_room(nl); + if (r < 0) + return r; + + nl->rqueue[nl->rqueue_size++] = TAKE_PTR(first); + + if (multi_part && (i < nl->rqueue_partial_size)) { + /* remove the message form the partial read queue */ + memmove(nl->rqueue_partial + i, nl->rqueue_partial + i + 1, + sizeof(sd_netlink_message*) * (nl->rqueue_partial_size - i - 1)); + nl->rqueue_partial_size--; + } + + return 1; + } else { + /* we only got a partial multi-part message, push it on the + partial read queue */ + if (i < nl->rqueue_partial_size) + nl->rqueue_partial[i] = TAKE_PTR(first); + else { + r = netlink_rqueue_partial_make_room(nl); + if (r < 0) + return r; + + nl->rqueue_partial[nl->rqueue_partial_size++] = TAKE_PTR(first); + } + + return 0; + } +} diff --git a/src/libsystemd/sd-netlink/netlink-types-genl.c b/src/libsystemd/sd-netlink/netlink-types-genl.c new file mode 100644 index 0000000..6fe9adc --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types-genl.c @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <sys/socket.h> +#include <linux/batman_adv.h> +#include <linux/fou.h> +#include <linux/genetlink.h> +#include <linux/if.h> +#include <linux/if_macsec.h> +#include <linux/l2tp.h> +#include <linux/nl80211.h> +#include <linux/wireguard.h> + +#include "missing_network.h" +#include "netlink-genl.h" +#include "netlink-types-internal.h" + +/***************** genl ctrl type systems *****************/ +static const NLAPolicy genl_ctrl_mcast_group_policies[] = { + [CTRL_ATTR_MCAST_GRP_NAME] = BUILD_POLICY(STRING), + [CTRL_ATTR_MCAST_GRP_ID] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(genl_ctrl_mcast_group); + +static const NLAPolicy genl_ctrl_ops_policies[] = { + [CTRL_ATTR_OP_ID] = BUILD_POLICY(U32), + [CTRL_ATTR_OP_FLAGS] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(genl_ctrl_ops); + +static const NLAPolicy genl_ctrl_policies[] = { + [CTRL_ATTR_FAMILY_ID] = BUILD_POLICY(U16), + [CTRL_ATTR_FAMILY_NAME] = BUILD_POLICY(STRING), + [CTRL_ATTR_VERSION] = BUILD_POLICY(U32), + [CTRL_ATTR_HDRSIZE] = BUILD_POLICY(U32), + [CTRL_ATTR_MAXATTR] = BUILD_POLICY(U32), + [CTRL_ATTR_OPS] = BUILD_POLICY_NESTED(genl_ctrl_ops), + [CTRL_ATTR_MCAST_GROUPS] = BUILD_POLICY_NESTED(genl_ctrl_mcast_group), + /* + [CTRL_ATTR_POLICY] = { .type = NETLINK_TYPE_NESTED, }, + [CTRL_ATTR_OP_POLICY] = { .type = NETLINK_TYPE_NESTED, } + */ + [CTRL_ATTR_OP] = BUILD_POLICY(U32), +}; + +/***************** genl batadv type systems *****************/ +static const NLAPolicy genl_batadv_policies[] = { + [BATADV_ATTR_VERSION] = BUILD_POLICY(STRING), + [BATADV_ATTR_ALGO_NAME] = BUILD_POLICY(STRING), + [BATADV_ATTR_MESH_IFINDEX] = BUILD_POLICY(U32), + [BATADV_ATTR_MESH_IFNAME] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ), + [BATADV_ATTR_MESH_ADDRESS] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_HARD_IFINDEX] = BUILD_POLICY(U32), + [BATADV_ATTR_HARD_IFNAME] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ), + [BATADV_ATTR_HARD_ADDRESS] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_ORIG_ADDRESS] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_TPMETER_RESULT] = BUILD_POLICY(U8), + [BATADV_ATTR_TPMETER_TEST_TIME] = BUILD_POLICY(U32), + [BATADV_ATTR_TPMETER_BYTES] = BUILD_POLICY(U64), + [BATADV_ATTR_TPMETER_COOKIE] = BUILD_POLICY(U32), + [BATADV_ATTR_PAD] = BUILD_POLICY(UNSPEC), + [BATADV_ATTR_ACTIVE] = BUILD_POLICY(FLAG), + [BATADV_ATTR_TT_ADDRESS] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_TT_TTVN] = BUILD_POLICY(U8), + [BATADV_ATTR_TT_LAST_TTVN] = BUILD_POLICY(U8), + [BATADV_ATTR_TT_CRC32] = BUILD_POLICY(U32), + [BATADV_ATTR_TT_VID] = BUILD_POLICY(U16), + [BATADV_ATTR_TT_FLAGS] = BUILD_POLICY(U32), + [BATADV_ATTR_FLAG_BEST] = BUILD_POLICY(FLAG), + [BATADV_ATTR_LAST_SEEN_MSECS] = BUILD_POLICY(U32), + [BATADV_ATTR_NEIGH_ADDRESS] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_TQ] = BUILD_POLICY(U8), + [BATADV_ATTR_THROUGHPUT] = BUILD_POLICY(U32), + [BATADV_ATTR_BANDWIDTH_UP] = BUILD_POLICY(U32), + [BATADV_ATTR_BANDWIDTH_DOWN] = BUILD_POLICY(U32), + [BATADV_ATTR_ROUTER] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_BLA_OWN] = BUILD_POLICY(FLAG), + [BATADV_ATTR_BLA_ADDRESS] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_BLA_VID] = BUILD_POLICY(U16), + [BATADV_ATTR_BLA_BACKBONE] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_BLA_CRC] = BUILD_POLICY(U16), + [BATADV_ATTR_DAT_CACHE_IP4ADDRESS] = BUILD_POLICY(U32), + [BATADV_ATTR_DAT_CACHE_HWADDRESS] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [BATADV_ATTR_DAT_CACHE_VID] = BUILD_POLICY(U16), + [BATADV_ATTR_MCAST_FLAGS] = BUILD_POLICY(U32), + [BATADV_ATTR_MCAST_FLAGS_PRIV] = BUILD_POLICY(U32), + [BATADV_ATTR_VLANID] = BUILD_POLICY(U16), + [BATADV_ATTR_AGGREGATED_OGMS_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_AP_ISOLATION_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_ISOLATION_MARK] = BUILD_POLICY(U32), + [BATADV_ATTR_ISOLATION_MASK] = BUILD_POLICY(U32), + [BATADV_ATTR_BONDING_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_FRAGMENTATION_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_GW_BANDWIDTH_DOWN] = BUILD_POLICY(U32), + [BATADV_ATTR_GW_BANDWIDTH_UP] = BUILD_POLICY(U32), + [BATADV_ATTR_GW_MODE] = BUILD_POLICY(U8), + [BATADV_ATTR_GW_SEL_CLASS] = BUILD_POLICY(U32), + [BATADV_ATTR_HOP_PENALTY] = BUILD_POLICY(U8), + [BATADV_ATTR_LOG_LEVEL] = BUILD_POLICY(U32), + [BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_MULTICAST_FANOUT] = BUILD_POLICY(U32), + [BATADV_ATTR_NETWORK_CODING_ENABLED] = BUILD_POLICY(U8), + [BATADV_ATTR_ORIG_INTERVAL] = BUILD_POLICY(U32), + [BATADV_ATTR_ELP_INTERVAL] = BUILD_POLICY(U32), + [BATADV_ATTR_THROUGHPUT_OVERRIDE] = BUILD_POLICY(U32), +}; + +/***************** genl fou type systems *****************/ +static const NLAPolicy genl_fou_policies[] = { + [FOU_ATTR_PORT] = BUILD_POLICY(U16), + [FOU_ATTR_AF] = BUILD_POLICY(U8), + [FOU_ATTR_IPPROTO] = BUILD_POLICY(U8), + [FOU_ATTR_TYPE] = BUILD_POLICY(U8), + [FOU_ATTR_REMCSUM_NOPARTIAL] = BUILD_POLICY(FLAG), + [FOU_ATTR_LOCAL_V4] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in_addr)), + [FOU_ATTR_PEER_V4] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in_addr)), + [FOU_ATTR_LOCAL_V6] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [FOU_ATTR_PEER_V6] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [FOU_ATTR_PEER_PORT] = BUILD_POLICY(U16), + [FOU_ATTR_IFINDEX] = BUILD_POLICY(U32), +}; + +/***************** genl l2tp type systems *****************/ +static const NLAPolicy genl_l2tp_policies[] = { + [L2TP_ATTR_PW_TYPE] = BUILD_POLICY(U16), + [L2TP_ATTR_ENCAP_TYPE] = BUILD_POLICY(U16), + [L2TP_ATTR_OFFSET] = BUILD_POLICY(U16), + [L2TP_ATTR_DATA_SEQ] = BUILD_POLICY(U16), + [L2TP_ATTR_L2SPEC_TYPE] = BUILD_POLICY(U8), + [L2TP_ATTR_L2SPEC_LEN] = BUILD_POLICY(U8), + [L2TP_ATTR_PROTO_VERSION] = BUILD_POLICY(U8), + [L2TP_ATTR_IFNAME] = BUILD_POLICY(STRING), + [L2TP_ATTR_CONN_ID] = BUILD_POLICY(U32), + [L2TP_ATTR_PEER_CONN_ID] = BUILD_POLICY(U32), + [L2TP_ATTR_SESSION_ID] = BUILD_POLICY(U32), + [L2TP_ATTR_PEER_SESSION_ID] = BUILD_POLICY(U32), + [L2TP_ATTR_UDP_CSUM] = BUILD_POLICY(U8), + [L2TP_ATTR_VLAN_ID] = BUILD_POLICY(U16), + [L2TP_ATTR_RECV_SEQ] = BUILD_POLICY(U8), + [L2TP_ATTR_SEND_SEQ] = BUILD_POLICY(U8), + [L2TP_ATTR_LNS_MODE] = BUILD_POLICY(U8), + [L2TP_ATTR_USING_IPSEC] = BUILD_POLICY(U8), + [L2TP_ATTR_FD] = BUILD_POLICY(U32), + [L2TP_ATTR_IP_SADDR] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in_addr)), + [L2TP_ATTR_IP_DADDR] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in_addr)), + [L2TP_ATTR_UDP_SPORT] = BUILD_POLICY(U16), + [L2TP_ATTR_UDP_DPORT] = BUILD_POLICY(U16), + [L2TP_ATTR_IP6_SADDR] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [L2TP_ATTR_IP6_DADDR] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [L2TP_ATTR_UDP_ZERO_CSUM6_TX] = BUILD_POLICY(FLAG), + [L2TP_ATTR_UDP_ZERO_CSUM6_RX] = BUILD_POLICY(FLAG), +}; + +/***************** genl macsec type systems *****************/ +static const NLAPolicy genl_macsec_rxsc_policies[] = { + [MACSEC_RXSC_ATTR_SCI] = BUILD_POLICY(U64), +}; + +DEFINE_POLICY_SET(genl_macsec_rxsc); + +static const NLAPolicy genl_macsec_sa_policies[] = { + [MACSEC_SA_ATTR_AN] = BUILD_POLICY(U8), + [MACSEC_SA_ATTR_ACTIVE] = BUILD_POLICY(U8), + [MACSEC_SA_ATTR_PN] = BUILD_POLICY(U32), + [MACSEC_SA_ATTR_KEYID] = BUILD_POLICY_WITH_SIZE(BINARY, MACSEC_KEYID_LEN), + [MACSEC_SA_ATTR_KEY] = BUILD_POLICY_WITH_SIZE(BINARY, MACSEC_MAX_KEY_LEN), +}; + +DEFINE_POLICY_SET(genl_macsec_sa); + +static const NLAPolicy genl_macsec_policies[] = { + [MACSEC_ATTR_IFINDEX] = BUILD_POLICY(U32), + [MACSEC_ATTR_RXSC_CONFIG] = BUILD_POLICY_NESTED(genl_macsec_rxsc), + [MACSEC_ATTR_SA_CONFIG] = BUILD_POLICY_NESTED(genl_macsec_sa), +}; + +/***************** genl NetLabel type systems *****************/ +static const NLAPolicy genl_netlabel_policies[] = { + [NLBL_UNLABEL_A_IPV4ADDR] = BUILD_POLICY(IN_ADDR), + [NLBL_UNLABEL_A_IPV4MASK] = BUILD_POLICY(IN_ADDR), + [NLBL_UNLABEL_A_IPV6ADDR] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [NLBL_UNLABEL_A_IPV6MASK] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [NLBL_UNLABEL_A_IFACE] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ-1), + [NLBL_UNLABEL_A_SECCTX] = BUILD_POLICY(STRING), +}; + +/***************** genl nl80211 type systems *****************/ +static const NLAPolicy genl_nl80211_policies[] = { + [NL80211_ATTR_WIPHY] = BUILD_POLICY(U32), + [NL80211_ATTR_WIPHY_NAME] = BUILD_POLICY(STRING), + [NL80211_ATTR_IFINDEX] = BUILD_POLICY(U32), + [NL80211_ATTR_IFNAME] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ-1), + [NL80211_ATTR_IFTYPE] = BUILD_POLICY(U32), + [NL80211_ATTR_MAC] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [NL80211_ATTR_SSID] = BUILD_POLICY_WITH_SIZE(BINARY, IEEE80211_MAX_SSID_LEN), + [NL80211_ATTR_STATUS_CODE] = BUILD_POLICY(U16), + [NL80211_ATTR_4ADDR] = BUILD_POLICY(U8), +}; + +/***************** genl wireguard type systems *****************/ +static const NLAPolicy genl_wireguard_allowedip_policies[] = { + [WGALLOWEDIP_A_FAMILY] = BUILD_POLICY(U16), + [WGALLOWEDIP_A_IPADDR] = BUILD_POLICY(IN_ADDR), + [WGALLOWEDIP_A_CIDR_MASK] = BUILD_POLICY(U8), +}; + +DEFINE_POLICY_SET(genl_wireguard_allowedip); + +static const NLAPolicy genl_wireguard_peer_policies[] = { + [WGPEER_A_PUBLIC_KEY] = BUILD_POLICY_WITH_SIZE(BINARY, WG_KEY_LEN), + [WGPEER_A_FLAGS] = BUILD_POLICY(U32), + [WGPEER_A_PRESHARED_KEY] = BUILD_POLICY_WITH_SIZE(BINARY, WG_KEY_LEN), + [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = BUILD_POLICY(U16), + [WGPEER_A_ENDPOINT] = BUILD_POLICY(SOCKADDR), + [WGPEER_A_ALLOWEDIPS] = BUILD_POLICY_NESTED(genl_wireguard_allowedip), +}; + +DEFINE_POLICY_SET(genl_wireguard_peer); + +static const NLAPolicy genl_wireguard_policies[] = { + [WGDEVICE_A_IFINDEX] = BUILD_POLICY(U32), + [WGDEVICE_A_IFNAME] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ-1), + [WGDEVICE_A_FLAGS] = BUILD_POLICY(U32), + [WGDEVICE_A_PRIVATE_KEY] = BUILD_POLICY_WITH_SIZE(BINARY, WG_KEY_LEN), + [WGDEVICE_A_LISTEN_PORT] = BUILD_POLICY(U16), + [WGDEVICE_A_FWMARK] = BUILD_POLICY(U32), + [WGDEVICE_A_PEERS] = BUILD_POLICY_NESTED(genl_wireguard_peer), +}; + +/***************** genl families *****************/ +static const NLAPolicySetUnionElement genl_policy_set_union_elements[] = { + BUILD_UNION_ELEMENT_BY_STRING(CTRL_GENL_NAME, genl_ctrl), + BUILD_UNION_ELEMENT_BY_STRING(BATADV_NL_NAME, genl_batadv), + BUILD_UNION_ELEMENT_BY_STRING(FOU_GENL_NAME, genl_fou), + BUILD_UNION_ELEMENT_BY_STRING(L2TP_GENL_NAME, genl_l2tp), + BUILD_UNION_ELEMENT_BY_STRING(MACSEC_GENL_NAME, genl_macsec), + BUILD_UNION_ELEMENT_BY_STRING(NETLBL_NLTYPE_UNLABELED_NAME, genl_netlabel), + BUILD_UNION_ELEMENT_BY_STRING(NL80211_GENL_NAME, genl_nl80211), + BUILD_UNION_ELEMENT_BY_STRING(WG_GENL_NAME, genl_wireguard), +}; + +/* This is the root type system union, so match_attribute is not necessary. */ +DEFINE_POLICY_SET_UNION(genl, 0); + +const NLAPolicySet *genl_get_policy_set_by_name(const char *name) { + return policy_set_union_get_policy_set_by_string(&genl_policy_set_union, name); +} diff --git a/src/libsystemd/sd-netlink/netlink-types-internal.h b/src/libsystemd/sd-netlink/netlink-types-internal.h new file mode 100644 index 0000000..1412514 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types-internal.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "macro.h" +#include "netlink-types.h" + +/* C.f. see 'struct nla_policy' at include/net/netlink.h. */ +struct NLAPolicy { + NLAType type; + size_t size; + union { + const NLAPolicySet *policy_set; + const NLAPolicySetUnion *policy_set_union; + }; +}; + +struct NLAPolicySet { + uint16_t count; + const NLAPolicy *policies; +}; + +typedef struct NLAPolicySetUnionElement { + union { + int family; /* used by NETLINK_TYPE_NESTED_UNION_BY_FAMILY */ + const char *string; /* used by NETLINK_TYPE_NESTED_UNION_BY_STRING */ + }; + NLAPolicySet policy_set; +} NLAPolicySetUnionElement; + +struct NLAPolicySetUnion { + size_t count; + const NLAPolicySetUnionElement *elements; + uint16_t match_attribute; /* used by NETLINK_TYPE_NESTED_UNION_BY_STRING */ +}; + +#define BUILD_POLICY_WITH_SIZE(t, n) \ + { .type = NETLINK_TYPE_##t, .size = n } +#define BUILD_POLICY(t) \ + BUILD_POLICY_WITH_SIZE(t, 0) +#define BUILD_POLICY_NESTED_WITH_SIZE(name, n) \ + { .type = NETLINK_TYPE_NESTED, .size = n, .policy_set = &name##_policy_set } +#define BUILD_POLICY_NESTED(name) \ + BUILD_POLICY_NESTED_WITH_SIZE(name, 0) +#define _BUILD_POLICY_NESTED_UNION(name, by) \ + { .type = NETLINK_TYPE_NESTED_UNION_BY_##by, .policy_set_union = &name##_policy_set_union } +#define BUILD_POLICY_NESTED_UNION_BY_STRING(name) \ + _BUILD_POLICY_NESTED_UNION(name, STRING) +#define BUILD_POLICY_NESTED_UNION_BY_FAMILY(name) \ + _BUILD_POLICY_NESTED_UNION(name, FAMILY) + +#define _BUILD_POLICY_SET(name) \ + { .count = ELEMENTSOF(name##_policies), .policies = name##_policies } +#define DEFINE_POLICY_SET(name) \ + static const NLAPolicySet name##_policy_set = _BUILD_POLICY_SET(name) + +# define BUILD_UNION_ELEMENT_BY_STRING(s, name) \ + { .string = s, .policy_set = _BUILD_POLICY_SET(name) } +# define BUILD_UNION_ELEMENT_BY_FAMILY(f, name) \ + { .family = f, .policy_set = _BUILD_POLICY_SET(name) } + +#define DEFINE_POLICY_SET_UNION(name, attr) \ + static const NLAPolicySetUnion name##_policy_set_union = { \ + .count = ELEMENTSOF(name##_policy_set_union_elements), \ + .elements = name##_policy_set_union_elements, \ + .match_attribute = attr, \ + } diff --git a/src/libsystemd/sd-netlink/netlink-types-nfnl.c b/src/libsystemd/sd-netlink/netlink-types-nfnl.c new file mode 100644 index 0000000..8ef4d45 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types-nfnl.c @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/if.h> +#include <linux/netfilter/nf_tables.h> +#include <linux/netfilter/nfnetlink.h> + +#include "netlink-types-internal.h" + +static const NLAPolicy nfnl_nft_table_policies[] = { + [NFTA_TABLE_NAME] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_TABLE_FLAGS] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(nfnl_nft_table); + +static const NLAPolicy nfnl_nft_chain_hook_policies[] = { + [NFTA_HOOK_HOOKNUM] = BUILD_POLICY(U32), + [NFTA_HOOK_PRIORITY] = BUILD_POLICY(U32), + [NFTA_HOOK_DEV] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ - 1), +}; + +DEFINE_POLICY_SET(nfnl_nft_chain_hook); + +static const NLAPolicy nfnl_nft_chain_policies[] = { + [NFTA_CHAIN_TABLE] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_CHAIN_NAME] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_CHAIN_HOOK] = BUILD_POLICY_NESTED(nfnl_nft_chain_hook), + [NFTA_CHAIN_TYPE] = BUILD_POLICY_WITH_SIZE(STRING, 16), + [NFTA_CHAIN_FLAGS] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(nfnl_nft_chain); + +static const NLAPolicy nfnl_nft_expr_meta_policies[] = { + [NFTA_META_DREG] = BUILD_POLICY(U32), + [NFTA_META_KEY] = BUILD_POLICY(U32), + [NFTA_META_SREG] = BUILD_POLICY(U32), +}; + +static const NLAPolicy nfnl_nft_expr_payload_policies[] = { + [NFTA_PAYLOAD_DREG] = BUILD_POLICY(U32), + [NFTA_PAYLOAD_BASE] = BUILD_POLICY(U32), + [NFTA_PAYLOAD_OFFSET] = BUILD_POLICY(U32), + [NFTA_PAYLOAD_LEN] = BUILD_POLICY(U32), +}; + +static const NLAPolicy nfnl_nft_expr_nat_policies[] = { + [NFTA_NAT_TYPE] = BUILD_POLICY(U32), + [NFTA_NAT_FAMILY] = BUILD_POLICY(U32), + [NFTA_NAT_REG_ADDR_MIN] = BUILD_POLICY(U32), + [NFTA_NAT_REG_ADDR_MAX] = BUILD_POLICY(U32), + [NFTA_NAT_REG_PROTO_MIN] = BUILD_POLICY(U32), + [NFTA_NAT_REG_PROTO_MAX] = BUILD_POLICY(U32), + [NFTA_NAT_FLAGS] = BUILD_POLICY(U32), +}; + +static const NLAPolicy nfnl_nft_data_policies[] = { + [NFTA_DATA_VALUE] = { .type = NETLINK_TYPE_BINARY }, +}; + +DEFINE_POLICY_SET(nfnl_nft_data); + +static const NLAPolicy nfnl_nft_expr_bitwise_policies[] = { + [NFTA_BITWISE_SREG] = BUILD_POLICY(U32), + [NFTA_BITWISE_DREG] = BUILD_POLICY(U32), + [NFTA_BITWISE_LEN] = BUILD_POLICY(U32), + [NFTA_BITWISE_MASK] = BUILD_POLICY_NESTED(nfnl_nft_data), + [NFTA_BITWISE_XOR] = BUILD_POLICY_NESTED(nfnl_nft_data), +}; + +static const NLAPolicy nfnl_nft_expr_cmp_policies[] = { + [NFTA_CMP_SREG] = BUILD_POLICY(U32), + [NFTA_CMP_OP] = BUILD_POLICY(U32), + [NFTA_CMP_DATA] = BUILD_POLICY_NESTED(nfnl_nft_data), +}; + +static const NLAPolicy nfnl_nft_expr_fib_policies[] = { + [NFTA_FIB_DREG] = BUILD_POLICY(U32), + [NFTA_FIB_RESULT] = BUILD_POLICY(U32), + [NFTA_FIB_FLAGS] = BUILD_POLICY(U32), +}; + +static const NLAPolicy nfnl_nft_expr_lookup_policies[] = { + [NFTA_LOOKUP_SET] = { .type = NETLINK_TYPE_STRING }, + [NFTA_LOOKUP_SREG] = BUILD_POLICY(U32), + [NFTA_LOOKUP_DREG] = BUILD_POLICY(U32), + [NFTA_LOOKUP_FLAGS] = BUILD_POLICY(U32), +}; + +static const NLAPolicy nfnl_nft_expr_masq_policies[] = { + [NFTA_MASQ_FLAGS] = BUILD_POLICY(U32), + [NFTA_MASQ_REG_PROTO_MIN] = BUILD_POLICY(U32), + [NFTA_MASQ_REG_PROTO_MAX] = BUILD_POLICY(U32), +}; + +static const NLAPolicySetUnionElement nfnl_expr_data_policy_set_union_elements[] = { + BUILD_UNION_ELEMENT_BY_STRING("bitwise", nfnl_nft_expr_bitwise), + BUILD_UNION_ELEMENT_BY_STRING("cmp", nfnl_nft_expr_cmp), + BUILD_UNION_ELEMENT_BY_STRING("fib", nfnl_nft_expr_fib), + BUILD_UNION_ELEMENT_BY_STRING("lookup", nfnl_nft_expr_lookup), + BUILD_UNION_ELEMENT_BY_STRING("masq", nfnl_nft_expr_masq), + BUILD_UNION_ELEMENT_BY_STRING("meta", nfnl_nft_expr_meta), + BUILD_UNION_ELEMENT_BY_STRING("nat", nfnl_nft_expr_nat), + BUILD_UNION_ELEMENT_BY_STRING("payload", nfnl_nft_expr_payload), +}; + +DEFINE_POLICY_SET_UNION(nfnl_expr_data, NFTA_EXPR_NAME); + +static const NLAPolicy nfnl_nft_rule_expr_policies[] = { + [NFTA_EXPR_NAME] = BUILD_POLICY_WITH_SIZE(STRING, 16), + [NFTA_EXPR_DATA] = BUILD_POLICY_NESTED_UNION_BY_STRING(nfnl_expr_data), +}; + +DEFINE_POLICY_SET(nfnl_nft_rule_expr); + +static const NLAPolicy nfnl_nft_rule_policies[] = { + [NFTA_RULE_TABLE] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_RULE_CHAIN] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_RULE_EXPRESSIONS] = BUILD_POLICY_NESTED(nfnl_nft_rule_expr), +}; + +DEFINE_POLICY_SET(nfnl_nft_rule); + +static const NLAPolicy nfnl_nft_set_policies[] = { + [NFTA_SET_TABLE] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_SET_NAME] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_SET_FLAGS] = BUILD_POLICY(U32), + [NFTA_SET_KEY_TYPE] = BUILD_POLICY(U32), + [NFTA_SET_KEY_LEN] = BUILD_POLICY(U32), + [NFTA_SET_DATA_TYPE] = BUILD_POLICY(U32), + [NFTA_SET_DATA_LEN] = BUILD_POLICY(U32), + [NFTA_SET_POLICY] = BUILD_POLICY(U32), + [NFTA_SET_ID] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(nfnl_nft_set); + +static const NLAPolicy nfnl_nft_setelem_policies[] = { + [NFTA_SET_ELEM_KEY] = BUILD_POLICY_NESTED(nfnl_nft_data), + [NFTA_SET_ELEM_DATA] = BUILD_POLICY_NESTED(nfnl_nft_data), + [NFTA_SET_ELEM_FLAGS] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(nfnl_nft_setelem); + +static const NLAPolicy nfnl_nft_setelem_list_policies[] = { + [NFTA_SET_ELEM_LIST_TABLE] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_SET_ELEM_LIST_SET] = BUILD_POLICY_WITH_SIZE(STRING, NFT_TABLE_MAXNAMELEN - 1), + [NFTA_SET_ELEM_LIST_ELEMENTS] = BUILD_POLICY_NESTED(nfnl_nft_setelem), +}; + +DEFINE_POLICY_SET(nfnl_nft_setelem_list); + +static const NLAPolicy nfnl_subsys_nft_policies[] = { + [NFT_MSG_DELTABLE] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_nft_table, sizeof(struct nfgenmsg)), + [NFT_MSG_NEWTABLE] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_nft_table, sizeof(struct nfgenmsg)), + [NFT_MSG_NEWCHAIN] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_nft_chain, sizeof(struct nfgenmsg)), + [NFT_MSG_NEWRULE] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_nft_rule, sizeof(struct nfgenmsg)), + [NFT_MSG_NEWSET] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_nft_set, sizeof(struct nfgenmsg)), + [NFT_MSG_NEWSETELEM] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_nft_setelem_list, sizeof(struct nfgenmsg)), + [NFT_MSG_DELSETELEM] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_nft_setelem_list, sizeof(struct nfgenmsg)), +}; + +DEFINE_POLICY_SET(nfnl_subsys_nft); + +static const NLAPolicy nfnl_msg_batch_policies[] = { + [NFNL_BATCH_GENID] = BUILD_POLICY(U32) +}; + +DEFINE_POLICY_SET(nfnl_msg_batch); + +static const NLAPolicy nfnl_subsys_none_policies[] = { + [NFNL_MSG_BATCH_BEGIN] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_msg_batch, sizeof(struct nfgenmsg)), + [NFNL_MSG_BATCH_END] = BUILD_POLICY_NESTED_WITH_SIZE(nfnl_msg_batch, sizeof(struct nfgenmsg)), +}; + +DEFINE_POLICY_SET(nfnl_subsys_none); + +static const NLAPolicy nfnl_policies[] = { + [NFNL_SUBSYS_NONE] = BUILD_POLICY_NESTED(nfnl_subsys_none), + [NFNL_SUBSYS_NFTABLES] = BUILD_POLICY_NESTED(nfnl_subsys_nft), +}; + +DEFINE_POLICY_SET(nfnl); + +const NLAPolicy *nfnl_get_policy(uint16_t nlmsg_type) { + const NLAPolicySet *subsys; + + subsys = policy_set_get_policy_set(&nfnl_policy_set, NFNL_SUBSYS_ID(nlmsg_type)); + if (!subsys) + return NULL; + + return policy_set_get_policy(subsys, NFNL_MSG_TYPE(nlmsg_type)); +} diff --git a/src/libsystemd/sd-netlink/netlink-types-rtnl.c b/src/libsystemd/sd-netlink/netlink-types-rtnl.c new file mode 100644 index 0000000..919512d --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types-rtnl.c @@ -0,0 +1,1228 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <sys/socket.h> +#include <linux/batman_adv.h> +#include <linux/can/netlink.h> +#include <linux/can/vxcan.h> +#include <linux/cfm_bridge.h> +#include <linux/fib_rules.h> +#include <linux/fou.h> +#include <linux/if.h> +#include <linux/if_addr.h> +#include <linux/if_addrlabel.h> +#include <linux/if_bridge.h> +#include <linux/if_link.h> +#include <linux/if_macsec.h> +#include <linux/if_tunnel.h> +#include <linux/ip.h> +#include <linux/l2tp.h> +#include <linux/netlink.h> +#include <linux/nexthop.h> +#include <linux/nl80211.h> +#include <linux/pkt_sched.h> +#include <linux/rtnetlink.h> +#include <linux/veth.h> +#include <linux/wireguard.h> + +#include "missing_network.h" +#include "netlink-types-internal.h" + +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_15, + _BOND_ARP_TARGETS_MAX, +}; + +assert_cc(_BOND_ARP_TARGETS_MAX == BOND_MAX_ARP_TARGETS); + +static const NLAPolicySet rtnl_link_policy_set; + +static const NLAPolicy rtnl_link_info_data_bareudp_policies[] = { + [IFLA_BAREUDP_PORT] = BUILD_POLICY(U16), + [IFLA_BAREUDP_ETHERTYPE] = BUILD_POLICY(U16), + [IFLA_BAREUDP_SRCPORT_MIN] = BUILD_POLICY(U16), + [IFLA_BAREUDP_MULTIPROTO_MODE] = BUILD_POLICY(FLAG), +}; + +static const NLAPolicy rtnl_link_info_data_batadv_policies[] = { + [IFLA_BATADV_ALGO_NAME] = BUILD_POLICY_WITH_SIZE(STRING, 20), +}; + +static const NLAPolicy rtnl_bond_arp_ip_target_policies[] = { + [BOND_ARP_TARGETS_0] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_1] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_2] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_3] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_4] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_5] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_6] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_7] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_8] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_9] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_10] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_11] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_12] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_13] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_14] = BUILD_POLICY(U32), + [BOND_ARP_TARGETS_15] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bond_arp_ip_target); + +static const NLAPolicy rtnl_bond_ad_info_policies[] = { + [IFLA_BOND_AD_INFO_AGGREGATOR] = BUILD_POLICY(U16), + [IFLA_BOND_AD_INFO_NUM_PORTS] = BUILD_POLICY(U16), + [IFLA_BOND_AD_INFO_ACTOR_KEY] = BUILD_POLICY(U16), + [IFLA_BOND_AD_INFO_PARTNER_KEY] = BUILD_POLICY(U16), + [IFLA_BOND_AD_INFO_PARTNER_MAC] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), +}; + +DEFINE_POLICY_SET(rtnl_bond_ad_info); + +static const NLAPolicy rtnl_link_info_data_bond_policies[] = { + [IFLA_BOND_MODE] = BUILD_POLICY(U8), + [IFLA_BOND_ACTIVE_SLAVE] = BUILD_POLICY(U32), + [IFLA_BOND_MIIMON] = BUILD_POLICY(U32), + [IFLA_BOND_UPDELAY] = BUILD_POLICY(U32), + [IFLA_BOND_DOWNDELAY] = BUILD_POLICY(U32), + [IFLA_BOND_USE_CARRIER] = BUILD_POLICY(U8), + [IFLA_BOND_ARP_INTERVAL] = BUILD_POLICY(U32), + [IFLA_BOND_ARP_IP_TARGET] = BUILD_POLICY_NESTED(rtnl_bond_arp_ip_target), + [IFLA_BOND_ARP_VALIDATE] = BUILD_POLICY(U32), + [IFLA_BOND_ARP_ALL_TARGETS] = BUILD_POLICY(U32), + [IFLA_BOND_PRIMARY] = BUILD_POLICY(U32), + [IFLA_BOND_PRIMARY_RESELECT] = BUILD_POLICY(U8), + [IFLA_BOND_FAIL_OVER_MAC] = BUILD_POLICY(U8), + [IFLA_BOND_XMIT_HASH_POLICY] = BUILD_POLICY(U8), + [IFLA_BOND_RESEND_IGMP] = BUILD_POLICY(U32), + [IFLA_BOND_NUM_PEER_NOTIF] = BUILD_POLICY(U8), + [IFLA_BOND_ALL_SLAVES_ACTIVE] = BUILD_POLICY(U8), + [IFLA_BOND_MIN_LINKS] = BUILD_POLICY(U32), + [IFLA_BOND_LP_INTERVAL] = BUILD_POLICY(U32), + [IFLA_BOND_PACKETS_PER_SLAVE] = BUILD_POLICY(U32), + [IFLA_BOND_AD_LACP_RATE] = BUILD_POLICY(U8), + [IFLA_BOND_AD_SELECT] = BUILD_POLICY(U8), + [IFLA_BOND_AD_INFO] = BUILD_POLICY_NESTED(rtnl_bond_ad_info), + [IFLA_BOND_AD_ACTOR_SYS_PRIO] = BUILD_POLICY(U16), + [IFLA_BOND_AD_USER_PORT_KEY] = BUILD_POLICY(U16), + [IFLA_BOND_AD_ACTOR_SYSTEM] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [IFLA_BOND_TLB_DYNAMIC_LB] = BUILD_POLICY(U8), + [IFLA_BOND_PEER_NOTIF_DELAY] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_link_info_data_bridge_policies[] = { + [IFLA_BR_FORWARD_DELAY] = BUILD_POLICY(U32), + [IFLA_BR_HELLO_TIME] = BUILD_POLICY(U32), + [IFLA_BR_MAX_AGE] = BUILD_POLICY(U32), + [IFLA_BR_AGEING_TIME] = BUILD_POLICY(U32), + [IFLA_BR_STP_STATE] = BUILD_POLICY(U32), + [IFLA_BR_PRIORITY] = BUILD_POLICY(U16), + [IFLA_BR_VLAN_FILTERING] = BUILD_POLICY(U8), + [IFLA_BR_VLAN_PROTOCOL] = BUILD_POLICY(U16), + [IFLA_BR_GROUP_FWD_MASK] = BUILD_POLICY(U16), + [IFLA_BR_ROOT_ID] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_bridge_id)), + [IFLA_BR_BRIDGE_ID] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_bridge_id)), + [IFLA_BR_ROOT_PORT] = BUILD_POLICY(U16), + [IFLA_BR_ROOT_PATH_COST] = BUILD_POLICY(U32), + [IFLA_BR_TOPOLOGY_CHANGE] = BUILD_POLICY(U8), + [IFLA_BR_TOPOLOGY_CHANGE_DETECTED] = BUILD_POLICY(U8), + [IFLA_BR_HELLO_TIMER] = BUILD_POLICY(U64), + [IFLA_BR_TCN_TIMER] = BUILD_POLICY(U64), + [IFLA_BR_TOPOLOGY_CHANGE_TIMER] = BUILD_POLICY(U64), + [IFLA_BR_GC_TIMER] = BUILD_POLICY(U64), + [IFLA_BR_GROUP_ADDR] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [IFLA_BR_FDB_FLUSH] = BUILD_POLICY(FLAG), + [IFLA_BR_MCAST_ROUTER] = BUILD_POLICY(U8), + [IFLA_BR_MCAST_SNOOPING] = BUILD_POLICY(U8), + [IFLA_BR_MCAST_QUERY_USE_IFADDR] = BUILD_POLICY(U8), + [IFLA_BR_MCAST_QUERIER] = BUILD_POLICY(U8), + [IFLA_BR_MCAST_HASH_ELASTICITY] = BUILD_POLICY(U32), + [IFLA_BR_MCAST_HASH_MAX] = BUILD_POLICY(U32), + [IFLA_BR_MCAST_LAST_MEMBER_CNT] = BUILD_POLICY(U32), + [IFLA_BR_MCAST_STARTUP_QUERY_CNT] = BUILD_POLICY(U32), + [IFLA_BR_MCAST_LAST_MEMBER_INTVL] = BUILD_POLICY(U64), + [IFLA_BR_MCAST_MEMBERSHIP_INTVL] = BUILD_POLICY(U64), + [IFLA_BR_MCAST_QUERIER_INTVL] = BUILD_POLICY(U64), + [IFLA_BR_MCAST_QUERY_INTVL] = BUILD_POLICY(U64), + [IFLA_BR_MCAST_QUERY_RESPONSE_INTVL] = BUILD_POLICY(U64), + [IFLA_BR_MCAST_STARTUP_QUERY_INTVL] = BUILD_POLICY(U64), + [IFLA_BR_NF_CALL_IPTABLES] = BUILD_POLICY(U8), + [IFLA_BR_NF_CALL_IP6TABLES] = BUILD_POLICY(U8), + [IFLA_BR_NF_CALL_ARPTABLES] = BUILD_POLICY(U8), + [IFLA_BR_VLAN_DEFAULT_PVID] = BUILD_POLICY(U16), + [IFLA_BR_VLAN_STATS_ENABLED] = BUILD_POLICY(U8), + [IFLA_BR_MCAST_STATS_ENABLED] = BUILD_POLICY(U8), + [IFLA_BR_MCAST_IGMP_VERSION] = BUILD_POLICY(U8), + [IFLA_BR_MCAST_MLD_VERSION] = BUILD_POLICY(U8), + [IFLA_BR_VLAN_STATS_PER_PORT] = BUILD_POLICY(U8), + [IFLA_BR_MULTI_BOOLOPT] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct br_boolopt_multi)), +}; + +static const NLAPolicy rtnl_link_info_data_can_policies[] = { + [IFLA_CAN_BITTIMING] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_bittiming)), + [IFLA_CAN_BITTIMING_CONST] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_bittiming_const)), + [IFLA_CAN_CLOCK] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_clock)), + [IFLA_CAN_STATE] = BUILD_POLICY(U32), + [IFLA_CAN_CTRLMODE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_ctrlmode)), + [IFLA_CAN_RESTART_MS] = BUILD_POLICY(U32), + [IFLA_CAN_RESTART] = BUILD_POLICY(U32), + [IFLA_CAN_BERR_COUNTER] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_berr_counter)), + [IFLA_CAN_DATA_BITTIMING] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_bittiming)), + [IFLA_CAN_DATA_BITTIMING_CONST] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_bittiming_const)), + [IFLA_CAN_TERMINATION] = BUILD_POLICY(U16), + [IFLA_CAN_TERMINATION_CONST] = BUILD_POLICY(BINARY), /* size = termination_const_cnt * sizeof(u16) */ + [IFLA_CAN_BITRATE_CONST] = BUILD_POLICY(BINARY), /* size = bitrate_const_cnt * sizeof(u32) */ + [IFLA_CAN_DATA_BITRATE_CONST] = BUILD_POLICY(BINARY), /* size = data_bitrate_const_cnt * sizeof(u32) */ + [IFLA_CAN_BITRATE_MAX] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_link_info_data_geneve_policies[] = { + [IFLA_GENEVE_ID] = BUILD_POLICY(U32), + [IFLA_GENEVE_REMOTE] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in_addr)), + [IFLA_GENEVE_TTL] = BUILD_POLICY(U8), + [IFLA_GENEVE_TOS] = BUILD_POLICY(U8), + [IFLA_GENEVE_PORT] = BUILD_POLICY(U16), + [IFLA_GENEVE_COLLECT_METADATA] = BUILD_POLICY(FLAG), + [IFLA_GENEVE_REMOTE6] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [IFLA_GENEVE_UDP_CSUM] = BUILD_POLICY(U8), + [IFLA_GENEVE_UDP_ZERO_CSUM6_TX] = BUILD_POLICY(U8), + [IFLA_GENEVE_UDP_ZERO_CSUM6_RX] = BUILD_POLICY(U8), + [IFLA_GENEVE_LABEL] = BUILD_POLICY(U32), + [IFLA_GENEVE_TTL_INHERIT] = BUILD_POLICY(U8), + [IFLA_GENEVE_DF] = BUILD_POLICY(U8), +}; + +static const NLAPolicy rtnl_link_info_data_gre_policies[] = { + [IFLA_GRE_LINK] = BUILD_POLICY(U32), + [IFLA_GRE_IFLAGS] = BUILD_POLICY(U16), + [IFLA_GRE_OFLAGS] = BUILD_POLICY(U16), + [IFLA_GRE_IKEY] = BUILD_POLICY(U32), + [IFLA_GRE_OKEY] = BUILD_POLICY(U32), + [IFLA_GRE_LOCAL] = BUILD_POLICY(IN_ADDR), + [IFLA_GRE_REMOTE] = BUILD_POLICY(IN_ADDR), + [IFLA_GRE_TTL] = BUILD_POLICY(U8), + [IFLA_GRE_TOS] = BUILD_POLICY(U8), + [IFLA_GRE_PMTUDISC] = BUILD_POLICY(U8), + [IFLA_GRE_ENCAP_LIMIT] = BUILD_POLICY(U8), + [IFLA_GRE_FLOWINFO] = BUILD_POLICY(U32), + [IFLA_GRE_FLAGS] = BUILD_POLICY(U32), + [IFLA_GRE_ENCAP_TYPE] = BUILD_POLICY(U16), + [IFLA_GRE_ENCAP_FLAGS] = BUILD_POLICY(U16), + [IFLA_GRE_ENCAP_SPORT] = BUILD_POLICY(U16), + [IFLA_GRE_ENCAP_DPORT] = BUILD_POLICY(U16), + [IFLA_GRE_COLLECT_METADATA] = BUILD_POLICY(FLAG), + [IFLA_GRE_IGNORE_DF] = BUILD_POLICY(U8), + [IFLA_GRE_FWMARK] = BUILD_POLICY(U32), + [IFLA_GRE_ERSPAN_INDEX] = BUILD_POLICY(U32), + [IFLA_GRE_ERSPAN_VER] = BUILD_POLICY(U8), + [IFLA_GRE_ERSPAN_DIR] = BUILD_POLICY(U8), + [IFLA_GRE_ERSPAN_HWID] = BUILD_POLICY(U16), +}; + +static const NLAPolicy rtnl_link_info_data_ipoib_policies[] = { + [IFLA_IPOIB_PKEY] = BUILD_POLICY(U16), + [IFLA_IPOIB_MODE] = BUILD_POLICY(U16), + [IFLA_IPOIB_UMCAST] = BUILD_POLICY(U16), +}; + +/* IFLA_IPTUN_ attributes are used in ipv4/ipip.c, ipv6/ip6_tunnel.c, and ipv6/sit.c. And unfortunately, + * IFLA_IPTUN_FLAGS is used with different types, ugh... */ +#define DEFINE_IPTUN_TYPES(name, flags_type) \ + static const NLAPolicy rtnl_link_info_data_##name##_policies[] = { \ + [IFLA_IPTUN_LINK] = BUILD_POLICY(U32), \ + [IFLA_IPTUN_LOCAL] = BUILD_POLICY(IN_ADDR), \ + [IFLA_IPTUN_REMOTE] = BUILD_POLICY(IN_ADDR), \ + [IFLA_IPTUN_TTL] = BUILD_POLICY(U8), \ + [IFLA_IPTUN_TOS] = BUILD_POLICY(U8), \ + [IFLA_IPTUN_ENCAP_LIMIT] = BUILD_POLICY(U8), \ + [IFLA_IPTUN_FLOWINFO] = BUILD_POLICY(U32), \ + [IFLA_IPTUN_FLAGS] = BUILD_POLICY(flags_type), \ + [IFLA_IPTUN_PROTO] = BUILD_POLICY(U8), \ + [IFLA_IPTUN_PMTUDISC] = BUILD_POLICY(U8), \ + [IFLA_IPTUN_6RD_PREFIX] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), \ + [IFLA_IPTUN_6RD_RELAY_PREFIX] = BUILD_POLICY(U32), \ + [IFLA_IPTUN_6RD_PREFIXLEN] = BUILD_POLICY(U16), \ + [IFLA_IPTUN_6RD_RELAY_PREFIXLEN] = BUILD_POLICY(U16), \ + [IFLA_IPTUN_ENCAP_TYPE] = BUILD_POLICY(U16), \ + [IFLA_IPTUN_ENCAP_FLAGS] = BUILD_POLICY(U16), \ + [IFLA_IPTUN_ENCAP_SPORT] = BUILD_POLICY(U16), \ + [IFLA_IPTUN_ENCAP_DPORT] = BUILD_POLICY(U16), \ + [IFLA_IPTUN_COLLECT_METADATA] = BUILD_POLICY(FLAG), \ + [IFLA_IPTUN_FWMARK] = BUILD_POLICY(U32), \ + } + +DEFINE_IPTUN_TYPES(iptun, U32); /* for ipip and ip6tnl */ +DEFINE_IPTUN_TYPES(sit, U16); /* for sit */ + +static const NLAPolicy rtnl_link_info_data_ipvlan_policies[] = { + [IFLA_IPVLAN_MODE] = BUILD_POLICY(U16), + [IFLA_IPVLAN_FLAGS] = BUILD_POLICY(U16), +}; + +static const NLAPolicy rtnl_link_info_data_macsec_policies[] = { + [IFLA_MACSEC_SCI] = BUILD_POLICY(U64), + [IFLA_MACSEC_PORT] = BUILD_POLICY(U16), + [IFLA_MACSEC_ICV_LEN] = BUILD_POLICY(U8), + [IFLA_MACSEC_CIPHER_SUITE] = BUILD_POLICY(U64), + [IFLA_MACSEC_WINDOW] = BUILD_POLICY(U32), + [IFLA_MACSEC_ENCODING_SA] = BUILD_POLICY(U8), + [IFLA_MACSEC_ENCRYPT] = BUILD_POLICY(U8), + [IFLA_MACSEC_PROTECT] = BUILD_POLICY(U8), + [IFLA_MACSEC_INC_SCI] = BUILD_POLICY(U8), + [IFLA_MACSEC_ES] = BUILD_POLICY(U8), + [IFLA_MACSEC_SCB] = BUILD_POLICY(U8), + [IFLA_MACSEC_REPLAY_PROTECT] = BUILD_POLICY(U8), + [IFLA_MACSEC_VALIDATION] = BUILD_POLICY(U8), + [IFLA_MACSEC_OFFLOAD] = BUILD_POLICY(U8), +}; + +static const NLAPolicy rtnl_macvlan_macaddr_policies[] = { + [IFLA_MACVLAN_MACADDR] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), +}; + +DEFINE_POLICY_SET(rtnl_macvlan_macaddr); + +static const NLAPolicy rtnl_link_info_data_macvlan_policies[] = { + [IFLA_MACVLAN_MODE] = BUILD_POLICY(U32), + [IFLA_MACVLAN_FLAGS] = BUILD_POLICY(U16), + [IFLA_MACVLAN_MACADDR_MODE] = BUILD_POLICY(U32), + [IFLA_MACVLAN_MACADDR_DATA] = BUILD_POLICY_NESTED(rtnl_macvlan_macaddr), + [IFLA_MACVLAN_MACADDR_COUNT] = BUILD_POLICY(U32), + [IFLA_MACVLAN_BC_QUEUE_LEN] = BUILD_POLICY(U32), + [IFLA_MACVLAN_BC_QUEUE_LEN_USED] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_link_info_data_tun_policies[] = { + [IFLA_TUN_OWNER] = BUILD_POLICY(U32), + [IFLA_TUN_GROUP] = BUILD_POLICY(U32), + [IFLA_TUN_TYPE] = BUILD_POLICY(U8), + [IFLA_TUN_PI] = BUILD_POLICY(U8), + [IFLA_TUN_VNET_HDR] = BUILD_POLICY(U8), + [IFLA_TUN_PERSIST] = BUILD_POLICY(U8), + [IFLA_TUN_MULTI_QUEUE] = BUILD_POLICY(U8), + [IFLA_TUN_NUM_QUEUES] = BUILD_POLICY(U32), + [IFLA_TUN_NUM_DISABLED_QUEUES] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_link_info_data_veth_policies[] = { + [VETH_INFO_PEER] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), +}; + +static const NLAPolicy rtnl_vlan_qos_map_policies[] = { + [IFLA_VLAN_QOS_MAPPING] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vlan_qos_mapping)), +}; + +DEFINE_POLICY_SET(rtnl_vlan_qos_map); + +static const NLAPolicy rtnl_link_info_data_vlan_policies[] = { + [IFLA_VLAN_ID] = BUILD_POLICY(U16), + [IFLA_VLAN_FLAGS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vlan_flags)), + [IFLA_VLAN_EGRESS_QOS] = BUILD_POLICY_NESTED(rtnl_vlan_qos_map), + [IFLA_VLAN_INGRESS_QOS] = BUILD_POLICY_NESTED(rtnl_vlan_qos_map), + [IFLA_VLAN_PROTOCOL] = BUILD_POLICY(U16), +}; + +static const NLAPolicy rtnl_link_info_data_vrf_policies[] = { + [IFLA_VRF_TABLE] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_link_info_data_vti_policies[] = { + [IFLA_VTI_LINK] = BUILD_POLICY(U32), + [IFLA_VTI_IKEY] = BUILD_POLICY(U32), + [IFLA_VTI_OKEY] = BUILD_POLICY(U32), + [IFLA_VTI_LOCAL] = BUILD_POLICY(IN_ADDR), + [IFLA_VTI_REMOTE] = BUILD_POLICY(IN_ADDR), + [IFLA_VTI_FWMARK] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_link_info_data_vxcan_policies[] = { + [VXCAN_INFO_PEER] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), +}; + +static const NLAPolicy rtnl_link_info_data_vxlan_policies[] = { + [IFLA_VXLAN_ID] = BUILD_POLICY(U32), + [IFLA_VXLAN_GROUP] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in_addr)), + [IFLA_VXLAN_LINK] = BUILD_POLICY(U32), + [IFLA_VXLAN_LOCAL] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in_addr)), + [IFLA_VXLAN_TTL] = BUILD_POLICY(U8), + [IFLA_VXLAN_TOS] = BUILD_POLICY(U8), + [IFLA_VXLAN_LEARNING] = BUILD_POLICY(U8), + [IFLA_VXLAN_AGEING] = BUILD_POLICY(U32), + [IFLA_VXLAN_LIMIT] = BUILD_POLICY(U32), + [IFLA_VXLAN_PORT_RANGE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vxlan_port_range)), + [IFLA_VXLAN_PROXY] = BUILD_POLICY(U8), + [IFLA_VXLAN_RSC] = BUILD_POLICY(U8), + [IFLA_VXLAN_L2MISS] = BUILD_POLICY(U8), + [IFLA_VXLAN_L3MISS] = BUILD_POLICY(U8), + [IFLA_VXLAN_PORT] = BUILD_POLICY(U16), + [IFLA_VXLAN_GROUP6] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [IFLA_VXLAN_LOCAL6] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [IFLA_VXLAN_UDP_CSUM] = BUILD_POLICY(U8), + [IFLA_VXLAN_UDP_ZERO_CSUM6_TX] = BUILD_POLICY(U8), + [IFLA_VXLAN_UDP_ZERO_CSUM6_RX] = BUILD_POLICY(U8), + [IFLA_VXLAN_REMCSUM_TX] = BUILD_POLICY(U8), + [IFLA_VXLAN_REMCSUM_RX] = BUILD_POLICY(U8), + [IFLA_VXLAN_GBP] = BUILD_POLICY(FLAG), + [IFLA_VXLAN_REMCSUM_NOPARTIAL] = BUILD_POLICY(FLAG), + [IFLA_VXLAN_COLLECT_METADATA] = BUILD_POLICY(U8), + [IFLA_VXLAN_LABEL] = BUILD_POLICY(U32), + [IFLA_VXLAN_GPE] = BUILD_POLICY(FLAG), + [IFLA_VXLAN_TTL_INHERIT] = BUILD_POLICY(FLAG), + [IFLA_VXLAN_DF] = BUILD_POLICY(U8), +}; + +static const NLAPolicy rtnl_link_info_data_xfrm_policies[] = { + [IFLA_XFRM_LINK] = BUILD_POLICY(U32), + [IFLA_XFRM_IF_ID] = BUILD_POLICY(U32) +}; + +static const NLAPolicySetUnionElement rtnl_link_info_data_policy_set_union_elements[] = { + BUILD_UNION_ELEMENT_BY_STRING("bareudp", rtnl_link_info_data_bareudp), + BUILD_UNION_ELEMENT_BY_STRING("batadv", rtnl_link_info_data_batadv), + BUILD_UNION_ELEMENT_BY_STRING("bond", rtnl_link_info_data_bond), + BUILD_UNION_ELEMENT_BY_STRING("bridge", rtnl_link_info_data_bridge), +/* + BUILD_UNION_ELEMENT_BY_STRING("caif", rtnl_link_info_data_caif), +*/ + BUILD_UNION_ELEMENT_BY_STRING("can", rtnl_link_info_data_can), + BUILD_UNION_ELEMENT_BY_STRING("erspan", rtnl_link_info_data_gre), + BUILD_UNION_ELEMENT_BY_STRING("geneve", rtnl_link_info_data_geneve), + BUILD_UNION_ELEMENT_BY_STRING("gre", rtnl_link_info_data_gre), + BUILD_UNION_ELEMENT_BY_STRING("gretap", rtnl_link_info_data_gre), +/* + BUILD_UNION_ELEMENT_BY_STRING("gtp", rtnl_link_info_data_gtp), + BUILD_UNION_ELEMENT_BY_STRING("hsr", rtnl_link_info_data_hsr), +*/ + BUILD_UNION_ELEMENT_BY_STRING("ip6erspan", rtnl_link_info_data_gre), + BUILD_UNION_ELEMENT_BY_STRING("ip6gre", rtnl_link_info_data_gre), + BUILD_UNION_ELEMENT_BY_STRING("ip6gretap", rtnl_link_info_data_gre), + BUILD_UNION_ELEMENT_BY_STRING("ip6tnl", rtnl_link_info_data_iptun), + BUILD_UNION_ELEMENT_BY_STRING("ipoib", rtnl_link_info_data_ipoib), + BUILD_UNION_ELEMENT_BY_STRING("ipip", rtnl_link_info_data_iptun), + BUILD_UNION_ELEMENT_BY_STRING("ipvlan", rtnl_link_info_data_ipvlan), + BUILD_UNION_ELEMENT_BY_STRING("ipvtap", rtnl_link_info_data_ipvlan), + BUILD_UNION_ELEMENT_BY_STRING("macsec", rtnl_link_info_data_macsec), + BUILD_UNION_ELEMENT_BY_STRING("macvlan", rtnl_link_info_data_macvlan), + BUILD_UNION_ELEMENT_BY_STRING("macvtap", rtnl_link_info_data_macvlan), +/* + BUILD_UNION_ELEMENT_BY_STRING("ppp", rtnl_link_info_data_ppp), + BUILD_UNION_ELEMENT_BY_STRING("rmnet", rtnl_link_info_data_rmnet), +*/ + BUILD_UNION_ELEMENT_BY_STRING("sit", rtnl_link_info_data_sit), + BUILD_UNION_ELEMENT_BY_STRING("tun", rtnl_link_info_data_tun), + BUILD_UNION_ELEMENT_BY_STRING("veth", rtnl_link_info_data_veth), + BUILD_UNION_ELEMENT_BY_STRING("vlan", rtnl_link_info_data_vlan), + BUILD_UNION_ELEMENT_BY_STRING("vrf", rtnl_link_info_data_vrf), + BUILD_UNION_ELEMENT_BY_STRING("vti", rtnl_link_info_data_vti), + BUILD_UNION_ELEMENT_BY_STRING("vti6", rtnl_link_info_data_vti), + BUILD_UNION_ELEMENT_BY_STRING("vxcan", rtnl_link_info_data_vxcan), + BUILD_UNION_ELEMENT_BY_STRING("vxlan", rtnl_link_info_data_vxlan), +/* + BUILD_UNION_ELEMENT_BY_STRING("wwan", rtnl_link_info_data_wwan), +*/ + BUILD_UNION_ELEMENT_BY_STRING("xfrm", rtnl_link_info_data_xfrm), +}; + +DEFINE_POLICY_SET_UNION(rtnl_link_info_data, IFLA_INFO_KIND); + +static const struct NLAPolicy rtnl_bridge_port_policies[] = { + [IFLA_BRPORT_STATE] = BUILD_POLICY(U8), + [IFLA_BRPORT_COST] = BUILD_POLICY(U32), + [IFLA_BRPORT_PRIORITY] = BUILD_POLICY(U16), + [IFLA_BRPORT_MODE] = BUILD_POLICY(U8), + [IFLA_BRPORT_GUARD] = BUILD_POLICY(U8), + [IFLA_BRPORT_PROTECT] = BUILD_POLICY(U8), + [IFLA_BRPORT_FAST_LEAVE] = BUILD_POLICY(U8), + [IFLA_BRPORT_LEARNING] = BUILD_POLICY(U8), + [IFLA_BRPORT_UNICAST_FLOOD] = BUILD_POLICY(U8), + [IFLA_BRPORT_PROXYARP] = BUILD_POLICY(U8), + [IFLA_BRPORT_LEARNING_SYNC] = BUILD_POLICY(U8), + [IFLA_BRPORT_PROXYARP_WIFI] = BUILD_POLICY(U8), + [IFLA_BRPORT_ROOT_ID] = BUILD_POLICY(U8), + [IFLA_BRPORT_BRIDGE_ID] = BUILD_POLICY(U8), + [IFLA_BRPORT_DESIGNATED_PORT] = BUILD_POLICY(U16), + [IFLA_BRPORT_DESIGNATED_COST] = BUILD_POLICY(U16), + [IFLA_BRPORT_ID] = BUILD_POLICY(U16), + [IFLA_BRPORT_NO] = BUILD_POLICY(U16), + [IFLA_BRPORT_TOPOLOGY_CHANGE_ACK] = BUILD_POLICY(U8), + [IFLA_BRPORT_CONFIG_PENDING] = BUILD_POLICY(U8), + [IFLA_BRPORT_MESSAGE_AGE_TIMER] = BUILD_POLICY(U64), + [IFLA_BRPORT_FORWARD_DELAY_TIMER] = BUILD_POLICY(U64), + [IFLA_BRPORT_HOLD_TIMER] = BUILD_POLICY(U64), + [IFLA_BRPORT_FLUSH] = BUILD_POLICY(U8), + [IFLA_BRPORT_MULTICAST_ROUTER] = BUILD_POLICY(U8), + [IFLA_BRPORT_PAD] = BUILD_POLICY(U8), + [IFLA_BRPORT_MCAST_FLOOD] = BUILD_POLICY(U8), + [IFLA_BRPORT_MCAST_TO_UCAST] = BUILD_POLICY(U8), + [IFLA_BRPORT_VLAN_TUNNEL] = BUILD_POLICY(U8), + [IFLA_BRPORT_BCAST_FLOOD] = BUILD_POLICY(U8), + [IFLA_BRPORT_GROUP_FWD_MASK] = BUILD_POLICY(U16), + [IFLA_BRPORT_NEIGH_SUPPRESS] = BUILD_POLICY(U8), + [IFLA_BRPORT_ISOLATED] = BUILD_POLICY(U8), + [IFLA_BRPORT_BACKUP_PORT] = BUILD_POLICY(U32), + [IFLA_BRPORT_MRP_RING_OPEN] = BUILD_POLICY(U8), + [IFLA_BRPORT_MRP_IN_OPEN] = BUILD_POLICY(U8), + [IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT] = BUILD_POLICY(U32), + [IFLA_BRPORT_MCAST_EHT_HOSTS_CNT] = BUILD_POLICY(U32), +}; + +static const NLAPolicySetUnionElement rtnl_link_info_slave_data_policy_set_union_elements[] = { + BUILD_UNION_ELEMENT_BY_STRING("bridge", rtnl_bridge_port), +}; + +DEFINE_POLICY_SET_UNION(rtnl_link_info_slave_data, IFLA_INFO_SLAVE_KIND); + +static const NLAPolicy rtnl_link_info_policies[] = { + [IFLA_INFO_KIND] = BUILD_POLICY(STRING), + [IFLA_INFO_DATA] = BUILD_POLICY_NESTED_UNION_BY_STRING(rtnl_link_info_data), + /* TODO: Currently IFLA_INFO_XSTATS is used only when IFLA_INFO_KIND is "can". In the future, + * when multiple kinds of netdevs use this attribute, convert its type to NETLINK_TYPE_UNION. */ + [IFLA_INFO_XSTATS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct can_device_stats)), + [IFLA_INFO_SLAVE_KIND] = BUILD_POLICY(STRING), + [IFLA_INFO_SLAVE_DATA] = BUILD_POLICY_NESTED_UNION_BY_STRING(rtnl_link_info_slave_data), +}; + +DEFINE_POLICY_SET(rtnl_link_info); + +static const struct NLAPolicy rtnl_inet_policies[] = { + [IFLA_INET_CONF] = BUILD_POLICY(BINARY), /* size = IPV4_DEVCONF_MAX * 4 */ +}; + +DEFINE_POLICY_SET(rtnl_inet); + +static const struct NLAPolicy rtnl_inet6_policies[] = { + [IFLA_INET6_FLAGS] = BUILD_POLICY(U32), + [IFLA_INET6_CONF] = BUILD_POLICY(BINARY), /* size = DEVCONF_MAX * sizeof(s32) */ + [IFLA_INET6_STATS] = BUILD_POLICY(BINARY), /* size = IPSTATS_MIB_MAX * sizeof(u64) */ + [IFLA_INET6_MCAST] = {}, /* unused. */ + [IFLA_INET6_CACHEINFO] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_cacheinfo)), + [IFLA_INET6_ICMP6STATS] = BUILD_POLICY(BINARY), /* size = ICMP6_MIB_MAX * sizeof(u64) */ + [IFLA_INET6_TOKEN] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [IFLA_INET6_ADDR_GEN_MODE] = BUILD_POLICY(U8), +}; + +DEFINE_POLICY_SET(rtnl_inet6); + +static const NLAPolicySetUnionElement rtnl_prot_info_policy_set_union_elements[] = { + BUILD_UNION_ELEMENT_BY_FAMILY(AF_BRIDGE, rtnl_bridge_port), + BUILD_UNION_ELEMENT_BY_FAMILY(AF_INET6, rtnl_inet6), +}; + +DEFINE_POLICY_SET_UNION(rtnl_prot_info, 0); + +static const NLAPolicy rtnl_af_spec_unspec_policies[] = { + [AF_INET] = BUILD_POLICY_NESTED(rtnl_inet), + [AF_INET6] = BUILD_POLICY_NESTED(rtnl_inet6), +}; + +static const NLAPolicy rtnl_bridge_vlan_tunnel_info_policies[] = { + [IFLA_BRIDGE_VLAN_TUNNEL_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_VLAN_TUNNEL_VID] = BUILD_POLICY(U16), + [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = BUILD_POLICY(U16), +}; + +DEFINE_POLICY_SET(rtnl_bridge_vlan_tunnel_info); + +static const NLAPolicy rtnl_bridge_mrp_instance_policies[] = { + [IFLA_BRIDGE_MRP_INSTANCE_RING_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INSTANCE_P_IFINDEX] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INSTANCE_S_IFINDEX] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INSTANCE_PRIO] = BUILD_POLICY(U16), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_instance); + +static const NLAPolicy rtnl_bridge_mrp_port_state_policies[] = { + [IFLA_BRIDGE_MRP_PORT_STATE_STATE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_port_state); + +static const NLAPolicy rtnl_bridge_mrp_port_role_policies[] = { + [IFLA_BRIDGE_MRP_PORT_ROLE_ROLE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_port_role); + +static const NLAPolicy rtnl_bridge_mrp_ring_state_policies[] = { + [IFLA_BRIDGE_MRP_RING_STATE_RING_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_RING_STATE_STATE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_ring_state); + +static const NLAPolicy rtnl_bridge_mrp_ring_role_policies[] = { + [IFLA_BRIDGE_MRP_RING_ROLE_RING_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_RING_ROLE_ROLE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_ring_role); + +static const NLAPolicy rtnl_bridge_mrp_start_test_policies[] = { + [IFLA_BRIDGE_MRP_START_TEST_RING_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_START_TEST_INTERVAL] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_START_TEST_MAX_MISS] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_START_TEST_PERIOD] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_START_TEST_MONITOR] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_start_test); + +static const NLAPolicy rtnl_bridge_mrp_info_policies[] = { + [IFLA_BRIDGE_MRP_INFO_RING_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_P_IFINDEX] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_S_IFINDEX] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_PRIO] = BUILD_POLICY(U16), + [IFLA_BRIDGE_MRP_INFO_RING_STATE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_RING_ROLE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_TEST_INTERVAL] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_TEST_MAX_MISS] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_TEST_MONITOR] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_I_IFINDEX] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_IN_STATE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_IN_ROLE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_IN_TEST_INTERVAL] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_INFO_IN_TEST_MAX_MISS] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_info); + +static const NLAPolicy rtnl_bridge_mrp_in_role_policies[] = { + [IFLA_BRIDGE_MRP_IN_ROLE_RING_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_IN_ROLE_IN_ID] = BUILD_POLICY(U16), + [IFLA_BRIDGE_MRP_IN_ROLE_ROLE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_IN_ROLE_I_IFINDEX] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_in_role); + +static const NLAPolicy rtnl_bridge_mrp_in_state_policies[] = { + [IFLA_BRIDGE_MRP_IN_STATE_IN_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_IN_STATE_STATE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_in_state); + +static const NLAPolicy rtnl_bridge_mrp_start_in_test_policies[] = { + [IFLA_BRIDGE_MRP_START_IN_TEST_IN_ID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_START_IN_TEST_INTERVAL] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_START_IN_TEST_MAX_MISS] = BUILD_POLICY(U32), + [IFLA_BRIDGE_MRP_START_IN_TEST_PERIOD] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp_start_in_test); + +static const NLAPolicy rtnl_bridge_mrp_policies[] = { + [IFLA_BRIDGE_MRP_INSTANCE] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_instance), + [IFLA_BRIDGE_MRP_PORT_STATE] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_port_state), + [IFLA_BRIDGE_MRP_PORT_ROLE] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_port_role), + [IFLA_BRIDGE_MRP_RING_STATE] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_ring_state), + [IFLA_BRIDGE_MRP_RING_ROLE] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_ring_role), + [IFLA_BRIDGE_MRP_START_TEST] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_start_test), + [IFLA_BRIDGE_MRP_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_info), + [IFLA_BRIDGE_MRP_IN_ROLE] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_in_role), + [IFLA_BRIDGE_MRP_IN_STATE] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_in_state), + [IFLA_BRIDGE_MRP_START_IN_TEST] = BUILD_POLICY_NESTED(rtnl_bridge_mrp_start_in_test), +}; + +DEFINE_POLICY_SET(rtnl_bridge_mrp); + +static const NLAPolicy rtnl_bridge_cfm_mep_create_policies[] = { + [IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_mep_create); + +static const NLAPolicy rtnl_bridge_cfm_mep_delete_policies[] = { + [IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_mep_delete); + +static const NLAPolicy rtnl_bridge_cfm_mep_config_policies[] = { + [IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_mep_config); + +static const NLAPolicy rtnl_bridge_cfm_cc_config_policies[] = { + [IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID] = BUILD_POLICY_WITH_SIZE(BINARY, CFM_MAID_LENGTH), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_cc_config); + +static const NLAPolicy rtnl_bridge_cfm_cc_peer_mep_policies[] = { + [IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_PEER_MEPID] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_cc_peer_mep); + +static const NLAPolicy rtnl_bridge_cfm_cc_rdi_policies[] = { + [IFLA_BRIDGE_CFM_CC_RDI_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_RDI_RDI] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_cc_rdi); + +static const NLAPolicy rtnl_bridge_cfm_cc_ccm_tx_policies[] = { + [IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), + [IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE] = BUILD_POLICY(U8), + [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE] = BUILD_POLICY(U8), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_cc_ccm_tx); + +static const NLAPolicy rtnl_bridge_cfm_mep_status_policies[] = { + [IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_mep_status); + +static const NLAPolicy rtnl_bridge_cfm_cc_peer_status_policies[] = { + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE] = BUILD_POLICY(U8), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE] = BUILD_POLICY(U8), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN] = BUILD_POLICY(U32), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm_cc_peer_status); + +static const NLAPolicy rtnl_bridge_cfm_policies[] = { + [IFLA_BRIDGE_CFM_MEP_CREATE] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_mep_create), + [IFLA_BRIDGE_CFM_MEP_DELETE] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_mep_delete), + [IFLA_BRIDGE_CFM_MEP_CONFIG] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_mep_config), + [IFLA_BRIDGE_CFM_CC_CONFIG] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_config), + [IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_peer_mep), + [IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_peer_mep), + [IFLA_BRIDGE_CFM_CC_RDI] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_rdi), + [IFLA_BRIDGE_CFM_CC_CCM_TX] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_ccm_tx), + [IFLA_BRIDGE_CFM_MEP_CREATE_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_mep_create), + [IFLA_BRIDGE_CFM_MEP_CONFIG_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_mep_config), + [IFLA_BRIDGE_CFM_CC_CONFIG_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_config), + [IFLA_BRIDGE_CFM_CC_RDI_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_rdi), + [IFLA_BRIDGE_CFM_CC_CCM_TX_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_ccm_tx), + [IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_peer_mep), + [IFLA_BRIDGE_CFM_MEP_STATUS_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_mep_status), + [IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_cfm_cc_peer_status), +}; + +DEFINE_POLICY_SET(rtnl_bridge_cfm); + +static const NLAPolicy rtnl_af_spec_bridge_policies[] = { + [IFLA_BRIDGE_FLAGS] = BUILD_POLICY(U16), + [IFLA_BRIDGE_MODE] = BUILD_POLICY(U16), + [IFLA_BRIDGE_VLAN_INFO] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct bridge_vlan_info)), + [IFLA_BRIDGE_VLAN_TUNNEL_INFO] = BUILD_POLICY_NESTED(rtnl_bridge_vlan_tunnel_info), + [IFLA_BRIDGE_MRP] = BUILD_POLICY_NESTED(rtnl_bridge_mrp), + [IFLA_BRIDGE_CFM] = BUILD_POLICY_NESTED(rtnl_bridge_cfm), +}; + +static const NLAPolicySetUnionElement rtnl_af_spec_policy_set_union_elements[] = { + BUILD_UNION_ELEMENT_BY_FAMILY(AF_UNSPEC, rtnl_af_spec_unspec), + BUILD_UNION_ELEMENT_BY_FAMILY(AF_BRIDGE, rtnl_af_spec_bridge), +}; + +DEFINE_POLICY_SET_UNION(rtnl_af_spec, 0); + +static const NLAPolicy rtnl_prop_list_policies[] = { + [IFLA_ALT_IFNAME] = BUILD_POLICY_WITH_SIZE(STRING, ALTIFNAMSIZ - 1), +}; + +DEFINE_POLICY_SET(rtnl_prop_list); + +static const NLAPolicy rtnl_vf_vlan_list_policies[] = { + [IFLA_VF_VLAN_INFO] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_vlan_info)), +}; + +DEFINE_POLICY_SET(rtnl_vf_vlan_list); + +static const NLAPolicy rtnl_vf_info_policies[] = { + [IFLA_VF_MAC] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_mac)), + [IFLA_VF_VLAN] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_vlan)), + [IFLA_VF_VLAN_LIST] = BUILD_POLICY_NESTED(rtnl_vf_vlan_list), + [IFLA_VF_TX_RATE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_tx_rate)), + [IFLA_VF_SPOOFCHK] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_spoofchk)), + [IFLA_VF_RATE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_rate)), + [IFLA_VF_LINK_STATE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_link_state)), + [IFLA_VF_RSS_QUERY_EN] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_rss_query_en)), + [IFLA_VF_TRUST] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_trust)), + [IFLA_VF_IB_NODE_GUID] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_guid)), + [IFLA_VF_IB_PORT_GUID] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_vf_guid)), +}; + +DEFINE_POLICY_SET(rtnl_vf_info); + +static const NLAPolicy rtnl_vfinfo_list_policies[] = { + [IFLA_VF_INFO] = BUILD_POLICY_NESTED(rtnl_vf_info), +}; + +DEFINE_POLICY_SET(rtnl_vfinfo_list); + +static const NLAPolicy rtnl_vf_port_policies[] = { + [IFLA_PORT_VF] = BUILD_POLICY(U32), + [IFLA_PORT_PROFILE] = BUILD_POLICY(STRING), + [IFLA_PORT_VSI_TYPE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct ifla_port_vsi)), + [IFLA_PORT_INSTANCE_UUID] = BUILD_POLICY_WITH_SIZE(BINARY, PORT_UUID_MAX), + [IFLA_PORT_HOST_UUID] = BUILD_POLICY_WITH_SIZE(BINARY, PORT_UUID_MAX), + [IFLA_PORT_REQUEST] = BUILD_POLICY(U8), + [IFLA_PORT_RESPONSE] = BUILD_POLICY(U16), +}; + +DEFINE_POLICY_SET(rtnl_vf_port); + +static const NLAPolicy rtnl_vf_ports_policies[] = { + [IFLA_VF_PORT] = BUILD_POLICY_NESTED(rtnl_vf_port), +}; + +DEFINE_POLICY_SET(rtnl_vf_ports); + +static const NLAPolicy rtnl_xdp_policies[] = { + [IFLA_XDP_FD] = BUILD_POLICY(S32), + [IFLA_XDP_ATTACHED] = BUILD_POLICY(U8), + [IFLA_XDP_FLAGS] = BUILD_POLICY(U32), + [IFLA_XDP_PROG_ID] = BUILD_POLICY(U32), + [IFLA_XDP_DRV_PROG_ID] = BUILD_POLICY(U32), + [IFLA_XDP_SKB_PROG_ID] = BUILD_POLICY(U32), + [IFLA_XDP_HW_PROG_ID] = BUILD_POLICY(U32), + [IFLA_XDP_EXPECTED_FD] = BUILD_POLICY(S32), +}; + +DEFINE_POLICY_SET(rtnl_xdp); + +static const NLAPolicy rtnl_proto_down_reason_policies[] = { + [IFLA_PROTO_DOWN_REASON_MASK] = BUILD_POLICY(U32), + [IFLA_PROTO_DOWN_REASON_VALUE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_proto_down_reason); + +static const NLAPolicy rtnl_link_policies[] = { + [IFLA_ADDRESS] = BUILD_POLICY(ETHER_ADDR), + [IFLA_BROADCAST] = BUILD_POLICY(ETHER_ADDR), + [IFLA_IFNAME] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ - 1), + [IFLA_MTU] = BUILD_POLICY(U32), + [IFLA_LINK] = BUILD_POLICY(U32), + [IFLA_QDISC] = BUILD_POLICY(STRING), + [IFLA_STATS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct rtnl_link_stats)), + [IFLA_COST] = { /* Not used. */ }, + [IFLA_PRIORITY] = { /* Not used. */ }, + [IFLA_MASTER] = BUILD_POLICY(U32), + [IFLA_WIRELESS] = { /* Used only by wext. */ }, + [IFLA_PROTINFO] = BUILD_POLICY_NESTED_UNION_BY_FAMILY(rtnl_prot_info), + [IFLA_TXQLEN] = BUILD_POLICY(U32), + [IFLA_MAP] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct rtnl_link_ifmap)), + [IFLA_WEIGHT] = BUILD_POLICY(U32), + [IFLA_OPERSTATE] = BUILD_POLICY(U8), + [IFLA_LINKMODE] = BUILD_POLICY(U8), + [IFLA_LINKINFO] = BUILD_POLICY_NESTED(rtnl_link_info), + [IFLA_NET_NS_PID] = BUILD_POLICY(U32), + [IFLA_IFALIAS] = BUILD_POLICY_WITH_SIZE(STRING, IFALIASZ - 1), + [IFLA_NUM_VF] = BUILD_POLICY(U32), + [IFLA_VFINFO_LIST] = BUILD_POLICY_NESTED(rtnl_vfinfo_list), + [IFLA_STATS64] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct rtnl_link_stats64)), + [IFLA_VF_PORTS] = BUILD_POLICY_NESTED(rtnl_vf_ports), + [IFLA_PORT_SELF] = BUILD_POLICY_NESTED(rtnl_vf_port), + [IFLA_AF_SPEC] = BUILD_POLICY_NESTED_UNION_BY_FAMILY(rtnl_af_spec), + [IFLA_GROUP] = BUILD_POLICY(U32), + [IFLA_NET_NS_FD] = BUILD_POLICY(U32), + [IFLA_EXT_MASK] = BUILD_POLICY(U32), + [IFLA_PROMISCUITY] = BUILD_POLICY(U32), + [IFLA_NUM_TX_QUEUES] = BUILD_POLICY(U32), + [IFLA_NUM_RX_QUEUES] = BUILD_POLICY(U32), + [IFLA_CARRIER] = BUILD_POLICY(U8), + [IFLA_PHYS_PORT_ID] = BUILD_POLICY_WITH_SIZE(BINARY, MAX_PHYS_ITEM_ID_LEN), + [IFLA_CARRIER_CHANGES] = BUILD_POLICY(U32), + [IFLA_PHYS_SWITCH_ID] = BUILD_POLICY_WITH_SIZE(BINARY, MAX_PHYS_ITEM_ID_LEN), + [IFLA_LINK_NETNSID] = BUILD_POLICY(S32), + [IFLA_PHYS_PORT_NAME] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ - 1), + [IFLA_PROTO_DOWN] = BUILD_POLICY(U8), + [IFLA_GSO_MAX_SEGS] = BUILD_POLICY(U32), + [IFLA_GSO_MAX_SIZE] = BUILD_POLICY(U32), + [IFLA_XDP] = BUILD_POLICY_NESTED(rtnl_xdp), + [IFLA_EVENT] = BUILD_POLICY(U32), + [IFLA_NEW_NETNSID] = BUILD_POLICY(S32), + [IFLA_TARGET_NETNSID] = BUILD_POLICY(S32), + [IFLA_CARRIER_UP_COUNT] = BUILD_POLICY(U32), + [IFLA_CARRIER_DOWN_COUNT] = BUILD_POLICY(U32), + [IFLA_NEW_IFINDEX] = BUILD_POLICY(S32), + [IFLA_MIN_MTU] = BUILD_POLICY(U32), + [IFLA_MAX_MTU] = BUILD_POLICY(U32), + [IFLA_PROP_LIST] = BUILD_POLICY_NESTED(rtnl_prop_list), + [IFLA_ALT_IFNAME] = BUILD_POLICY_WITH_SIZE(STRING, ALTIFNAMSIZ - 1), + [IFLA_PERM_ADDRESS] = BUILD_POLICY(ETHER_ADDR), + [IFLA_PROTO_DOWN_REASON] = BUILD_POLICY_NESTED(rtnl_proto_down_reason), + [IFLA_PARENT_DEV_NAME] = BUILD_POLICY(STRING), + [IFLA_PARENT_DEV_BUS_NAME] = BUILD_POLICY(STRING), +}; + +DEFINE_POLICY_SET(rtnl_link); + +/* IFA_FLAGS was defined in kernel 3.14, but we still support older + * kernels where IFA_MAX is lower. */ +static const NLAPolicy rtnl_address_policies[] = { + [IFA_ADDRESS] = BUILD_POLICY(IN_ADDR), + [IFA_LOCAL] = BUILD_POLICY(IN_ADDR), + [IFA_LABEL] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ - 1), + [IFA_BROADCAST] = BUILD_POLICY(IN_ADDR), + [IFA_ANYCAST] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [IFA_CACHEINFO] = BUILD_POLICY_WITH_SIZE(CACHE_INFO, sizeof(struct ifa_cacheinfo)), + [IFA_MULTICAST] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [IFA_FLAGS] = BUILD_POLICY(U32), + [IFA_RT_PRIORITY] = BUILD_POLICY(U32), + [IFA_TARGET_NETNSID] = BUILD_POLICY(S32), +}; + +DEFINE_POLICY_SET(rtnl_address); + +/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */ + +static const NLAPolicy rtnl_route_metrics_policies[] = { + [RTAX_MTU] = BUILD_POLICY(U32), + [RTAX_WINDOW] = BUILD_POLICY(U32), + [RTAX_RTT] = BUILD_POLICY(U32), + [RTAX_RTTVAR] = BUILD_POLICY(U32), + [RTAX_SSTHRESH] = BUILD_POLICY(U32), + [RTAX_CWND] = BUILD_POLICY(U32), + [RTAX_ADVMSS] = BUILD_POLICY(U32), + [RTAX_REORDERING] = BUILD_POLICY(U32), + [RTAX_HOPLIMIT] = BUILD_POLICY(U32), + [RTAX_INITCWND] = BUILD_POLICY(U32), + [RTAX_FEATURES] = BUILD_POLICY(U32), + [RTAX_RTO_MIN] = BUILD_POLICY(U32), + [RTAX_INITRWND] = BUILD_POLICY(U32), + [RTAX_QUICKACK] = BUILD_POLICY(U32), + [RTAX_CC_ALGO] = BUILD_POLICY(STRING), + [RTAX_FASTOPEN_NO_COOKIE] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_route_metrics); + +static const NLAPolicy rtnl_route_policies[] = { + [RTA_DST] = BUILD_POLICY(IN_ADDR), + [RTA_SRC] = BUILD_POLICY(IN_ADDR), + [RTA_IIF] = BUILD_POLICY(U32), + [RTA_OIF] = BUILD_POLICY(U32), + [RTA_GATEWAY] = BUILD_POLICY(IN_ADDR), + [RTA_PRIORITY] = BUILD_POLICY(U32), + [RTA_PREFSRC] = BUILD_POLICY(IN_ADDR), + [RTA_METRICS] = BUILD_POLICY_NESTED(rtnl_route_metrics), + [RTA_MULTIPATH] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct rtnexthop)), + [RTA_FLOW] = BUILD_POLICY(U32), + [RTA_CACHEINFO] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct rta_cacheinfo)), + [RTA_TABLE] = BUILD_POLICY(U32), + [RTA_MARK] = BUILD_POLICY(U32), + [RTA_MFC_STATS] = BUILD_POLICY(U64), + [RTA_VIA] = BUILD_POLICY(BINARY), /* See struct rtvia */ + [RTA_NEWDST] = BUILD_POLICY(U32), + [RTA_PREF] = BUILD_POLICY(U8), + [RTA_ENCAP_TYPE] = BUILD_POLICY(U16), + [RTA_ENCAP] = { .type = NETLINK_TYPE_NESTED }, /* Multiple type systems i.e. LWTUNNEL_ENCAP_MPLS/LWTUNNEL_ENCAP_IP/LWTUNNEL_ENCAP_ILA etc... */ + [RTA_EXPIRES] = BUILD_POLICY(U32), + [RTA_UID] = BUILD_POLICY(U32), + [RTA_TTL_PROPAGATE] = BUILD_POLICY(U8), + [RTA_IP_PROTO] = BUILD_POLICY(U8), + [RTA_SPORT] = BUILD_POLICY(U16), + [RTA_DPORT] = BUILD_POLICY(U16), + [RTA_NH_ID] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_route); + +static const NLAPolicy rtnl_neigh_policies[] = { + [NDA_DST] = BUILD_POLICY(IN_ADDR), + [NDA_LLADDR] = BUILD_POLICY(ETHER_ADDR), + [NDA_CACHEINFO] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct nda_cacheinfo)), + [NDA_PROBES] = BUILD_POLICY(U32), + [NDA_VLAN] = BUILD_POLICY(U16), + [NDA_PORT] = BUILD_POLICY(U16), + [NDA_VNI] = BUILD_POLICY(U32), + [NDA_IFINDEX] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_neigh); + +static const NLAPolicy rtnl_addrlabel_policies[] = { + [IFAL_ADDRESS] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [IFAL_LABEL] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_addrlabel); + +static const NLAPolicy rtnl_routing_policy_rule_policies[] = { + [FRA_DST] = BUILD_POLICY(IN_ADDR), + [FRA_SRC] = BUILD_POLICY(IN_ADDR), + [FRA_IIFNAME] = BUILD_POLICY(STRING), + [FRA_GOTO] = BUILD_POLICY(U32), + [FRA_PRIORITY] = BUILD_POLICY(U32), + [FRA_FWMARK] = BUILD_POLICY(U32), + [FRA_FLOW] = BUILD_POLICY(U32), + [FRA_TUN_ID] = BUILD_POLICY(U64), + [FRA_SUPPRESS_IFGROUP] = BUILD_POLICY(U32), + [FRA_SUPPRESS_PREFIXLEN] = BUILD_POLICY(U32), + [FRA_TABLE] = BUILD_POLICY(U32), + [FRA_FWMASK] = BUILD_POLICY(U32), + [FRA_OIFNAME] = BUILD_POLICY(STRING), + [FRA_PAD] = BUILD_POLICY(U32), + [FRA_L3MDEV] = BUILD_POLICY(U8), + [FRA_UID_RANGE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct fib_rule_uid_range)), + [FRA_PROTOCOL] = BUILD_POLICY(U8), + [FRA_IP_PROTO] = BUILD_POLICY(U8), + [FRA_SPORT_RANGE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct fib_rule_port_range)), + [FRA_DPORT_RANGE] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct fib_rule_port_range)), +}; + +DEFINE_POLICY_SET(rtnl_routing_policy_rule); + +static const NLAPolicy rtnl_nexthop_policies[] = { + [NHA_ID] = BUILD_POLICY(U32), + [NHA_GROUP] = { /* array of struct nexthop_grp */ }, + [NHA_GROUP_TYPE] = BUILD_POLICY(U16), + [NHA_BLACKHOLE] = BUILD_POLICY(FLAG), + [NHA_OIF] = BUILD_POLICY(U32), + [NHA_GATEWAY] = BUILD_POLICY(IN_ADDR), + [NHA_ENCAP_TYPE] = BUILD_POLICY(U16), + [NHA_ENCAP] = { .type = NETLINK_TYPE_NESTED }, + [NHA_GROUPS] = BUILD_POLICY(FLAG), + [NHA_MASTER] = BUILD_POLICY(U32), + [NHA_FDB] = BUILD_POLICY(FLAG), +}; + +DEFINE_POLICY_SET(rtnl_nexthop); + +static const NLAPolicy rtnl_tca_option_data_cake_policies[] = { + [TCA_CAKE_BASE_RATE64] = BUILD_POLICY(U64), + [TCA_CAKE_DIFFSERV_MODE] = BUILD_POLICY(U32), + [TCA_CAKE_ATM] = BUILD_POLICY(U32), + [TCA_CAKE_FLOW_MODE] = BUILD_POLICY(U32), + [TCA_CAKE_OVERHEAD] = BUILD_POLICY(S32), + [TCA_CAKE_RTT] = BUILD_POLICY(U32), + [TCA_CAKE_TARGET] = BUILD_POLICY(U32), + [TCA_CAKE_AUTORATE] = BUILD_POLICY(U32), + [TCA_CAKE_MEMORY] = BUILD_POLICY(U32), + [TCA_CAKE_NAT] = BUILD_POLICY(U32), + [TCA_CAKE_RAW] = BUILD_POLICY(U32), + [TCA_CAKE_WASH] = BUILD_POLICY(U32), + [TCA_CAKE_MPU] = BUILD_POLICY(U32), + [TCA_CAKE_INGRESS] = BUILD_POLICY(U32), + [TCA_CAKE_ACK_FILTER] = BUILD_POLICY(U32), + [TCA_CAKE_SPLIT_GSO] = BUILD_POLICY(U32), + [TCA_CAKE_FWMARK] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_codel_policies[] = { + [TCA_CODEL_TARGET] = BUILD_POLICY(U32), + [TCA_CODEL_LIMIT] = BUILD_POLICY(U32), + [TCA_CODEL_INTERVAL] = BUILD_POLICY(U32), + [TCA_CODEL_ECN] = BUILD_POLICY(U32), + [TCA_CODEL_CE_THRESHOLD] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_drr_policies[] = { + [TCA_DRR_QUANTUM] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_ets_quanta_policies[] = { + [TCA_ETS_QUANTA_BAND] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_tca_option_data_ets_quanta); + +static const NLAPolicy rtnl_tca_option_data_ets_prio_policies[] = { + [TCA_ETS_PRIOMAP_BAND] = BUILD_POLICY(U8), +}; + +DEFINE_POLICY_SET(rtnl_tca_option_data_ets_prio); + +static const NLAPolicy rtnl_tca_option_data_ets_policies[] = { + [TCA_ETS_NBANDS] = BUILD_POLICY(U8), + [TCA_ETS_NSTRICT] = BUILD_POLICY(U8), + [TCA_ETS_QUANTA] = BUILD_POLICY_NESTED(rtnl_tca_option_data_ets_quanta), + [TCA_ETS_PRIOMAP] = BUILD_POLICY_NESTED(rtnl_tca_option_data_ets_prio), + [TCA_ETS_QUANTA_BAND] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_fq_policies[] = { + [TCA_FQ_PLIMIT] = BUILD_POLICY(U32), + [TCA_FQ_FLOW_PLIMIT] = BUILD_POLICY(U32), + [TCA_FQ_QUANTUM] = BUILD_POLICY(U32), + [TCA_FQ_INITIAL_QUANTUM] = BUILD_POLICY(U32), + [TCA_FQ_RATE_ENABLE] = BUILD_POLICY(U32), + [TCA_FQ_FLOW_DEFAULT_RATE] = BUILD_POLICY(U32), + [TCA_FQ_FLOW_MAX_RATE] = BUILD_POLICY(U32), + [TCA_FQ_BUCKETS_LOG] = BUILD_POLICY(U32), + [TCA_FQ_FLOW_REFILL_DELAY] = BUILD_POLICY(U32), + [TCA_FQ_LOW_RATE_THRESHOLD] = BUILD_POLICY(U32), + [TCA_FQ_CE_THRESHOLD] = BUILD_POLICY(U32), + [TCA_FQ_ORPHAN_MASK] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_fq_codel_policies[] = { + [TCA_FQ_CODEL_TARGET] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_LIMIT] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_INTERVAL] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_ECN] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_FLOWS] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_QUANTUM] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_CE_THRESHOLD] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_DROP_BATCH_SIZE] = BUILD_POLICY(U32), + [TCA_FQ_CODEL_MEMORY_LIMIT] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_fq_pie_policies[] = { + [TCA_FQ_PIE_LIMIT] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_gred_policies[] = { + [TCA_GRED_DPS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct tc_gred_sopt)), +}; + +static const NLAPolicy rtnl_tca_option_data_hhf_policies[] = { + [TCA_HHF_BACKLOG_LIMIT] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_htb_policies[] = { + [TCA_HTB_PARMS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct tc_htb_opt)), + [TCA_HTB_INIT] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct tc_htb_glob)), + [TCA_HTB_CTAB] = BUILD_POLICY_WITH_SIZE(BINARY, TC_RTAB_SIZE), + [TCA_HTB_RTAB] = BUILD_POLICY_WITH_SIZE(BINARY, TC_RTAB_SIZE), + [TCA_HTB_RATE64] = BUILD_POLICY(U64), + [TCA_HTB_CEIL64] = BUILD_POLICY(U64), +}; + +static const NLAPolicy rtnl_tca_option_data_pie_policies[] = { + [TCA_PIE_LIMIT] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_qfq_policies[] = { + [TCA_QFQ_WEIGHT] = BUILD_POLICY(U32), + [TCA_QFQ_LMAX] = BUILD_POLICY(U32), +}; + +static const NLAPolicy rtnl_tca_option_data_sfb_policies[] = { + [TCA_SFB_PARMS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct tc_sfb_qopt)), +}; + +static const NLAPolicy rtnl_tca_option_data_tbf_policies[] = { + [TCA_TBF_PARMS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct tc_tbf_qopt)), + [TCA_TBF_RTAB] = BUILD_POLICY_WITH_SIZE(BINARY, TC_RTAB_SIZE), + [TCA_TBF_PTAB] = BUILD_POLICY_WITH_SIZE(BINARY, TC_RTAB_SIZE), + [TCA_TBF_RATE64] = BUILD_POLICY(U64), + [TCA_TBF_PRATE64] = BUILD_POLICY(U64), + [TCA_TBF_BURST] = BUILD_POLICY(U32), + [TCA_TBF_PBURST] = BUILD_POLICY(U32), +}; + +static const NLAPolicySetUnionElement rtnl_tca_option_data_policy_set_union_elements[] = { + BUILD_UNION_ELEMENT_BY_STRING("cake", rtnl_tca_option_data_cake), + BUILD_UNION_ELEMENT_BY_STRING("codel", rtnl_tca_option_data_codel), + BUILD_UNION_ELEMENT_BY_STRING("drr", rtnl_tca_option_data_drr), + BUILD_UNION_ELEMENT_BY_STRING("ets", rtnl_tca_option_data_ets), + BUILD_UNION_ELEMENT_BY_STRING("fq", rtnl_tca_option_data_fq), + BUILD_UNION_ELEMENT_BY_STRING("fq_codel", rtnl_tca_option_data_fq_codel), + BUILD_UNION_ELEMENT_BY_STRING("fq_pie", rtnl_tca_option_data_fq_pie), + BUILD_UNION_ELEMENT_BY_STRING("gred", rtnl_tca_option_data_gred), + BUILD_UNION_ELEMENT_BY_STRING("hhf", rtnl_tca_option_data_hhf), + BUILD_UNION_ELEMENT_BY_STRING("htb", rtnl_tca_option_data_htb), + BUILD_UNION_ELEMENT_BY_STRING("pie", rtnl_tca_option_data_pie), + BUILD_UNION_ELEMENT_BY_STRING("qfq", rtnl_tca_option_data_qfq), + BUILD_UNION_ELEMENT_BY_STRING("sfb", rtnl_tca_option_data_sfb), + BUILD_UNION_ELEMENT_BY_STRING("tbf", rtnl_tca_option_data_tbf), +}; + +DEFINE_POLICY_SET_UNION(rtnl_tca_option_data, TCA_KIND); + +static const NLAPolicy rtnl_tca_policies[] = { + [TCA_KIND] = BUILD_POLICY(STRING), + [TCA_OPTIONS] = BUILD_POLICY_NESTED_UNION_BY_STRING(rtnl_tca_option_data), + [TCA_INGRESS_BLOCK] = BUILD_POLICY(U32), + [TCA_EGRESS_BLOCK] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_tca); + +static const NLAPolicy rtnl_mdb_policies[] = { + [MDBA_SET_ENTRY] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct br_port_msg)), +}; + +DEFINE_POLICY_SET(rtnl_mdb); + +static const NLAPolicy rtnl_policies[] = { + [RTM_NEWLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), + [RTM_DELLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), + [RTM_GETLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), + [RTM_SETLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), + [RTM_NEWLINKPROP] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), + [RTM_DELLINKPROP] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), + [RTM_GETLINKPROP] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), + [RTM_NEWADDR] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_address, sizeof(struct ifaddrmsg)), + [RTM_DELADDR] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_address, sizeof(struct ifaddrmsg)), + [RTM_GETADDR] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_address, sizeof(struct ifaddrmsg)), + [RTM_NEWROUTE] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_route, sizeof(struct rtmsg)), + [RTM_DELROUTE] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_route, sizeof(struct rtmsg)), + [RTM_GETROUTE] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_route, sizeof(struct rtmsg)), + [RTM_NEWNEIGH] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_neigh, sizeof(struct ndmsg)), + [RTM_DELNEIGH] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_neigh, sizeof(struct ndmsg)), + [RTM_GETNEIGH] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_neigh, sizeof(struct ndmsg)), + [RTM_NEWADDRLABEL] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_addrlabel, sizeof(struct ifaddrlblmsg)), + [RTM_DELADDRLABEL] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_addrlabel, sizeof(struct ifaddrlblmsg)), + [RTM_GETADDRLABEL] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_addrlabel, sizeof(struct ifaddrlblmsg)), + [RTM_NEWRULE] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_routing_policy_rule, sizeof(struct fib_rule_hdr)), + [RTM_DELRULE] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_routing_policy_rule, sizeof(struct fib_rule_hdr)), + [RTM_GETRULE] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_routing_policy_rule, sizeof(struct fib_rule_hdr)), + [RTM_NEWNEXTHOP] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nexthop, sizeof(struct nhmsg)), + [RTM_DELNEXTHOP] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nexthop, sizeof(struct nhmsg)), + [RTM_GETNEXTHOP] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nexthop, sizeof(struct nhmsg)), + [RTM_NEWQDISC] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_tca, sizeof(struct tcmsg)), + [RTM_DELQDISC] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_tca, sizeof(struct tcmsg)), + [RTM_GETQDISC] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_tca, sizeof(struct tcmsg)), + [RTM_NEWTCLASS] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_tca, sizeof(struct tcmsg)), + [RTM_DELTCLASS] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_tca, sizeof(struct tcmsg)), + [RTM_GETTCLASS] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_tca, sizeof(struct tcmsg)), + [RTM_NEWMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), + [RTM_DELMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), + [RTM_GETMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), +}; + +DEFINE_POLICY_SET(rtnl); + +const NLAPolicy *rtnl_get_policy(uint16_t nlmsg_type) { + return policy_set_get_policy(&rtnl_policy_set, nlmsg_type); +} diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c new file mode 100644 index 0000000..21ef80c --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/netlink.h> + +#include "netlink-genl.h" +#include "netlink-internal.h" +#include "netlink-types-internal.h" + +static const NLAPolicy empty_policies[1] = { + /* fake array to avoid .types==NULL, which denotes invalid type-systems */ +}; + +DEFINE_POLICY_SET(empty); + +static const NLAPolicy error_policies[] = { + [NLMSGERR_ATTR_MSG] = BUILD_POLICY(STRING), + [NLMSGERR_ATTR_OFFS] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(error); + +static const NLAPolicy basic_policies[] = { + [NLMSG_DONE] = BUILD_POLICY_NESTED(empty), + [NLMSG_ERROR] = BUILD_POLICY_NESTED_WITH_SIZE(error, sizeof(struct nlmsgerr)), +}; + +DEFINE_POLICY_SET(basic); + +NLAType policy_get_type(const NLAPolicy *policy) { + return ASSERT_PTR(policy)->type; +} + +size_t policy_get_size(const NLAPolicy *policy) { + return ASSERT_PTR(policy)->size; +} + +const NLAPolicySet *policy_get_policy_set(const NLAPolicy *policy) { + assert(policy); + assert(policy->type == NETLINK_TYPE_NESTED); + + return ASSERT_PTR(policy->policy_set); +} + +const NLAPolicySetUnion *policy_get_policy_set_union(const NLAPolicy *policy) { + assert(policy); + assert(IN_SET(policy->type, NETLINK_TYPE_NESTED_UNION_BY_STRING, NETLINK_TYPE_NESTED_UNION_BY_FAMILY)); + + return ASSERT_PTR(policy->policy_set_union); +} + +int netlink_get_policy_set_and_header_size( + sd_netlink *nl, + uint16_t type, + const NLAPolicySet **ret_policy_set, + size_t *ret_header_size) { + + const NLAPolicy *policy; + + assert(nl); + + if (IN_SET(type, NLMSG_DONE, NLMSG_ERROR)) + policy = policy_set_get_policy(&basic_policy_set, type); + else + switch (nl->protocol) { + case NETLINK_ROUTE: + policy = rtnl_get_policy(type); + break; + case NETLINK_NETFILTER: + policy = nfnl_get_policy(type); + break; + case NETLINK_GENERIC: + return genl_get_policy_set_and_header_size(nl, type, ret_policy_set, ret_header_size); + default: + return -EOPNOTSUPP; + } + if (!policy) + return -EOPNOTSUPP; + + if (policy_get_type(policy) != NETLINK_TYPE_NESTED) + return -EOPNOTSUPP; + + if (ret_policy_set) + *ret_policy_set = policy_get_policy_set(policy); + if (ret_header_size) + *ret_header_size = policy_get_size(policy); + return 0; +} + +const NLAPolicy *policy_set_get_policy(const NLAPolicySet *policy_set, uint16_t attr_type) { + const NLAPolicy *policy; + + assert(policy_set); + assert(policy_set->policies); + + if (attr_type >= policy_set->count) + return NULL; + + policy = &policy_set->policies[attr_type]; + + if (policy->type == NETLINK_TYPE_UNSPEC) + return NULL; + + return policy; +} + +const NLAPolicySet *policy_set_get_policy_set(const NLAPolicySet *policy_set, uint16_t attr_type) { + const NLAPolicy *policy; + + policy = policy_set_get_policy(policy_set, attr_type); + if (!policy) + return NULL; + + return policy_get_policy_set(policy); +} + +const NLAPolicySetUnion *policy_set_get_policy_set_union(const NLAPolicySet *policy_set, uint16_t attr_type) { + const NLAPolicy *policy; + + policy = policy_set_get_policy(policy_set, attr_type); + if (!policy) + return NULL; + + return policy_get_policy_set_union(policy); +} + +uint16_t policy_set_union_get_match_attribute(const NLAPolicySetUnion *policy_set_union) { + assert(policy_set_union->match_attribute != 0); + + return policy_set_union->match_attribute; +} + +const NLAPolicySet *policy_set_union_get_policy_set_by_string(const NLAPolicySetUnion *policy_set_union, const char *string) { + assert(policy_set_union); + assert(policy_set_union->elements); + assert(string); + + for (size_t i = 0; i < policy_set_union->count; i++) + if (streq(policy_set_union->elements[i].string, string)) + return &policy_set_union->elements[i].policy_set; + + return NULL; +} + +const NLAPolicySet *policy_set_union_get_policy_set_by_family(const NLAPolicySetUnion *policy_set_union, int family) { + assert(policy_set_union); + assert(policy_set_union->elements); + + for (size_t i = 0; i < policy_set_union->count; i++) + if (policy_set_union->elements[i].family == family) + return &policy_set_union->elements[i].policy_set; + + return NULL; +} diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h new file mode 100644 index 0000000..e034a98 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <errno.h> + +#include "sd-netlink.h" + +typedef enum NLAType { + NETLINK_TYPE_UNSPEC, /* NLA_UNSPEC */ + NETLINK_TYPE_BINARY, /* NLA_BINARY */ + NETLINK_TYPE_FLAG, /* NLA_FLAG */ + NETLINK_TYPE_U8, /* NLA_U8 */ + NETLINK_TYPE_U16, /* NLA_U16 */ + NETLINK_TYPE_U32, /* NLA_U32 */ + NETLINK_TYPE_U64, /* NLA_U64 */ + NETLINK_TYPE_S8, /* NLA_S8 */ + NETLINK_TYPE_S16, /* NLA_S16 */ + NETLINK_TYPE_S32, /* NLA_S32 */ + NETLINK_TYPE_S64, /* NLA_S64 */ + NETLINK_TYPE_STRING, /* NLA_STRING */ + NETLINK_TYPE_BITFIELD32, /* NLA_BITFIELD32 */ + NETLINK_TYPE_REJECT, /* NLA_REJECT */ + NETLINK_TYPE_IN_ADDR, + NETLINK_TYPE_ETHER_ADDR, + NETLINK_TYPE_CACHE_INFO, + NETLINK_TYPE_SOCKADDR, + NETLINK_TYPE_NESTED, /* NLA_NESTED */ + NETLINK_TYPE_NESTED_UNION_BY_STRING, + NETLINK_TYPE_NESTED_UNION_BY_FAMILY, + _NETLINK_TYPE_MAX, + _NETLINK_TYPE_INVALID = -EINVAL, +} NLAType; + +typedef struct NLAPolicy NLAPolicy; +typedef struct NLAPolicySet NLAPolicySet; +typedef struct NLAPolicySetUnion NLAPolicySetUnion; + +const NLAPolicy *rtnl_get_policy(uint16_t nlmsg_type); +const NLAPolicy *nfnl_get_policy(uint16_t nlmsg_type); +const NLAPolicySet *genl_get_policy_set_by_name(const char *name); +int genl_get_policy_set_and_header_size( + sd_netlink *nl, + uint16_t id, + const NLAPolicySet **ret_policy_set, + size_t *ret_header_size); + +NLAType policy_get_type(const NLAPolicy *policy); +size_t policy_get_size(const NLAPolicy *policy); +const NLAPolicySet *policy_get_policy_set(const NLAPolicy *policy); +const NLAPolicySetUnion *policy_get_policy_set_union(const NLAPolicy *policy); + +int netlink_get_policy_set_and_header_size( + sd_netlink *nl, + uint16_t type, + const NLAPolicySet **ret_policy_set, + size_t *ret_header_size); + +const NLAPolicy *policy_set_get_policy(const NLAPolicySet *policy_set, uint16_t attr_type); +const NLAPolicySet *policy_set_get_policy_set(const NLAPolicySet *type_system, uint16_t attr_type); +const NLAPolicySetUnion *policy_set_get_policy_set_union(const NLAPolicySet *type_system, uint16_t attr_type); +uint16_t policy_set_union_get_match_attribute(const NLAPolicySetUnion *policy_set_union); +const NLAPolicySet *policy_set_union_get_policy_set_by_string(const NLAPolicySetUnion *type_system_union, const char *string); +const NLAPolicySet *policy_set_union_get_policy_set_by_family(const NLAPolicySetUnion *type_system_union, int family); diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c new file mode 100644 index 0000000..cfcf257 --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-util.c @@ -0,0 +1,760 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-netlink.h" + +#include "fd-util.h" +#include "io-util.h" +#include "memory-util.h" +#include "netlink-internal.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "process-util.h" +#include "strv.h" + +int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + _cleanup_strv_free_ char **alternative_names = NULL; + bool altname_deleted = false; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(name); + + if (!ifname_valid(name)) + return -EINVAL; + + r = rtnl_get_link_alternative_names(rtnl, ifindex, &alternative_names); + if (r < 0) + log_debug_errno(r, "Failed to get alternative names on network interface %i, ignoring: %m", + ifindex); + + if (strv_contains(alternative_names, name)) { + r = rtnl_delete_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); + if (r < 0) + return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", + name, ifindex); + + altname_deleted = true; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) + goto fail; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) + goto fail; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + goto fail; + + return 0; + +fail: + if (altname_deleted) { + int q = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); + if (q < 0) + log_debug_errno(q, "Failed to restore '%s' as an alternative name on network interface %i, ignoring: %m", + name, ifindex); + } + + return r; +} + +int rtnl_set_link_properties( + sd_netlink **rtnl, + int ifindex, + const char *alias, + const struct hw_addr_data *hw_addr, + uint32_t txqueues, + uint32_t rxqueues, + uint32_t txqueuelen, + uint32_t mtu, + uint32_t gso_max_size, + size_t gso_max_segments) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + + if (!alias && + (!hw_addr || hw_addr->length == 0) && + txqueues == 0 && + rxqueues == 0 && + txqueuelen == UINT32_MAX && + mtu == 0 && + gso_max_size == 0 && + gso_max_segments == 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 (hw_addr && hw_addr->length > 0) { + r = netlink_message_append_hw_addr(message, IFLA_ADDRESS, hw_addr); + if (r < 0) + return r; + } + + if (txqueues > 0) { + r = sd_netlink_message_append_u32(message, IFLA_NUM_TX_QUEUES, txqueues); + if (r < 0) + return r; + } + + if (rxqueues > 0) { + r = sd_netlink_message_append_u32(message, IFLA_NUM_RX_QUEUES, rxqueues); + if (r < 0) + return r; + } + + if (txqueuelen < UINT32_MAX) { + r = sd_netlink_message_append_u32(message, IFLA_TXQLEN, txqueuelen); + if (r < 0) + return r; + } + + if (mtu != 0) { + r = sd_netlink_message_append_u32(message, IFLA_MTU, mtu); + if (r < 0) + return r; + } + + if (gso_max_size > 0) { + r = sd_netlink_message_append_u32(message, IFLA_GSO_MAX_SIZE, gso_max_size); + if (r < 0) + return r; + } + + if (gso_max_segments > 0) { + r = sd_netlink_message_append_u32(message, IFLA_GSO_MAX_SEGS, gso_max_segments); + if (r < 0) + return r; + } + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + _cleanup_strv_free_ char **names = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(ret); + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, ifindex); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r < 0) + return r; + + r = sd_netlink_message_read_strv(reply, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &names); + if (r < 0 && r != -ENODATA) + return r; + + *ret = TAKE_PTR(names); + + return 0; +} + +static int rtnl_update_link_alternative_names( + sd_netlink **rtnl, + uint16_t nlmsg_type, + int ifindex, + char* const *alternative_names) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(IN_SET(nlmsg_type, RTM_NEWLINKPROP, RTM_DELLINKPROP)); + + if (strv_isempty(alternative_names)) + return 0; + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, nlmsg_type, ifindex); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(message, IFLA_PROP_LIST); + if (r < 0) + return r; + + r = sd_netlink_message_append_strv(message, IFLA_ALT_IFNAME, (const char**) alternative_names); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(message); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_set_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) { + return rtnl_update_link_alternative_names(rtnl, RTM_NEWLINKPROP, ifindex, alternative_names); +} + +int rtnl_delete_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) { + return rtnl_update_link_alternative_names(rtnl, RTM_DELLINKPROP, ifindex, alternative_names); +} + +int rtnl_set_link_alternative_names_by_ifname( + sd_netlink **rtnl, + const char *ifname, + char* const *alternative_names) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifname); + + if (strv_isempty(alternative_names)) + return 0; + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_NEWLINKPROP, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, ifname); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(message, IFLA_PROP_LIST); + if (r < 0) + return r; + + r = sd_netlink_message_append_strv(message, IFLA_ALT_IFNAME, (const char**) alternative_names); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(message); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + int r, ifindex; + + assert(name); + + /* This returns ifindex and the main interface name. */ + + if (!ifname_valid_full(name, IFNAME_VALID_ALTERNATIVE)) + return -EINVAL; + + if (!rtnl) + rtnl = &our_rtnl; + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_ALT_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r == -EINVAL) + return -ENODEV; /* The device doesn't exist */ + if (r < 0) + return r; + + r = sd_rtnl_message_link_get_ifindex(reply, &ifindex); + if (r < 0) + return r; + assert(ifindex > 0); + + if (ret) { + r = sd_netlink_message_read_string_strdup(message, IFLA_IFNAME, ret); + if (r < 0) + return r; + } + + return ifindex; +} + +int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name) { + int r; + + /* Like if_nametoindex, but resolves "alternative names" too. */ + + assert(name); + + r = if_nametoindex(name); + if (r > 0) + return r; + + return rtnl_resolve_link_alternative_name(rtnl, name, NULL); +} + +int rtnl_resolve_interface(sd_netlink **rtnl, const char *name) { + int r; + + /* Like rtnl_resolve_ifname, but resolves interface numbers too. */ + + assert(name); + + r = parse_ifindex(name); + if (r > 0) + return r; + assert(r < 0); + + return rtnl_resolve_ifname(rtnl, name); +} + +int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name) { + int r; + + r = rtnl_resolve_interface(rtnl, name); + if (r < 0) + return log_error_errno(r, "Failed to resolve interface \"%s\": %m", name); + return r; +} + +int rtnl_get_link_info( + sd_netlink **rtnl, + int ifindex, + unsigned short *ret_iftype, + unsigned *ret_flags, + char **ret_kind, + struct hw_addr_data *ret_hw_addr, + struct hw_addr_data *ret_permanent_hw_addr) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + struct hw_addr_data addr = HW_ADDR_NULL, perm_addr = HW_ADDR_NULL; + _cleanup_free_ char *kind = NULL; + unsigned short iftype; + unsigned flags; + int r; + + assert(rtnl); + assert(ifindex > 0); + + if (!ret_iftype && !ret_flags) + return 0; + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, ifindex); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r == -EINVAL) + return -ENODEV; /* The device does not exist */ + if (r < 0) + return r; + + if (ret_iftype) { + r = sd_rtnl_message_link_get_type(reply, &iftype); + if (r < 0) + return r; + } + + if (ret_flags) { + r = sd_rtnl_message_link_get_flags(reply, &flags); + if (r < 0) + return r; + } + + if (ret_kind) { + r = sd_netlink_message_enter_container(reply, IFLA_LINKINFO); + if (r >= 0) { + r = sd_netlink_message_read_string_strdup(reply, IFLA_INFO_KIND, &kind); + if (r < 0 && r != -ENODATA) + return r; + + r = sd_netlink_message_exit_container(reply); + if (r < 0) + return r; + } + } + + if (ret_hw_addr) { + r = netlink_message_read_hw_addr(reply, IFLA_ADDRESS, &addr); + if (r < 0 && r != -ENODATA) + return r; + } + + if (ret_permanent_hw_addr) { + r = netlink_message_read_hw_addr(reply, IFLA_PERM_ADDRESS, &perm_addr); + if (r < 0 && r != -ENODATA) + return r; + } + + if (ret_iftype) + *ret_iftype = iftype; + if (ret_flags) + *ret_flags = flags; + if (ret_kind) + *ret_kind = TAKE_PTR(kind); + if (ret_hw_addr) + *ret_hw_addr = addr; + if (ret_permanent_hw_addr) + *ret_permanent_hw_addr = perm_addr; + 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"); +} + +void rtattr_append_attribute_internal(struct rtattr *rta, unsigned short type, const void *data, size_t data_length) { + size_t padding_length; + uint8_t *padding; + + assert(rta); + assert(!data || data_length > 0); + + /* fill in the attribute */ + rta->rta_type = type; + rta->rta_len = RTA_LENGTH(data_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 *) rta + RTA_SPACE(data_length) - padding; + memzero(padding, padding_length); +} + +int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void *data, size_t data_length) { + struct rtattr *new_rta, *sub_rta; + size_t message_length; + + assert(rta); + assert(!data || data_length > 0); + + /* get the new message size (with padding at the end) */ + message_length = RTA_ALIGN(rta ? (*rta)->rta_len : 0) + RTA_SPACE(data_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_rta = realloc(*rta, message_length); + if (!new_rta) + return -ENOMEM; + *rta = new_rta; + + /* get pointer to the attribute we are about to add */ + sub_rta = (struct rtattr *) ((uint8_t *) *rta + RTA_ALIGN((*rta)->rta_len)); + + rtattr_append_attribute_internal(sub_rta, type, data, data_length); + + /* update rta_len */ + (*rta)->rta_len = message_length; + + return 0; +} + +MultipathRoute *multipath_route_free(MultipathRoute *m) { + if (!m) + return NULL; + + free(m->ifname); + + return mfree(m); +} + +int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret) { + _cleanup_(multipath_route_freep) MultipathRoute *n = NULL; + _cleanup_free_ char *ifname = NULL; + + assert(m); + assert(ret); + + if (m->ifname) { + ifname = strdup(m->ifname); + if (!ifname) + return -ENOMEM; + } + + n = new(MultipathRoute, 1); + if (!n) + return -ENOMEM; + + *n = (MultipathRoute) { + .gateway = m->gateway, + .weight = m->weight, + .ifindex = m->ifindex, + .ifname = TAKE_PTR(ifname), + }; + + *ret = TAKE_PTR(n); + + return 0; +} + +int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret) { + _cleanup_ordered_set_free_free_ OrderedSet *set = NULL; + int r; + + assert(rtnh); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (size < sizeof(struct rtnexthop)) + return -EBADMSG; + + for (; size >= sizeof(struct rtnexthop); ) { + _cleanup_(multipath_route_freep) MultipathRoute *m = NULL; + + if (NLMSG_ALIGN(rtnh->rtnh_len) > size) + return -EBADMSG; + + if (rtnh->rtnh_len < sizeof(struct rtnexthop)) + return -EBADMSG; + + m = new(MultipathRoute, 1); + if (!m) + return -ENOMEM; + + *m = (MultipathRoute) { + .ifindex = rtnh->rtnh_ifindex, + .weight = rtnh->rtnh_hops, + }; + + if (rtnh->rtnh_len > sizeof(struct rtnexthop)) { + size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); + + for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { + if (attr->rta_type == RTA_GATEWAY) { + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family))) + return -EBADMSG; + + m->gateway.family = family; + memcpy(&m->gateway.address, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family)); + break; + } else if (attr->rta_type == RTA_VIA) { + uint16_t gw_family; + + if (family != AF_INET) + return -EINVAL; + + if (attr->rta_len < RTA_LENGTH(sizeof(uint16_t))) + return -EBADMSG; + + gw_family = *(uint16_t *) RTA_DATA(attr); + + if (gw_family != AF_INET6) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family))) + return -EBADMSG; + + memcpy(&m->gateway, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family)); + break; + } + } + } + + r = ordered_set_ensure_put(&set, NULL, m); + if (r < 0) + return r; + + TAKE_PTR(m); + + size -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } + + if (ret) + *ret = TAKE_PTR(set); + return 0; +} + +bool netlink_pid_changed(sd_netlink *nl) { + /* We don't support people creating an nl connection and + * keeping it around over a fork(). Let's complain. */ + return ASSERT_PTR(nl)->original_pid != getpid_cached(); +} + +static int socket_open(int family) { + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, family); + if (fd < 0) + return -errno; + + return fd_move_above_stdio(fd); +} + +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; + TAKE_FD(fd); + + return 0; +} + +void netlink_seal_message(sd_netlink *nl, sd_netlink_message *m) { + uint32_t picked; + + assert(nl); + assert(!netlink_pid_changed(nl)); + assert(m); + assert(m->hdr); + + /* Avoid collisions with outstanding requests */ + do { + picked = nl->serial; + + /* Don't use seq == 0, as that is used for broadcasts, so we would get confused by replies to + such messages */ + nl->serial = nl->serial == UINT32_MAX ? 1 : nl->serial + 1; + + } while (hashmap_contains(nl->reply_callbacks, UINT32_TO_PTR(picked))); + + m->hdr->nlmsg_seq = picked; + message_seal(m); +} + +static int socket_writev_message(sd_netlink *nl, sd_netlink_message **m, size_t msgcount) { + _cleanup_free_ struct iovec *iovs = NULL; + ssize_t k; + + assert(nl); + assert(m); + assert(msgcount > 0); + + iovs = new(struct iovec, msgcount); + if (!iovs) + return -ENOMEM; + + for (size_t i = 0; i < msgcount; i++) { + assert(m[i]->hdr); + assert(m[i]->hdr->nlmsg_len > 0); + + iovs[i] = IOVEC_MAKE(m[i]->hdr, m[i]->hdr->nlmsg_len); + } + + k = writev(nl->fd, iovs, msgcount); + if (k < 0) + return -errno; + + return k; +} + +int sd_netlink_sendv( + sd_netlink *nl, + sd_netlink_message **messages, + size_t msgcount, + uint32_t **ret_serial) { + + _cleanup_free_ uint32_t *serials = NULL; + int r; + + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + assert_return(messages, -EINVAL); + assert_return(msgcount > 0, -EINVAL); + + if (ret_serial) { + serials = new(uint32_t, msgcount); + if (!serials) + return -ENOMEM; + } + + for (size_t i = 0; i < msgcount; i++) { + assert_return(!messages[i]->sealed, -EPERM); + + netlink_seal_message(nl, messages[i]); + if (serials) + serials[i] = message_get_serial(messages[i]); + } + + r = socket_writev_message(nl, messages, msgcount); + if (r < 0) + return r; + + if (ret_serial) + *ret_serial = TAKE_PTR(serials); + + return r; +} diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h new file mode 100644 index 0000000..d14392a --- /dev/null +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <linux/rtnetlink.h> + +#include "sd-netlink.h" + +#include "ether-addr-util.h" +#include "in-addr-util.h" +#include "ordered-set.h" +#include "socket-util.h" +#include "util.h" + +/* See struct rtvia in rtnetlink.h */ +typedef struct RouteVia { + uint16_t family; + union in_addr_union address; +} _packed_ RouteVia; + +typedef struct MultipathRoute { + RouteVia gateway; + uint32_t weight; + int ifindex; + char *ifname; +} MultipathRoute; + +MultipathRoute *multipath_route_free(MultipathRoute *m); +DEFINE_TRIVIAL_CLEANUP_FUNC(MultipathRoute*, multipath_route_free); + +int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret); + +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 hw_addr_data *hw_addr, + uint32_t txqueues, + uint32_t rxqueues, + uint32_t txqueuelen, + uint32_t mtu, + uint32_t gso_max_size, + size_t gso_max_segments); +int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret); +int rtnl_set_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names); +int rtnl_set_link_alternative_names_by_ifname(sd_netlink **rtnl, const char *ifname, char* const *alternative_names); +int rtnl_delete_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names); +int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret); +int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name); +int rtnl_resolve_interface(sd_netlink **rtnl, const char *name); +int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name); +int rtnl_get_link_info( + sd_netlink **rtnl, + int ifindex, + unsigned short *ret_iftype, + unsigned *ret_flags, + char **ret_kind, + struct hw_addr_data *ret_hw_addr, + struct hw_addr_data *ret_permanent_hw_addr); + +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, match, callback, destroy_callback, userdata, description) \ + ({ \ + 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, description); \ + }) + +#define genl_add_match(nl, ret_slot, family, group, cmd, callback, destroy_callback, userdata, description) \ + ({ \ + int (*_callback_)(sd_netlink *, sd_netlink_message *, typeof(userdata)) = callback; \ + void (*_destroy_)(typeof(userdata)) = destroy_callback; \ + sd_genl_add_match(nl, ret_slot, family, group, cmd, \ + (sd_netlink_message_handler_t) _callback_, \ + (sd_netlink_destroy_t) _destroy_, \ + userdata, description); \ + }) + +int netlink_message_append_hw_addr(sd_netlink_message *m, unsigned short type, const struct hw_addr_data *data); +int netlink_message_append_in_addr_union(sd_netlink_message *m, unsigned short type, int family, const union in_addr_union *data); +int netlink_message_append_sockaddr_union(sd_netlink_message *m, unsigned short type, const union sockaddr_union *data); + +int netlink_message_read_hw_addr(sd_netlink_message *m, unsigned short type, struct hw_addr_data *data); +int netlink_message_read_in_addr_union(sd_netlink_message *m, unsigned short type, int family, union in_addr_union *data); + +void rtattr_append_attribute_internal(struct rtattr *rta, unsigned short type, const void *data, size_t data_length); +int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void *data, size_t data_length); + +int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret); + +void netlink_seal_message(sd_netlink *nl, sd_netlink_message *m); + +/* TODO: to be exported later */ +int sd_netlink_sendv(sd_netlink *nl, sd_netlink_message **messages, size_t msgcnt, uint32_t **ret_serial); diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c new file mode 100644 index 0000000..74f2972 --- /dev/null +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -0,0 +1,933 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <poll.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "io-util.h" +#include "macro.h" +#include "netlink-genl.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" + +/* Some really high limit, to catch programming errors */ +#define REPLY_CALLBACKS_MAX UINT16_MAX + +static int netlink_new(sd_netlink **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *nl = NULL; + + assert_return(ret, -EINVAL); + + nl = new(sd_netlink, 1); + if (!nl) + return -ENOMEM; + + *nl = (sd_netlink) { + .n_ref = 1, + .fd = -1, + .sockaddr.nl.nl_family = AF_NETLINK, + .original_pid = getpid_cached(), + .protocol = -1, + + /* Kernel change notification messages have sequence number 0. We want to avoid that with our + * own serials, in order not to get confused when matching up kernel replies to our earlier + * requests. + * + * Moreover, when using netlink socket activation (i.e. where PID 1 binds an AF_NETLINK + * socket for us and passes it to us across execve()) and we get restarted multiple times + * while the socket sticks around we might get confused by replies from earlier runs coming + * in late — which is pretty likely if we'd start our sequence numbers always from 1. Hence, + * let's start with a value based on the system clock. This should make collisions much less + * likely (though still theoretically possible). We use a 32 bit µs counter starting at boot + * for this (and explicitly exclude the zero, see above). This counter will wrap around after + * a bit more than 1h, but that's hopefully OK as the kernel shouldn't take that long to + * reply to our requests. + * + * We only pick the initial start value this way. For each message we simply increase the + * sequence number by 1. This means we could enqueue 1 netlink message per µs without risking + * collisions, which should be OK. + * + * Note this means the serials will be in the range 1…UINT32_MAX here. + * + * (In an ideal world we'd attach the current serial counter to the netlink socket itself + * somehow, to avoid all this, but I couldn't come up with a nice way to do this) */ + .serial = (uint32_t) (now(CLOCK_MONOTONIC) % UINT32_MAX) + 1, + }; + + /* We guarantee that the read buffer has at least space for a message header */ + if (!greedy_realloc((void**) &nl->rbuffer, sizeof(struct nlmsghdr), sizeof(uint8_t))) + return -ENOMEM; + + *ret = TAKE_PTR(nl); + return 0; +} + +int sd_netlink_open_fd(sd_netlink **ret, int fd) { + _cleanup_(sd_netlink_unrefp) sd_netlink *nl = NULL; + int r, protocol; + + assert_return(ret, -EINVAL); + assert_return(fd >= 0, -EBADF); + + r = netlink_new(&nl); + if (r < 0) + return r; + + r = getsockopt_int(fd, SOL_SOCKET, SO_PROTOCOL, &protocol); + if (r < 0) + return r; + + nl->fd = fd; + nl->protocol = protocol; + + r = setsockopt_int(fd, SOL_NETLINK, NETLINK_EXT_ACK, true); + if (r < 0) + log_debug_errno(r, "sd-netlink: Failed to enable NETLINK_EXT_ACK option, ignoring: %m"); + + r = setsockopt_int(fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, true); + if (r < 0) + log_debug_errno(r, "sd-netlink: Failed to enable NETLINK_GET_STRICT_CHK option, ignoring: %m"); + + r = socket_bind(nl); + if (r < 0) { + nl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ + nl->protocol = -1; + return r; + } + + *ret = TAKE_PTR(nl); + + return 0; +} + +int sd_netlink_open(sd_netlink **ret) { + return netlink_open_family(ret, NETLINK_ROUTE); +} + +int sd_netlink_increase_rxbuf(sd_netlink *nl, size_t size) { + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + + return fd_increase_rxbuf(nl->fd, size); +} + +static sd_netlink *netlink_free(sd_netlink *nl) { + sd_netlink_slot *s; + unsigned i; + + assert(nl); + + for (i = 0; i < nl->rqueue_size; i++) + sd_netlink_message_unref(nl->rqueue[i]); + free(nl->rqueue); + + for (i = 0; i < nl->rqueue_partial_size; i++) + sd_netlink_message_unref(nl->rqueue_partial[i]); + free(nl->rqueue_partial); + + free(nl->rbuffer); + + while ((s = nl->slots)) { + assert(s->floating); + netlink_slot_disconnect(s, true); + } + hashmap_free(nl->reply_callbacks); + prioq_free(nl->reply_callbacks_prioq); + + sd_event_source_unref(nl->io_event_source); + sd_event_source_unref(nl->time_event_source); + sd_event_unref(nl->event); + + hashmap_free(nl->broadcast_group_refs); + + genl_clear_family(nl); + + safe_close(nl->fd); + return mfree(nl); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_netlink, sd_netlink, netlink_free); + +int sd_netlink_send( + sd_netlink *nl, + sd_netlink_message *message, + uint32_t *serial) { + + int r; + + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + assert_return(message, -EINVAL); + assert_return(!message->sealed, -EPERM); + + netlink_seal_message(nl, message); + + r = socket_write_message(nl, message); + if (r < 0) + return r; + + if (serial) + *serial = message_get_serial(message); + + return 1; +} + +int netlink_rqueue_make_room(sd_netlink *nl) { + assert(nl); + + if (nl->rqueue_size >= NETLINK_RQUEUE_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), + "sd-netlink: exhausted the read queue size (%d)", + NETLINK_RQUEUE_MAX); + + if (!GREEDY_REALLOC(nl->rqueue, nl->rqueue_size + 1)) + return -ENOMEM; + + return 0; +} + +int netlink_rqueue_partial_make_room(sd_netlink *nl) { + assert(nl); + + if (nl->rqueue_partial_size >= NETLINK_RQUEUE_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), + "sd-netlink: exhausted the partial read queue size (%d)", + NETLINK_RQUEUE_MAX); + + if (!GREEDY_REALLOC(nl->rqueue_partial, nl->rqueue_partial_size + 1)) + return -ENOMEM; + + return 0; +} + +static int dispatch_rqueue(sd_netlink *nl, sd_netlink_message **message) { + int r; + + assert(nl); + assert(message); + + if (nl->rqueue_size <= 0) { + /* Try to read a new message */ + r = socket_read_message(nl); + if (r == -ENOBUFS) { /* FIXME: ignore buffer overruns for now */ + log_debug_errno(r, "sd-netlink: Got ENOBUFS from netlink socket, ignoring."); + return 1; + } + if (r <= 0) + return r; + } + + /* Dispatch a queued message */ + *message = nl->rqueue[0]; + nl->rqueue_size--; + memmove(nl->rqueue, nl->rqueue + 1, sizeof(sd_netlink_message*) * nl->rqueue_size); + + return 1; +} + +static int process_timeout(sd_netlink *nl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + struct reply_callback *c; + sd_netlink_slot *slot; + usec_t n; + int r; + + assert(nl); + + c = prioq_peek(nl->reply_callbacks_prioq); + if (!c) + return 0; + + n = now(CLOCK_MONOTONIC); + if (c->timeout > n) + return 0; + + r = message_new_synthetic_error(nl, -ETIMEDOUT, c->serial, &m); + if (r < 0) + return r; + + assert_se(prioq_pop(nl->reply_callbacks_prioq) == c); + c->timeout = 0; + hashmap_remove(nl->reply_callbacks, UINT32_TO_PTR(c->serial)); + + slot = container_of(c, sd_netlink_slot, reply_callback); + + r = c->callback(nl, 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 *nl, sd_netlink_message *m) { + struct reply_callback *c; + sd_netlink_slot *slot; + uint32_t serial; + uint16_t type; + int r; + + assert(nl); + assert(m); + + serial = message_get_serial(m); + c = hashmap_remove(nl->reply_callbacks, UINT32_TO_PTR(serial)); + if (!c) + return 0; + + if (c->timeout != 0) { + prioq_remove(nl->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(nl, 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 *nl, sd_netlink_message *m) { + uint16_t type; + uint8_t cmd; + int r; + + assert(nl); + assert(m); + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + + if (m->protocol == NETLINK_GENERIC) { + r = sd_genl_message_get_command(nl, m, &cmd); + if (r < 0) + return r; + } else + cmd = 0; + + LIST_FOREACH(match_callbacks, c, nl->match_callbacks) { + sd_netlink_slot *slot; + bool found = false; + + if (c->type != type) + continue; + if (c->cmd != 0 && c->cmd != cmd) + continue; + + for (size_t i = 0; i < c->n_groups; i++) + if (c->groups[i] == m->multicast_group) { + found = true; + break; + } + + if (!found) + continue; + + slot = container_of(c, sd_netlink_slot, match_callback); + + r = c->callback(nl, m, slot->userdata); + if (r < 0) + log_debug_errno(r, "sd-netlink: match callback %s%s%sfailed: %m", + slot->description ? "'" : "", + strempty(slot->description), + slot->description ? "' " : ""); + if (r != 0) + break; + } + + return 1; +} + +static int process_running(sd_netlink *nl, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nl); + + r = process_timeout(nl); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(nl, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + if (sd_netlink_message_is_broadcast(m)) + r = process_match(nl, m); + else + r = process_reply(nl, 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 *nl, sd_netlink_message **ret) { + NETLINK_DONT_DESTROY(nl); + int r; + + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + assert_return(!nl->processing, -EBUSY); + + nl->processing = true; + r = process_running(nl, ret); + nl->processing = false; + + return r; +} + +static usec_t calc_elapse(uint64_t usec) { + if (usec == UINT64_MAX) + return 0; + + if (usec == 0) + usec = NETLINK_DEFAULT_TIMEOUT_USEC; + + return usec_add(now(CLOCK_MONOTONIC), usec); +} + +static int netlink_poll(sd_netlink *nl, bool need_more, usec_t timeout_usec) { + usec_t m = USEC_INFINITY; + int r, e; + + assert(nl); + + e = sd_netlink_get_events(nl); + 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(nl, &until); + if (r < 0) + return r; + + m = usec_sub_unsigned(until, now(CLOCK_MONOTONIC)); + } + + r = fd_wait_for_event(nl->fd, e, MIN(m, timeout_usec)); + if (r <= 0) + return r; + + return 1; +} + +int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + + if (nl->rqueue_size > 0) + return 0; + + return netlink_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; + int r, k; + + assert_return(nl, -EINVAL); + assert_return(m, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + + if (hashmap_size(nl->reply_callbacks) >= REPLY_CALLBACKS_MAX) + return -ERANGE; + + r = hashmap_ensure_allocated(&nl->reply_callbacks, &trivial_hash_ops); + if (r < 0) + return r; + + if (usec != UINT64_MAX) { + 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, &slot->reply_callback.serial); + if (k < 0) + return k; + + r = hashmap_put(nl->reply_callbacks, UINT32_TO_PTR(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, UINT32_TO_PTR(slot->reply_callback.serial)); + return r; + } + } + + /* Set this at last. Otherwise, some failures in above would call destroy_callback but some would not. */ + slot->destroy_callback = destroy_callback; + + if (ret_slot) + *ret_slot = slot; + + TAKE_PTR(slot); + + return k; +} + +int sd_netlink_read( + sd_netlink *nl, + uint32_t serial, + uint64_t usec, + sd_netlink_message **ret) { + + usec_t timeout; + int r; + + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + + timeout = calc_elapse(usec); + + for (;;) { + usec_t left; + + for (unsigned i = 0; i < nl->rqueue_size; i++) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; + uint32_t received_serial; + uint16_t type; + + received_serial = message_get_serial(nl->rqueue[i]); + if (received_serial != serial) + continue; + + incoming = nl->rqueue[i]; + + /* found a match, remove from rqueue and return it */ + memmove(nl->rqueue + i, nl->rqueue + i + 1, + sizeof(sd_netlink_message*) * (nl->rqueue_size - i - 1)); + nl->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) { + if (ret) + *ret = NULL; + return 0; + } + + if (ret) + *ret = TAKE_PTR(incoming); + return 1; + } + + r = socket_read_message(nl); + 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 = usec_sub_unsigned(timeout, n); + } else + left = USEC_INFINITY; + + r = netlink_poll(nl, true, left); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +int sd_netlink_call( + sd_netlink *nl, + sd_netlink_message *message, + uint64_t usec, + sd_netlink_message **ret) { + + uint32_t serial; + int r; + + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + assert_return(message, -EINVAL); + + r = sd_netlink_send(nl, message, &serial); + if (r < 0) + return r; + + return sd_netlink_read(nl, serial, usec, ret); +} + +int sd_netlink_get_events(sd_netlink *nl) { + assert_return(nl, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + + return nl->rqueue_size == 0 ? POLLIN : 0; +} + +int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout_usec) { + struct reply_callback *c; + + assert_return(nl, -EINVAL); + assert_return(timeout_usec, -EINVAL); + assert_return(!netlink_pid_changed(nl), -ECHILD); + + if (nl->rqueue_size > 0) { + *timeout_usec = 0; + return 1; + } + + c = prioq_peek(nl->reply_callbacks_prioq); + if (!c) { + *timeout_usec = UINT64_MAX; + 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 *nl = ASSERT_PTR(userdata); + int r; + + r = sd_netlink_process(nl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { + sd_netlink *nl = ASSERT_PTR(userdata); + int r; + + r = sd_netlink_process(nl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int prepare_callback(sd_event_source *s, void *userdata) { + sd_netlink *nl = ASSERT_PTR(userdata); + int r, enabled; + usec_t until; + + assert(s); + + r = sd_netlink_get_events(nl); + if (r < 0) + return r; + + r = sd_event_source_set_io_events(nl->io_event_source, r); + if (r < 0) + return r; + + enabled = sd_netlink_get_timeout(nl, &until); + if (enabled < 0) + return enabled; + if (enabled > 0) { + r = sd_event_source_set_time(nl->time_event_source, until); + if (r < 0) + return r; + } + + r = sd_event_source_set_enabled(nl->time_event_source, + enabled > 0 ? SD_EVENT_ONESHOT : SD_EVENT_OFF); + if (r < 0) + return r; + + return 1; +} + +int sd_netlink_attach_event(sd_netlink *nl, sd_event *event, int64_t priority) { + int r; + + assert_return(nl, -EINVAL); + assert_return(!nl->event, -EBUSY); + + assert(!nl->io_event_source); + assert(!nl->time_event_source); + + if (event) + nl->event = sd_event_ref(event); + else { + r = sd_event_default(&nl->event); + if (r < 0) + return r; + } + + r = sd_event_add_io(nl->event, &nl->io_event_source, nl->fd, 0, io_callback, nl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nl->io_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(nl->io_event_source, "netlink-receive-message"); + if (r < 0) + goto fail; + + r = sd_event_source_set_prepare(nl->io_event_source, prepare_callback); + if (r < 0) + goto fail; + + r = sd_event_add_time(nl->event, &nl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, nl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nl->time_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(nl->time_event_source, "netlink-timer"); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_netlink_detach_event(nl); + return r; +} + +int sd_netlink_detach_event(sd_netlink *nl) { + assert_return(nl, -EINVAL); + assert_return(nl->event, -ENXIO); + + nl->io_event_source = sd_event_source_unref(nl->io_event_source); + + nl->time_event_source = sd_event_source_unref(nl->time_event_source); + + nl->event = sd_event_unref(nl->event); + + return 0; +} + +int netlink_add_match_internal( + sd_netlink *nl, + sd_netlink_slot **ret_slot, + const uint32_t *groups, + size_t n_groups, + uint16_t type, + uint8_t cmd, + 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(groups); + assert(n_groups > 0); + + for (size_t i = 0; i < n_groups; i++) { + r = socket_broadcast_group_ref(nl, groups[i]); + if (r < 0) + return r; + } + + r = netlink_slot_allocate(nl, !ret_slot, NETLINK_MATCH_CALLBACK, sizeof(struct match_callback), + userdata, description, &slot); + if (r < 0) + return r; + + slot->match_callback.groups = newdup(uint32_t, groups, n_groups); + if (!slot->match_callback.groups) + return -ENOMEM; + + slot->match_callback.n_groups = n_groups; + slot->match_callback.callback = callback; + slot->match_callback.type = type; + slot->match_callback.cmd = cmd; + + LIST_PREPEND(match_callbacks, nl->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; +} + +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) { + + static const uint32_t + address_groups[] = { RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR, }, + link_groups[] = { RTNLGRP_LINK, }, + neighbor_groups[] = { RTNLGRP_NEIGH, }, + nexthop_groups[] = { RTNLGRP_NEXTHOP, }, + route_groups[] = { RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV6_ROUTE, }, + rule_groups[] = { RTNLGRP_IPV4_RULE, RTNLGRP_IPV6_RULE, }, + tc_groups[] = { RTNLGRP_TC }; + const uint32_t *groups; + size_t n_groups; + + assert_return(rtnl, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!netlink_pid_changed(rtnl), -ECHILD); + + switch (type) { + case RTM_NEWLINK: + case RTM_DELLINK: + groups = link_groups; + n_groups = ELEMENTSOF(link_groups); + break; + case RTM_NEWADDR: + case RTM_DELADDR: + groups = address_groups; + n_groups = ELEMENTSOF(address_groups); + break; + case RTM_NEWNEIGH: + case RTM_DELNEIGH: + groups = neighbor_groups; + n_groups = ELEMENTSOF(neighbor_groups); + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + groups = route_groups; + n_groups = ELEMENTSOF(route_groups); + break; + case RTM_NEWRULE: + case RTM_DELRULE: + groups = rule_groups; + n_groups = ELEMENTSOF(rule_groups); + break; + case RTM_NEWNEXTHOP: + case RTM_DELNEXTHOP: + groups = nexthop_groups; + n_groups = ELEMENTSOF(nexthop_groups); + break; + case RTM_NEWQDISC: + case RTM_DELQDISC: + case RTM_NEWTCLASS: + case RTM_DELTCLASS: + groups = tc_groups; + n_groups = ELEMENTSOF(tc_groups); + break; + default: + return -EOPNOTSUPP; + } + + return netlink_add_match_internal(rtnl, ret_slot, groups, n_groups, type, 0, callback, + destroy_callback, userdata, description); +} + +int sd_netlink_attach_filter(sd_netlink *nl, size_t len, const struct sock_filter *filter) { + assert_return(nl, -EINVAL); + assert_return(len == 0 || filter, -EINVAL); + + if (setsockopt(nl->fd, SOL_SOCKET, + len == 0 ? SO_DETACH_FILTER : SO_ATTACH_FILTER, + &(struct sock_fprog) { + .len = len, + .filter = (struct sock_filter*) filter, + }, sizeof(struct sock_fprog)) < 0) + return -errno; + + 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..df2e203 --- /dev/null +++ b/src/libsystemd/sd-netlink/test-netlink.c @@ -0,0 +1,759 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <netinet/ether.h> +#include <netinet/in.h> +#include <linux/fou.h> +#include <linux/genetlink.h> +#include <linux/if_macsec.h> +#include <linux/l2tp.h> +#include <linux/nl80211.h> +#include <unistd.h> + +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "macro.h" +#include "netlink-genl.h" +#include "netlink-internal.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +static void test_message_link_bridge(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + uint32_t cost; + + log_debug("/* %s */", __func__); + + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); + assert_se(sd_rtnl_message_link_set_family(message, AF_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, rtnl) >= 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, *reply = NULL; + uint32_t mtu_out; + const char *name_out; + struct ether_addr mac_out; + + log_debug("/* %s */", __func__); + + /* 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_call(rtnl, message, 0, &reply) == 1); + + assert_se(sd_netlink_message_read_string(reply, IFLA_IFNAME, &name_out) >= 0); + assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &mac_out) >= 0); + assert_se(sd_netlink_message_read_u32(reply, IFLA_MTU, &mtu_out) >= 0); +} + +static void test_link_get(sd_netlink *rtnl, int ifindex) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; + const char *str_data; + uint8_t u8_data; + uint32_t u32_data; + struct ether_addr eth_data; + + log_debug("/* %s */", __func__); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + assert_se(m); + + assert_se(sd_netlink_call(rtnl, m, 0, &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_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); +} + +static void test_address_get(sd_netlink *rtnl, int ifindex) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; + struct in_addr in_data; + struct ifa_cacheinfo cache; + const char *label; + + log_debug("/* %s */", __func__); + + assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); + assert_se(m); + assert_se(sd_netlink_message_set_request_dump(m, true) >= 0); + 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); +} + +static void test_route(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + struct in_addr addr, addr_data; + uint32_t index = 2, u32_data; + int r; + + log_debug("/* %s */", __func__); + + 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 = htobe32(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, rtnl) >= 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; + + log_debug("/* %s */", __func__); + + 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; + + log_debug("/* %s */", __func__); + + 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; + + log_debug("/* %s */", __func__); + + 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; + + log_debug("/* %s */", __func__); + + 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_se(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_se(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; + + log_debug("/* %s */", __func__); + + 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; + + log_debug("/* %s */", __func__); + + 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; + + log_debug("/* %s */", __func__); + + 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, rtnl) >= 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; + + log_debug("/* %s */", __func__); + + 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; + + log_debug("/* %s */", __func__); + + assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); + assert_se(sd_netlink_message_set_request_dump(req, true) >= 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%i 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; + + log_debug("/* %s */", __func__); + + assert_se(message_new_synthetic_error(rtnl, -ETIMEDOUT, 1, &m) >= 0); + assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); +} + +static void test_array(void) { + _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + + log_debug("/* %s */", __func__); + + assert_se(sd_genl_socket_open(&genl) >= 0); + assert_se(sd_genl_message_new(genl, CTRL_GENL_NAME, CTRL_CMD_GETFAMILY, &m) >= 0); + + assert_se(sd_netlink_message_open_container(m, CTRL_ATTR_MCAST_GROUPS) >= 0); + for (unsigned i = 0; i < 10; i++) { + char name[STRLEN("hoge") + DECIMAL_STR_MAX(uint32_t)]; + uint32_t id = i + 1000; + + xsprintf(name, "hoge%" PRIu32, id); + assert_se(sd_netlink_message_open_array(m, i + 1) >= 0); + assert_se(sd_netlink_message_append_u32(m, CTRL_ATTR_MCAST_GRP_ID, id) >= 0); + assert_se(sd_netlink_message_append_string(m, CTRL_ATTR_MCAST_GRP_NAME, name) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + } + assert_se(sd_netlink_message_close_container(m) >= 0); + + message_seal(m); + assert_se(sd_netlink_message_rewind(m, genl) >= 0); + + assert_se(sd_netlink_message_enter_container(m, CTRL_ATTR_MCAST_GROUPS) >= 0); + for (unsigned i = 0; i < 10; i++) { + char expected[STRLEN("hoge") + DECIMAL_STR_MAX(uint32_t)]; + const char *name; + uint32_t id; + + assert_se(sd_netlink_message_enter_array(m, i + 1) >= 0); + assert_se(sd_netlink_message_read_u32(m, CTRL_ATTR_MCAST_GRP_ID, &id) >= 0); + assert_se(sd_netlink_message_read_string(m, CTRL_ATTR_MCAST_GRP_NAME, &name) >= 0); + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(id == i + 1000); + xsprintf(expected, "hoge%" PRIu32, id); + assert_se(streq(name, expected)); + } + assert_se(sd_netlink_message_exit_container(m) >= 0); +} + +static void test_strv(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_strv_free_ char **names_in = NULL, **names_out; + const char *p; + + log_debug("/* %s */", __func__); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINKPROP, 1) >= 0); + + for (unsigned i = 0; i < 10; i++) { + char name[STRLEN("hoge") + DECIMAL_STR_MAX(uint32_t)]; + + xsprintf(name, "hoge%" PRIu32, i + 1000); + assert_se(strv_extend(&names_in, name) >= 0); + } + + assert_se(sd_netlink_message_open_container(m, IFLA_PROP_LIST) >= 0); + assert_se(sd_netlink_message_append_strv(m, IFLA_ALT_IFNAME, (const char**) names_in) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + + message_seal(m); + assert_se(sd_netlink_message_rewind(m, rtnl) >= 0); + + assert_se(sd_netlink_message_read_strv(m, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &names_out) >= 0); + assert_se(strv_equal(names_in, names_out)); + + assert_se(sd_netlink_message_enter_container(m, IFLA_PROP_LIST) >= 0); + assert_se(sd_netlink_message_read_string(m, IFLA_ALT_IFNAME, &p) >= 0); + assert_se(streq(p, "hoge1009")); + assert_se(sd_netlink_message_exit_container(m) >= 0); +} + +static int genl_ctrl_match_callback(sd_netlink *genl, sd_netlink_message *m, void *userdata) { + const char *name; + uint16_t id; + uint8_t cmd; + + assert_se(genl); + assert_se(m); + + assert_se(sd_genl_message_get_family_name(genl, m, &name) >= 0); + assert_se(streq(name, CTRL_GENL_NAME)); + + assert_se(sd_genl_message_get_command(genl, m, &cmd) >= 0); + + switch (cmd) { + case CTRL_CMD_NEWFAMILY: + case CTRL_CMD_DELFAMILY: + assert_se(sd_netlink_message_read_string(m, CTRL_ATTR_FAMILY_NAME, &name) >= 0); + assert_se(sd_netlink_message_read_u16(m, CTRL_ATTR_FAMILY_ID, &id) >= 0); + log_debug("%s: %s (id=%"PRIu16") family is %s.", + __func__, name, id, cmd == CTRL_CMD_NEWFAMILY ? "added" : "removed"); + break; + case CTRL_CMD_NEWMCAST_GRP: + case CTRL_CMD_DELMCAST_GRP: + assert_se(sd_netlink_message_read_string(m, CTRL_ATTR_FAMILY_NAME, &name) >= 0); + assert_se(sd_netlink_message_read_u16(m, CTRL_ATTR_FAMILY_ID, &id) >= 0); + log_debug("%s: multicast group for %s (id=%"PRIu16") family is %s.", + __func__, name, id, cmd == CTRL_CMD_NEWMCAST_GRP ? "added" : "removed"); + break; + default: + log_debug("%s: received nlctrl message with unknown command '%"PRIu8"'.", __func__, cmd); + } + + return 0; +} + +static void test_genl(void) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + const char *name; + uint8_t cmd; + int r; + + log_debug("/* %s */", __func__); + + assert_se(sd_genl_socket_open(&genl) >= 0); + assert_se(sd_event_default(&event) >= 0); + assert_se(sd_netlink_attach_event(genl, event, 0) >= 0); + + assert_se(sd_genl_message_new(genl, CTRL_GENL_NAME, CTRL_CMD_GETFAMILY, &m) >= 0); + assert_se(sd_genl_message_get_family_name(genl, m, &name) >= 0); + assert_se(streq(name, CTRL_GENL_NAME)); + assert_se(sd_genl_message_get_command(genl, m, &cmd) >= 0); + assert_se(cmd == CTRL_CMD_GETFAMILY); + + assert_se(sd_genl_add_match(genl, NULL, CTRL_GENL_NAME, "notify", 0, genl_ctrl_match_callback, NULL, NULL, "genl-ctrl-notify") >= 0); + + m = sd_netlink_message_unref(m); + assert_se(sd_genl_message_new(genl, "should-not-exist", CTRL_CMD_GETFAMILY, &m) < 0); + assert_se(sd_genl_message_new(genl, "should-not-exist", CTRL_CMD_GETFAMILY, &m) == -EOPNOTSUPP); + + /* These families may not be supported by kernel. Hence, ignore results. */ + (void) sd_genl_message_new(genl, FOU_GENL_NAME, 0, &m); + m = sd_netlink_message_unref(m); + (void) sd_genl_message_new(genl, L2TP_GENL_NAME, 0, &m); + m = sd_netlink_message_unref(m); + (void) sd_genl_message_new(genl, MACSEC_GENL_NAME, 0, &m); + m = sd_netlink_message_unref(m); + (void) sd_genl_message_new(genl, NL80211_GENL_NAME, 0, &m); + m = sd_netlink_message_unref(m); + (void) sd_genl_message_new(genl, NETLBL_NLTYPE_UNLABELED_NAME, 0, &m); + + for (;;) { + r = sd_event_run(event, 500 * USEC_PER_MSEC); + assert_se(r >= 0); + if (r == 0) + return; + } +} + +static void test_rtnl_set_link_name(sd_netlink *rtnl, int ifindex) { + _cleanup_strv_free_ char **alternative_names = NULL; + int r; + + log_debug("/* %s */", __func__); + + if (geteuid() != 0) + return (void) log_tests_skipped("not root"); + + /* Test that the new name (which is currently an alternative name) is + * restored as an alternative name on error. Create an error by using + * an invalid device name, namely one that exceeds IFNAMSIZ + * (alternative names can exceed IFNAMSIZ, but not regular names). */ + r = rtnl_set_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")); + if (r == -EPERM) + return (void) log_tests_skipped("missing required capabilities"); + + assert_se(r >= 0); + assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename") == -EINVAL); + assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); + assert_se(strv_contains(alternative_names, "testlongalternativename")); + assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); +} + +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_setup_logging(LOG_DEBUG); + + test_match(); + test_multiple(); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(rtnl); + + test_route(rtnl); + test_message(rtnl); + test_container(rtnl); + test_array(); + test_strv(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_rtnl_set_link_name(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); + + test_genl(); + + return EXIT_SUCCESS; +} |