/* 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; }