diff --git a/lib/libifconfig/Makefile b/lib/libifconfig/Makefile --- a/lib/libifconfig/Makefile +++ b/lib/libifconfig/Makefile @@ -18,6 +18,7 @@ libifconfig_internal.c \ libifconfig_lagg.c \ libifconfig_media.c \ + libifconfig_nl.c \ libifconfig_sfp.c GEN= libifconfig_sfp_tables.h \ diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h --- a/lib/libifconfig/libifconfig.h +++ b/lib/libifconfig/libifconfig.h @@ -382,3 +382,23 @@ * length of *lenp * IFNAMSIZ bytes. */ int ifconfig_list_cloners(ifconfig_handle_t *h, char **bufp, size_t *lenp); + +/** Get MAC address of an interface + * @param h An open ifconfig state object + * @param ifname The interface name + * @param addr Return argument. It will be filled with the MAC address + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_get_mac(ifconfig_handle_t *h, const char *ifname, + struct ether_addr *addr); + +/** Set MAC address on an interface + * @param h An open ifconfig state object + * @param ifname The interface name + * @param addr The MAC address + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_set_mac(ifconfig_handle_t *h, const char *ifname, + const struct ether_addr *addr); diff --git a/lib/libifconfig/libifconfig_nl.c b/lib/libifconfig/libifconfig_nl.c new file mode 100644 --- /dev/null +++ b/lib/libifconfig/libifconfig_nl.c @@ -0,0 +1,186 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025, Muhammad Saheed + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libifconfig.h" +#include "libifconfig_internal.h" + +static int ifconfig_get_link(ifconfig_handle_t *h, struct snl_state *ss, + const char *ifname, struct snl_parsed_link_simple *link); + +int +ifconfig_get_mac(ifconfig_handle_t *h, const char *ifname, + struct ether_addr *addr) +{ + int ret = 0; + uint32_t nlmsg_seq = 0; + struct snl_state ss; + struct snl_writer nw = { 0 }; + struct nlmsghdr *hdr; + struct snl_parsed_link link = { 0 }; + struct snl_errmsg_data e = { 0 }; + + assert(addr != NULL); + + if (!snl_init(&ss, NETLINK_ROUTE)) { + ifconfig_error(h, NETLINK, ENOTSUP); + return (-1); + } + + snl_init_writer(&ss, &nw); + hdr = snl_create_msg_request(&nw, RTM_GETLINK); + (void)snl_reserve_msg_object(&nw, struct ifinfomsg); + + snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname); + + if ((hdr = snl_finalize_msg(&nw)) == NULL) { + ifconfig_error(h, NETLINK, ENOMEM); + ret = -1; + goto out; + } + + if (!snl_send_message(&ss, hdr)) { + ifconfig_error(h, NETLINK, EIO); + ret = -1; + goto out; + } + + nlmsg_seq = hdr->nlmsg_seq; + while ((hdr = snl_read_reply_multi(&ss, nlmsg_seq, &e)) != NULL) { + if (!snl_parse_nlmsg(&ss, hdr, &snl_rtm_link_parser, &link)) + continue; + + if (link.ifla_address != NULL && + NLA_DATA_LEN(link.ifla_address) == ETHER_ADDR_LEN) { + memcpy(addr, NLA_DATA(link.ifla_address), + ETHER_ADDR_LEN); + goto out; + } + } + ifconfig_error(h, NETLINK, ENOENT); + ret = -1; + +out: + snl_free(&ss); + return (ret); +} + +static int +ifconfig_get_link(ifconfig_handle_t *h, struct snl_state *ss, + const char *ifname, struct snl_parsed_link_simple *link) +{ + struct snl_writer nw = { 0 }; + struct nlmsghdr *hdr; + struct snl_errmsg_data e = { 0 }; + + snl_init_writer(ss, &nw); + hdr = snl_create_msg_request(&nw, RTM_GETLINK); + (void)snl_reserve_msg_object(&nw, struct ifinfomsg); + snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname); + + if ((hdr = snl_finalize_msg(&nw)) == NULL) { + ifconfig_error(h, NETLINK, ENOMEM); + return (-1); + } + + if (!snl_send_message(ss, hdr)) { + ifconfig_error(h, NETLINK, EIO); + return (-1); + } + + hdr = snl_read_reply(ss, hdr->nlmsg_seq); + if (hdr->nlmsg_type == NLMSG_ERROR) { + int error = snl_parse_errmsg(ss, hdr, &e) ? e.error : EINVAL; + + ifconfig_error(h, NETLINK, error); + return (-1); + } + + if (hdr->nlmsg_type != RTM_NEWLINK) { + ifconfig_error(h, NETLINK, EINVAL); + return (1); + } + + if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, link)) { + ifconfig_error(h, NETLINK, EINVAL); + return (1); + } + + return (0); +} + +int +ifconfig_set_mac(ifconfig_handle_t *h, const char *ifname, + const struct ether_addr *addr) +{ + int ret = 0; + struct snl_state ss; + struct snl_writer nw = { 0 }; + struct nlmsghdr *hdr; + struct ifinfomsg *ifi; + struct snl_errmsg_data e = { 0 }; + struct snl_parsed_link_simple link = { 0 }; + + assert(addr != NULL); + + if (!snl_init(&ss, NETLINK_ROUTE)) { + ifconfig_error(h, NETLINK, ENOTSUP); + return (-1); + } + + snl_init_writer(&ss, &nw); + hdr = snl_create_msg_request(&nw, RTM_NEWLINK); + ifi = snl_reserve_msg_object(&nw, struct ifinfomsg); + + if (ifconfig_get_link(h, &ss, ifname, &link) != 0) { + ret = -1; + goto out; + } + + ifi->ifi_family = AF_UNSPEC; + ifi->ifi_type = link.ifi_type; + ifi->ifi_index = link.ifi_index; + + /* + * Preserve interface flags + */ + ifi->ifi_flags = link.ifi_flags; + ifi->ifi_change = 0xFFFFFFFF; + + snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname); + snl_add_msg_attr(&nw, IFLA_ADDRESS, ETHER_ADDR_LEN, addr); + + if ((hdr = snl_finalize_msg(&nw)) == NULL) { + ifconfig_error(h, NETLINK, ENOMEM); + ret = -1; + goto out; + } + + if (!snl_send_message(&ss, hdr)) { + ifconfig_error(h, NETLINK, EIO); + ret = -1; + goto out; + } + + if (!snl_read_reply_code(&ss, hdr->nlmsg_seq, &e)) { + ifconfig_error(h, NETLINK, e.error); + ret = -1; + goto out; + } + +out: + snl_free(&ss); + return (ret); +}