diff options
Diffstat (limited to 'sbin/ifconfig/ifconfig_netlink.c')
-rw-r--r-- | sbin/ifconfig/ifconfig_netlink.c | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/sbin/ifconfig/ifconfig_netlink.c b/sbin/ifconfig/ifconfig_netlink.c new file mode 100644 index 000000000000..b5badfd585b8 --- /dev/null +++ b/sbin/ifconfig/ifconfig_netlink.c @@ -0,0 +1,495 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org> + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +#define _WANT_IFCAP_BIT_NAMES + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> + +#include <sys/bitcount.h> +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/types.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <net/ethernet.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_strings.h> +#include <net/if_types.h> +#include "ifconfig.h" +#include "ifconfig_netlink.h" + +static const char *IFFBITS[] = { + "UP", /* 00:0x1 IFF_UP*/ + "BROADCAST", /* 01:0x2 IFF_BROADCAST*/ + "DEBUG", /* 02:0x4 IFF_DEBUG*/ + "LOOPBACK", /* 03:0x8 IFF_LOOPBACK*/ + "POINTOPOINT", /* 04:0x10 IFF_POINTOPOINT*/ + "NEEDSEPOCH", /* 05:0x20 IFF_NEEDSEPOCH*/ + "RUNNING", /* 06:0x40 IFF_DRV_RUNNING*/ + "NOARP", /* 07:0x80 IFF_NOARP*/ + "PROMISC", /* 08:0x100 IFF_PROMISC*/ + "ALLMULTI", /* 09:0x200 IFF_ALLMULTI*/ + "DRV_OACTIVE", /* 10:0x400 IFF_DRV_OACTIVE*/ + "SIMPLEX", /* 11:0x800 IFF_SIMPLEX*/ + "LINK0", /* 12:0x1000 IFF_LINK0*/ + "LINK1", /* 13:0x2000 IFF_LINK1*/ + "LINK2", /* 14:0x4000 IFF_LINK2*/ + "MULTICAST", /* 15:0x8000 IFF_MULTICAST*/ + "CANTCONFIG", /* 16:0x10000 IFF_CANTCONFIG*/ + "PPROMISC", /* 17:0x20000 IFF_PPROMISC*/ + "MONITOR", /* 18:0x40000 IFF_MONITOR*/ + "STATICARP", /* 19:0x80000 IFF_STATICARP*/ + "STICKYARP", /* 20:0x100000 IFF_STICKYARP*/ + "DYING", /* 21:0x200000 IFF_DYING*/ + "RENAMING", /* 22:0x400000 IFF_RENAMING*/ + "PALLMULTI", /* 23:0x800000 IFF_PALLMULTI*/ + "LOWER_UP", /* 24:0x1000000 IFF_NETLINK_1*/ +}; + +static void +nl_init_socket(struct snl_state *ss) +{ + if (snl_init(ss, NETLINK_ROUTE)) + return; + + if (modfind("netlink") == -1 && errno == ENOENT) { + /* Try to load */ + if (kldload("netlink") == -1) + err(1, "netlink is not loaded and load attempt failed"); + if (snl_init(ss, NETLINK_ROUTE)) + return; + } + + err(1, "unable to open netlink socket"); +} + +int +ifconfig_nl(if_ctx *ctx, int iscreate, + const struct afswtch *uafp) +{ + struct snl_state ss = {}; + + nl_init_socket(&ss); + ctx->io_ss = &ss; + + int error = ifconfig_ioctl(ctx, iscreate, uafp); + + snl_free(&ss); + ctx->io_ss = NULL; + + return (error); +} + +struct ifa { + struct ifa *next; + uint32_t idx; + struct snl_parsed_addr addr; +}; + +struct iface { + struct snl_parsed_link link; + struct ifa *ifa; + uint32_t ifa_count; + uint32_t idx; +}; + +struct ifmap { + uint32_t size; + uint32_t count; + struct iface **ifaces; +}; + +/* + * Returns ifmap ifindex->snl_parsed_link. + * Memory is allocated using snl temporary buffers + */ +static struct ifmap * +prepare_ifmap(struct snl_state *ss, const char *ifname) +{ + struct snl_writer nw = {}; + + snl_init_writer(ss, &nw); + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK); + hdr->nlmsg_flags |= NLM_F_DUMP; + snl_reserve_msg_object(&nw, struct ifinfomsg); + if (ifname != NULL) + snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname); + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) + return (NULL); + + uint32_t nlmsg_seq = hdr->nlmsg_seq; + struct ifmap *ifmap = snl_allocz(ss, sizeof(*ifmap)); + struct snl_errmsg_data e = {}; + + while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) { + struct iface *iface = snl_allocz(ss, sizeof(*iface)); + + if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser, &iface->link)) + continue; + if (iface->link.ifi_index >= ifmap->size) { + size_t new_size = MAX(ifmap->size, 32); + + while (new_size <= iface->link.ifi_index + 1) + new_size *= 2; + + struct iface **ifaces= snl_allocz(ss, new_size * sizeof(void *)); + memcpy(ifaces, ifmap->ifaces, ifmap->size * sizeof(void *)); + ifmap->ifaces = ifaces; + ifmap->size = new_size; + } + ifmap->ifaces[iface->link.ifi_index] = iface; + ifmap->count++; + iface->idx = ifmap->count; + } + return (ifmap); +} + +uint32_t +if_nametoindex_nl(struct snl_state *ss, const char *ifname) +{ + struct snl_writer nw = {}; + struct snl_parsed_link_simple link = {}; + + snl_init_writer(ss, &nw); + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK); + snl_reserve_msg_object(&nw, struct ifinfomsg); + snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname); + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) + return (0); + + hdr = snl_read_reply(ss, hdr->nlmsg_seq); + if (hdr->nlmsg_type != NL_RTM_NEWLINK) + return (0); + if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, &link)) + return (0); + + return (link.ifi_index); +} + +ifType +convert_iftype(ifType iftype) +{ + switch (iftype) { + case IFT_IEEE8023ADLAG: + return (IFT_ETHER); + case IFT_INFINIBANDLAG: + return (IFT_INFINIBAND); + default: + return (iftype); + } +} + +static void +prepare_ifaddrs(struct snl_state *ss, struct ifmap *ifmap) +{ + struct snl_writer nw = {}; + + snl_init_writer(ss, &nw); + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETADDR); + hdr->nlmsg_flags |= NLM_F_DUMP; + snl_reserve_msg_object(&nw, struct ifaddrmsg); + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) + return; + + uint32_t nlmsg_seq = hdr->nlmsg_seq; + struct snl_errmsg_data e = {}; + uint32_t count = 0; + + while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) { + struct ifa *ifa = snl_allocz(ss, sizeof(*ifa)); + + if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_addr_parser, &ifa->addr)) + continue; + + const uint32_t ifindex = ifa->addr.ifa_index; + if (ifindex >= ifmap->size || ifmap->ifaces[ifindex] == NULL) + continue; + struct iface *iface = ifmap->ifaces[ifindex]; + ifa->next = iface->ifa; + ifa->idx = ++count; + iface->ifa = ifa; + iface->ifa_count++; + } +} + +static bool +match_iface(struct ifconfig_args *args, struct iface *iface) +{ + if_link_t *link = &iface->link; + + if (args->ifname != NULL && strcmp(args->ifname, link->ifla_ifname)) + return (false); + + if (!match_if_flags(args, link->ifi_flags)) + return (false); + + if (!group_member(link->ifla_ifname, args->matchgroup, args->nogroup)) + return (false); + + if (args->afp == NULL) + return (true); + + if (!strcmp(args->afp->af_name, "ether")) { + if (link->ifla_address == NULL) + return (false); + + struct sockaddr_dl sdl = { + .sdl_len = sizeof(struct sockaddr_dl), + .sdl_family = AF_LINK, + .sdl_type = convert_iftype(link->ifi_type), + .sdl_alen = NLA_DATA_LEN(link->ifla_address), + }; + return (match_ether(&sdl)); + } else if (args->afp->af_af == AF_LINK) + /* + * The rtnetlink(4) RTM_GETADDR does not list link level + * addresses, so latter cycle won't match anything. Short + * circuit on RTM_GETLINK has provided us an address. + */ + return (link->ifla_address != NULL); + + for (struct ifa *ifa = iface->ifa; ifa != NULL; ifa = ifa->next) { + if (args->afp->af_af == ifa->addr.ifa_family) + return (true); + } + + return (false); +} + +/* Sort according to the kernel-provided order */ +static int +cmp_iface(const void *_a, const void *_b) +{ + const struct iface *a = *((const void * const *)_a); + const struct iface *b = *((const void * const *)_b); + + return ((a->idx > b->idx) * 2 - 1); +} + +static int +cmp_ifaddr(const void *_a, const void *_b) +{ + const struct ifa *a = *((const void * const *)_a); + const struct ifa *b = *((const void * const *)_b); + + if (a->addr.ifa_family != b->addr.ifa_family) + return ((a->addr.ifa_family > b->addr.ifa_family) * 2 - 1); + return ((a->idx > b->idx) * 2 - 1); +} + +static void +sort_iface_ifaddrs(struct snl_state *ss, struct iface *iface) +{ + if (iface->ifa_count == 0) + return; + + struct ifa **sorted_ifaddrs = snl_allocz(ss, iface->ifa_count * sizeof(void *)); + struct ifa *ifa = iface->ifa; + + for (uint32_t i = 0; i < iface->ifa_count; i++) { + struct ifa *ifa_next = ifa->next; + + sorted_ifaddrs[i] = ifa; + ifa->next = NULL; + ifa = ifa_next; + } + qsort(sorted_ifaddrs, iface->ifa_count, sizeof(void *), cmp_ifaddr); + ifa = sorted_ifaddrs[0]; + iface->ifa = ifa; + for (uint32_t i = 1; i < iface->ifa_count; i++) { + ifa->next = sorted_ifaddrs[i]; + ifa = sorted_ifaddrs[i]; + } +} + +static void +print_ifcaps(if_ctx *ctx, if_link_t *link) +{ + uint32_t sz_u32 = roundup2(link->iflaf_caps.nla_bitset_size, 32) / 32; + + if (sz_u32 > 0) { + uint32_t *caps = link->iflaf_caps.nla_bitset_value; + + printf("\toptions=%x", caps[0]); + print_bits("IFCAPS", caps, sz_u32, ifcap_bit_names, nitems(ifcap_bit_names)); + putchar('\n'); + } + + if (ctx->args->supmedia && sz_u32 > 0) { + uint32_t *caps = link->iflaf_caps.nla_bitset_mask; + + printf("\tcapabilities=%x", caps[0]); + print_bits("IFCAPS", caps, sz_u32, ifcap_bit_names, nitems(ifcap_bit_names)); + putchar('\n'); + } +} + +static void +status_nl(if_ctx *ctx, struct iface *iface) +{ + if_link_t *link = &iface->link; + struct ifconfig_args *args = ctx->args; + char *drivername = NULL; + + printf("%s: ", link->ifla_ifname); + + printf("flags=%x", link->ifi_flags); + print_bits("IFF", &link->ifi_flags, 1, IFFBITS, nitems(IFFBITS)); + + print_metric(ctx); + printf(" mtu %d\n", link->ifla_mtu); + + if (link->ifla_ifalias != NULL) + printf("\tdescription: %s\n", link->ifla_ifalias); + + print_ifcaps(ctx, link); + tunnel_status(ctx); + + if (args->allfamilies | (args->afp != NULL && args->afp->af_af == AF_LINK)) { + /* Start with link-level */ + const struct afswtch *p = af_getbyfamily(AF_LINK); + if (p != NULL && link->ifla_address != NULL) + p->af_status(ctx, link, NULL); + } + + sort_iface_ifaddrs(ctx->io_ss, iface); + + for (struct ifa *ifa = iface->ifa; ifa != NULL; ifa = ifa->next) { + if (args->allfamilies) { + const struct afswtch *p = af_getbyfamily(ifa->addr.ifa_family); + + if (p != NULL) + p->af_status(ctx, link, &ifa->addr); + } else if (args->afp->af_af == ifa->addr.ifa_family) { + const struct afswtch *p = args->afp; + + p->af_status(ctx, link, &ifa->addr); + } + } + + /* TODO: convert to netlink */ + if (args->allfamilies) + af_other_status(ctx); + else if (args->afp->af_other_status != NULL) + args->afp->af_other_status(ctx); + + print_ifstatus(ctx); + if (args->drivername || args->verbose) { + if (ifconfig_get_orig_name(lifh, link->ifla_ifname, + &drivername) != 0) { + if (ifconfig_err_errtype(lifh) == OTHER) + fprintf(stderr, "get original name: %s\n", + strerror(ifconfig_err_errno(lifh))); + else + fprintf(stderr, + "get original name: error type %d\n", + ifconfig_err_errtype(lifh)); + exit_code = 1; + } + if (drivername != NULL) + printf("\tdrivername: %s\n", drivername); + free(drivername); + } + if (args->verbose > 0) + sfp_status(ctx); +} + +static int +get_local_socket(void) +{ + int s = socket(AF_LOCAL, SOCK_DGRAM, 0); + + if (s < 0) + err(1, "socket(family %u,SOCK_DGRAM)", AF_LOCAL); + return (s); +} + +void +list_interfaces_nl(struct ifconfig_args *args) +{ + struct snl_state ss = {}; + struct ifconfig_context _ctx = { + .args = args, + .io_s = get_local_socket(), + .io_ss = &ss, + }; + struct ifconfig_context *ctx = &_ctx; + + nl_init_socket(&ss); + + struct ifmap *ifmap = prepare_ifmap(&ss, args->ifname); + struct iface **sorted_ifaces = snl_allocz(&ss, ifmap->count * sizeof(void *)); + for (uint32_t i = 0, num = 0; i < ifmap->size; i++) { + if (ifmap->ifaces[i] != NULL) { + sorted_ifaces[num++] = ifmap->ifaces[i]; + if (num == ifmap->count) + break; + } + } + qsort(sorted_ifaces, ifmap->count, sizeof(void *), cmp_iface); + prepare_ifaddrs(&ss, ifmap); + + for (uint32_t i = 0, num = 0; i < ifmap->count; i++) { + struct iface *iface = sorted_ifaces[i]; + + if (!match_iface(args, iface)) + continue; + + ctx->ifname = iface->link.ifla_ifname; + + if (args->namesonly) { + if (num++ != 0) + printf(" "); + fputs(iface->link.ifla_ifname, stdout); + } else if (args->argc == 0) + status_nl(ctx, iface); + else + ifconfig_ioctl(ctx, 0, args->afp); + } + if (args->namesonly) + printf("\n"); + + close(ctx->io_s); + snl_free(&ss); +} + |