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,82 @@ * length of *lenp * IFNAMSIZ bytes. */ int ifconfig_list_cloners(ifconfig_handle_t *h, char **bufp, size_t *lenp); + +/** Brings the interface up + * @param h An open ifconfig state object + * @param ifname The interface name + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_if_up(ifconfig_handle_t *h, const char *ifname); + +/** Brings the interface down + * @param h An open ifconfig state object + * @param ifname The interface name + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_if_down(ifconfig_handle_t *h, const char *ifname); + +/** Adds an IPv4 address to an interface + * @param h An open ifconfig state object + * @param ifname The interface name + * @param addr The IPv4 address to be added + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_add_inet(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet_addr *addr); + +/** Delete an IPv4 address on an interface + * @param h An open ifconfig state object + * @param ifname The interface name + * @param addr The IPv4 address to be deleted + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_del_inet(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet_addr *addr); + +/** Adds an IPv6 address to an interface + * @param h An open ifconfig state object + * @param ifname The interface name + * @param addr The IPv6 address to be added. + * For non-P2P addresses, + * either only the dst field is set + * or both sin6 and dst fields are set to the same address. + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_add_inet6(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet6_addr *addr); + +/** Delete an IPv6 address on an interface + * @param h An open ifconfig state object + * @param ifname The interface name + * @param addr The IPv6 address to be deleted + * @return 0 on success, nonzero on failure. + * On failure, the error info on the handle is set. + */ +int ifconfig_del_inet6(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet6_addr *addr); + +/** 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_inet.c b/lib/libifconfig/libifconfig_inet.c --- a/lib/libifconfig/libifconfig_inet.c +++ b/lib/libifconfig/libifconfig_inet.c @@ -31,6 +31,10 @@ #include #include +#include +#include +#include +#include #include #include @@ -42,6 +46,9 @@ static const struct sockaddr_in NULL_SIN; +static int ifconfig_inet_exec_nl(ifconfig_handle_t *h, int action, + const char *ifname, const struct ifconfig_inet_addr *addr); + static int inet_prefixlen(const struct in_addr *addr) { @@ -94,3 +101,85 @@ return (0); } + +static int +ifconfig_inet_exec_nl(ifconfig_handle_t *h, int action, const char *ifname, + const struct ifconfig_inet_addr *addr) +{ + int ret = 0, nested_offset = 0; + struct snl_state ss; + struct snl_writer nw = { 0 }; + struct nlmsghdr *hdr; + struct ifaddrmsg *ifahdr; + 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, action); + ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg); + + ifahdr->ifa_family = AF_INET; + ifahdr->ifa_prefixlen = addr->prefixlen; + + ifahdr->ifa_index = if_nametoindex(ifname); + if (ifahdr->ifa_index == 0) { + ifconfig_error(h, OTHER, EADDRNOTAVAIL); + ret = -1; + goto out; + } + + if (addr->sin != NULL) + snl_add_msg_attr_ip4(&nw, IFA_LOCAL, &addr->sin->sin_addr); + if (addr->dst != NULL) + snl_add_msg_attr_ip4(&nw, IFA_ADDRESS, &addr->dst->sin_addr); + if (addr->broadcast != NULL) + snl_add_msg_attr_ip4(&nw, IFA_BROADCAST, + &addr->broadcast->sin_addr); + + nested_offset = snl_add_msg_attr_nested(&nw, IFA_FREEBSD); + if (addr->vhid != 0) + snl_add_msg_attr_u32(&nw, IFAF_VHID, addr->vhid); + snl_end_attr_nested(&nw, nested_offset); + + 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); +} + +int +ifconfig_add_inet(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet_addr *addr) +{ + return (ifconfig_inet_exec_nl(h, NL_RTM_NEWADDR, ifname, addr)); +} + +int +ifconfig_del_inet(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet_addr *addr) +{ + return (ifconfig_inet_exec_nl(h, NL_RTM_DELADDR, ifname, addr)); +} diff --git a/lib/libifconfig/libifconfig_inet6.c b/lib/libifconfig/libifconfig_inet6.c --- a/lib/libifconfig/libifconfig_inet6.c +++ b/lib/libifconfig/libifconfig_inet6.c @@ -31,6 +31,10 @@ #include #include +#include +#include +#include +#include #include #include @@ -40,6 +44,8 @@ #include "libifconfig.h" #include "libifconfig_internal.h" +static int ifconfig_inet6_exec_nl(ifconfig_handle_t *h, int action, + const char *ifname, const struct ifconfig_inet6_addr *addr); static int inet6_prefixlen(struct in6_addr *addr) @@ -101,3 +107,89 @@ return (0); } + +static int +ifconfig_inet6_exec_nl(ifconfig_handle_t *h, int action, const char *ifname, + const struct ifconfig_inet6_addr *addr) +{ + int ret = 0, nested_offset = 0; + struct snl_state ss; + struct snl_writer nw = { 0 }; + struct nlmsghdr *hdr; + struct ifaddrmsg *ifahdr; + struct ifa_cacheinfo ci = { 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, action); + ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg); + + ifahdr->ifa_family = AF_INET6; + ifahdr->ifa_prefixlen = addr->prefixlen; + + ifahdr->ifa_index = if_nametoindex(ifname); + if (ifahdr->ifa_index == 0) { + ifconfig_error(h, OTHER, EADDRNOTAVAIL); + ret = -1; + goto out; + } + + if (addr->sin6 != NULL) + snl_add_msg_attr_ip6(&nw, IFA_LOCAL, &addr->sin6->sin6_addr); + if (addr->dstin6 != NULL) + snl_add_msg_attr_ip6(&nw, IFA_ADDRESS, + &addr->dstin6->sin6_addr); + + ci.ifa_prefered = addr->lifetime.ia6t_pltime; + ci.ifa_valid = addr->lifetime.ia6t_vltime; + snl_add_msg_attr(&nw, IFA_CACHEINFO, sizeof(ci), &ci); + + nested_offset = snl_add_msg_attr_nested(&nw, IFA_FREEBSD); + snl_add_msg_attr_u32(&nw, IFAF_FLAGS, addr->flags); + if (addr->vhid != 0) + snl_add_msg_attr_u32(&nw, IFAF_VHID, addr->vhid); + snl_end_attr_nested(&nw, nested_offset); + + 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); +} + +int +ifconfig_add_inet6(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet6_addr *addr) +{ + return (ifconfig_inet6_exec_nl(h, NL_RTM_NEWADDR, ifname, addr)); +} + +int +ifconfig_del_inet6(ifconfig_handle_t *h, const char *ifname, + const struct ifconfig_inet6_addr *addr) +{ + return (ifconfig_inet6_exec_nl(h, NL_RTM_DELADDR, ifname, 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,218 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025, Muhammad Saheed + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libifconfig.h" +#include "libifconfig_internal.h" + +static int ifconfig_modify_flags(ifconfig_handle_t *h, const char *ifname, + int ifi_flags, int ifi_change); + +static int +ifconfig_modify_flags(ifconfig_handle_t *h, const char *ifname, int ifi_flags, + int ifi_change) +{ + int ret = 0; + struct snl_state ss; + struct snl_writer nw; + struct nlmsghdr *hdr; + struct ifinfomsg *ifi; + struct snl_errmsg_data e = { 0 }; + + if (!snl_init(&ss, NETLINK_ROUTE)) { + ifconfig_error(h, NETLINK, ENOTSUP); + return (-1); + } + + snl_init_writer(&ss, &nw); + hdr = snl_create_msg_request(&nw, NL_RTM_NEWLINK); + ifi = snl_reserve_msg_object(&nw, struct ifinfomsg); + snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname); + + ifi->ifi_flags = ifi_flags; + ifi->ifi_change = ifi_change; + + hdr = snl_finalize_msg(&nw); + if (hdr == 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); +} + +int +ifconfig_if_up(ifconfig_handle_t *h, const char *ifname) +{ + return (ifconfig_modify_flags(h, ifname, IFF_UP, IFF_UP)); +} + +int +ifconfig_if_down(ifconfig_handle_t *h, const char *ifname) +{ + return (ifconfig_modify_flags(h, ifname, ~IFF_UP, IFF_UP)); +} + +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); +} + +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 }; + + 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); + + ifi->ifi_type = ARPHRD_ETHER; + ifi->ifi_family = AF_UNSPEC; + + ifi->ifi_index = if_nametoindex(ifname); + if (ifi->ifi_index == 0) { + ifconfig_error(h, OTHER, EADDRNOTAVAIL); + ret = -1; + goto out; + } + + 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); +}