summaryrefslogtreecommitdiffstats
path: root/src/libsystemd/sd-netlink/netlink-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsystemd/sd-netlink/netlink-util.c')
-rw-r--r--src/libsystemd/sd-netlink/netlink-util.c447
1 files changed, 447 insertions, 0 deletions
diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c
new file mode 100644
index 0000000..09e2158
--- /dev/null
+++ b/src/libsystemd/sd-netlink/netlink-util.c
@@ -0,0 +1,447 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-netlink.h"
+
+#include "format-util.h"
+#include "memory-util.h"
+#include "netlink-internal.h"
+#include "netlink-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;
+ char old_name[IF_NAMESIZE + 1] = {};
+ 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);
+
+ format_ifname(ifindex, old_name);
+ }
+
+ r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_string(message, IFLA_IFNAME, name);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(*rtnl, message, 0, NULL);
+ if (r < 0)
+ return r;
+
+ if (!isempty(old_name)) {
+ r = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(old_name));
+ if (r < 0)
+ log_debug_errno(r, "Failed to set '%s' as an alternative name on network interface %i, ignoring: %m",
+ old_name, ifindex);
+ }
+
+ return 0;
+}
+
+int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias,
+ const struct ether_addr *mac, uint32_t mtu) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL;
+ int r;
+
+ assert(rtnl);
+ assert(ifindex > 0);
+
+ if (!alias && !mac && mtu == 0)
+ return 0;
+
+ if (!*rtnl) {
+ r = sd_netlink_open(rtnl);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex);
+ if (r < 0)
+ return r;
+
+ if (alias) {
+ r = sd_netlink_message_append_string(message, IFLA_IFALIAS, alias);
+ if (r < 0)
+ return r;
+ }
+
+ if (mac) {
+ r = sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, mac);
+ if (r < 0)
+ return r;
+ }
+
+ if (mtu != 0) {
+ r = sd_netlink_message_append_u32(message, IFLA_MTU, mtu);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_netlink_call(*rtnl, message, 0, NULL);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int rtnl_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, 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, 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) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL;
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL;
+ int r, ret;
+
+ assert(name);
+
+ 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, &ret);
+ if (r < 0)
+ return r;
+ assert(ret > 0);
+ return ret;
+}
+
+int rtnl_get_link_iftype(sd_netlink **rtnl, int ifindex, unsigned short *ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL;
+ int r;
+
+ 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;
+
+ return sd_rtnl_message_link_get_type(reply, ret);
+}
+
+int rtnl_message_new_synthetic_error(sd_netlink *rtnl, int error, uint32_t serial, sd_netlink_message **ret) {
+ struct nlmsgerr *err;
+ int r;
+
+ assert(error <= 0);
+
+ r = message_new(rtnl, ret, NLMSG_ERROR);
+ if (r < 0)
+ return r;
+
+ rtnl_message_seal(*ret);
+ (*ret)->hdr->nlmsg_seq = serial;
+
+ err = NLMSG_DATA((*ret)->hdr);
+ err->error = error;
+
+ return 0;
+}
+
+int rtnl_log_parse_error(int r) {
+ return log_error_errno(r, "Failed to parse netlink message: %m");
+}
+
+int rtnl_log_create_error(int r) {
+ return log_error_errno(r, "Failed to create netlink message: %m");
+}
+
+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;
+}
+
+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_free_ 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 == 0 ? 0 : rtnh->rtnh_hops + 1,
+ };
+
+ 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;
+}