/* * Common ioctl functions. * Copyright (C) 1997, 98 Kunihiro Ishiguro * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; see the file COPYING; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "linklist.h" #include "if.h" #include "prefix.h" #include "ioctl.h" #include "log.h" #include "privs.h" #include "lib_errors.h" #include "vty.h" #include "zebra/rib.h" #include "zebra/rt.h" #include "zebra/interface.h" #include "zebra/zebra_errors.h" #include "zebra/debug.h" #ifdef HAVE_BSD_LINK_DETECT #include #endif /* HAVE_BSD_LINK_DETECT*/ extern struct zebra_privs_t zserv_privs; /* clear and set interface name string */ void ifreq_set_name(struct ifreq *ifreq, struct interface *ifp) { strlcpy(ifreq->ifr_name, ifp->name, sizeof(ifreq->ifr_name)); } #ifndef HAVE_NETLINK /* call ioctl system call */ int if_ioctl(unsigned long request, caddr_t buffer) { int sock; int ret; int err = 0; frr_with_privs(&zserv_privs) { sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { zlog_err("Cannot create UDP socket: %s", safe_strerror(errno)); exit(1); } if ((ret = ioctl(sock, request, buffer)) < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } #endif /* call ioctl system call */ int vrf_if_ioctl(unsigned long request, caddr_t buffer, vrf_id_t vrf_id) { int sock; int ret; int err = 0; frr_with_privs(&zserv_privs) { sock = vrf_socket(AF_INET, SOCK_DGRAM, 0, vrf_id, NULL); if (sock < 0) { zlog_err("Cannot create UDP socket: %s", safe_strerror(errno)); exit(1); } ret = vrf_ioctl(vrf_id, sock, request, buffer); if (ret < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } #ifndef HAVE_NETLINK static int if_ioctl_ipv6(unsigned long request, caddr_t buffer) { int sock; int ret; int err = 0; frr_with_privs(&zserv_privs) { sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock < 0) { zlog_err("Cannot create IPv6 datagram socket: %s", safe_strerror(errno)); exit(1); } if ((ret = ioctl(sock, request, buffer)) < 0) err = errno; } close(sock); if (ret < 0) { errno = err; return ret; } return 0; } /* * get interface metric * -- if value is not avaliable set -1 */ void if_get_metric(struct interface *ifp) { #ifdef SIOCGIFMETRIC struct ifreq ifreq = {}; ifreq_set_name(&ifreq, ifp); if (vrf_if_ioctl(SIOCGIFMETRIC, (caddr_t)&ifreq, ifp->vrf->vrf_id) < 0) return; ifp->metric = ifreq.ifr_metric; if (ifp->metric == 0) ifp->metric = 1; #else /* SIOCGIFMETRIC */ ifp->metric = -1; #endif /* SIOCGIFMETRIC */ } /* get interface MTU */ void if_get_mtu(struct interface *ifp) { struct ifreq ifreq = {}; ifreq_set_name(&ifreq, ifp); #if defined(SIOCGIFMTU) if (vrf_if_ioctl(SIOCGIFMTU, (caddr_t)&ifreq, ifp->vrf->vrf_id) < 0) { zlog_info("Can't lookup mtu by ioctl(SIOCGIFMTU) for %s(%u)", ifp->name, ifp->vrf->vrf_id); ifp->mtu6 = ifp->mtu = -1; return; } ifp->mtu6 = ifp->mtu = ifreq.ifr_mtu; /* propogate */ zebra_interface_up_update(ifp); #else zlog_info("Can't lookup mtu on this system for %s(%u)", ifp->name, ifp->vrf->vrf_id); ifp->mtu6 = ifp->mtu = -1; #endif } #endif /* ! HAVE_NETLINK */ /* * Handler for interface address programming via the zebra dplane, * for non-netlink platforms. This handler dispatches to per-platform * helpers, based on the operation requested. */ #ifndef HAVE_NETLINK /* Prototypes: these are placed in this block so that they're only seen * on non-netlink platforms. */ static int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx); static int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx); static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx); static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx); enum zebra_dplane_result kernel_address_update_ctx( struct zebra_dplane_ctx *ctx) { int ret = -1; const struct prefix *p; p = dplane_ctx_get_intf_addr(ctx); if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_INSTALL) { if (p->family == AF_INET) ret = if_set_prefix_ctx(ctx); else ret = if_set_prefix6_ctx(ctx); } else if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_UNINSTALL) { if (p->family == AF_INET) ret = if_unset_prefix_ctx(ctx); else ret = if_unset_prefix6_ctx(ctx); } else { if (IS_ZEBRA_DEBUG_DPLANE) zlog_debug("Invalid op in interface-addr install"); } return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); } #ifdef HAVE_STRUCT_IFALIASREQ /* * Helper for interface-addr install, non-netlink */ static int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifaliasreq addreq; struct sockaddr_in addr, mask, peer; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(addr)); addr.sin_addr = p->prefix; addr.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in)); if (dplane_ctx_intf_is_connected(ctx)) { p = (struct prefix_ipv4 *)dplane_ctx_get_intf_dest(ctx); memset(&mask, 0, sizeof(mask)); peer.sin_addr = p->prefix; peer.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN peer.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_broadaddr, &peer, sizeof(struct sockaddr_in)); } memset(&mask, 0, sizeof(mask)); masklen2ip(p->prefixlen, &mask.sin_addr); mask.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_mask, &mask, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCAIFADDR, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } /* * Helper for interface-addr un-install, non-netlink */ static int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifaliasreq addreq; struct sockaddr_in addr, mask, peer; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(addr)); addr.sin_addr = p->prefix; addr.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in)); if (dplane_ctx_intf_is_connected(ctx)) { p = (struct prefix_ipv4 *)dplane_ctx_get_intf_dest(ctx); memset(&mask, 0, sizeof(mask)); peer.sin_addr = p->prefix; peer.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN peer.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_broadaddr, &peer, sizeof(struct sockaddr_in)); } memset(&mask, 0, sizeof(mask)); masklen2ip(p->prefixlen, &mask.sin_addr); mask.sin_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin_len = sizeof(struct sockaddr_in); #endif memcpy(&addreq.ifra_mask, &mask, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCDIFADDR, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } #else /* Set up interface's address, netmask (and broadcas? ). Linux or Solaris uses ifname:number semantics to set IP address aliases. */ int if_set_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifreq ifreq; struct sockaddr_in addr; struct sockaddr_in broad; struct sockaddr_in mask; struct prefix_ipv4 ifaddr; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); ifaddr = *p; strlcpy(ifreq.ifr_name, dplane_ctx_get_ifname(ctx), sizeof(ifreq.ifr_name)); addr.sin_addr = p->prefix; addr.sin_family = p->family; memcpy(&ifreq.ifr_addr, &addr, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFADDR, (caddr_t)&ifreq); if (ret < 0) return ret; /* We need mask for make broadcast addr. */ masklen2ip(p->prefixlen, &mask.sin_addr); if (dplane_ctx_intf_is_broadcast(ctx)) { apply_mask_ipv4(&ifaddr); addr.sin_addr = ifaddr.prefix; broad.sin_addr.s_addr = (addr.sin_addr.s_addr | ~mask.sin_addr.s_addr); broad.sin_family = p->family; memcpy(&ifreq.ifr_broadaddr, &broad, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFBRDADDR, (caddr_t)&ifreq); if (ret < 0) return ret; } mask.sin_family = p->family; memcpy(&ifreq.ifr_addr, &mask, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFNETMASK, (caddr_t)&ifreq); if (ret < 0) return ret; return 0; } /* Set up interface's address, netmask (and broadcas? ). Linux or Solaris uses ifname:number semantics to set IP address aliases. */ int if_unset_prefix_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct ifreq ifreq; struct sockaddr_in addr; struct prefix_ipv4 *p; p = (struct prefix_ipv4 *)dplane_ctx_get_intf_addr(ctx); strlcpy(ifreq.ifr_name, dplane_ctx_get_ifname(ctx), sizeof(ifreq.ifr_name)); memset(&addr, 0, sizeof(addr)); addr.sin_family = p->family; memcpy(&ifreq.ifr_addr, &addr, sizeof(struct sockaddr_in)); ret = if_ioctl(SIOCSIFADDR, (caddr_t)&ifreq); if (ret < 0) return ret; return 0; } #endif /* HAVE_STRUCT_IFALIASREQ */ #endif /* HAVE_NETLINK */ /* get interface flags */ void if_get_flags(struct interface *ifp) { int ret; struct ifreq ifreqflags = {}; struct ifreq ifreqdata = {}; ifreq_set_name(&ifreqflags, ifp); ifreq_set_name(&ifreqdata, ifp); ret = vrf_if_ioctl(SIOCGIFFLAGS, (caddr_t)&ifreqflags, ifp->vrf->vrf_id); if (ret < 0) { flog_err_sys(EC_LIB_SYSTEM_CALL, "vrf_if_ioctl(SIOCGIFFLAGS %s) failed: %s", ifp->name, safe_strerror(errno)); return; } if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION)) goto out; /* Per-default, IFF_RUNNING is held high, unless link-detect * says otherwise - we abuse IFF_RUNNING inside zebra as a * link-state flag, following practice on Linux and Solaris * kernels */ #ifdef SIOCGIFDATA /* * BSD gets link state from ifi_link_link in struct if_data. * All BSD's have this in getifaddrs(3) ifa_data for AF_LINK * addresses. We can also access it via SIOCGIFDATA. */ #ifdef __NetBSD__ struct ifdatareq ifdr = {.ifdr_data.ifi_link_state = 0}; struct if_data *ifdata = &ifdr.ifdr_data; strlcpy(ifdr.ifdr_name, ifp->name, sizeof(ifdr.ifdr_name)); ret = vrf_if_ioctl(SIOCGIFDATA, (caddr_t)&ifdr, ifp->vrf->vrf_id); #else struct if_data ifd = {.ifi_link_state = 0}; struct if_data *ifdata = &ifd; ifreqdata.ifr_data = (caddr_t)ifdata; ret = vrf_if_ioctl(SIOCGIFDATA, (caddr_t)&ifreqdata, ifp->vrf->vrf_id); #endif if (ret == -1) /* Very unlikely. Did the interface disappear? */ flog_err_sys(EC_LIB_SYSTEM_CALL, "if_ioctl(SIOCGIFDATA %s) failed: %s", ifp->name, safe_strerror(errno)); else { if (ifdata->ifi_link_state >= LINK_STATE_UP) SET_FLAG(ifreqflags.ifr_flags, IFF_RUNNING); else if (ifdata->ifi_link_state == LINK_STATE_UNKNOWN) /* BSD traditionally treats UNKNOWN as UP */ SET_FLAG(ifreqflags.ifr_flags, IFF_RUNNING); else UNSET_FLAG(ifreqflags.ifr_flags, IFF_RUNNING); } #elif defined(HAVE_BSD_LINK_DETECT) /* * This is only needed for FreeBSD older than FreeBSD-13. * Valid and active media generally means the link state is * up, but this is not always the case. * For example, some BSD's with a net80211 interface in MONITOR * mode will treat the media as valid and active but the * link state is down - because we cannot send anything. * Also, virtual interfaces such as PPP, VLAN, etc generally * don't support media at all, so the ioctl will just fail. */ struct ifmediareq ifmr = {.ifm_status = 0}; strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name)); if (if_ioctl(SIOCGIFMEDIA, (caddr_t)&ifmr) == -1) { if (errno != EINVAL) flog_err_sys(EC_LIB_SYSTEM_CALL, "if_ioctl(SIOCGIFMEDIA %s) failed: %s", ifp->name, safe_strerror(errno)); } else if (ifmr.ifm_status & IFM_AVALID) { /* media state is valid */ if (ifmr.ifm_status & IFM_ACTIVE) /* media is active */ SET_FLAG(ifreqflags.ifr_flags, IFF_RUNNING); else UNSET_FLAG(ifreqflags.ifr_flags, IFF_RUNNING); } #endif /* HAVE_BSD_LINK_DETECT */ out: if_flags_update(ifp, (ifreqflags.ifr_flags & 0x0000ffff)); } /* Set interface flags */ int if_set_flags(struct interface *ifp, uint64_t flags) { int ret; struct ifreq ifreq; memset(&ifreq, 0, sizeof(ifreq)); ifreq_set_name(&ifreq, ifp); ifreq.ifr_flags = ifp->flags; ifreq.ifr_flags |= flags; ret = vrf_if_ioctl(SIOCSIFFLAGS, (caddr_t)&ifreq, ifp->vrf->vrf_id); if (ret < 0) { zlog_info("can't set interface %s(%u) flags %" PRIu64, ifp->name, ifp->vrf->vrf_id, flags); return ret; } return 0; } /* Unset interface's flag. */ int if_unset_flags(struct interface *ifp, uint64_t flags) { int ret; struct ifreq ifreq; memset(&ifreq, 0, sizeof(ifreq)); ifreq_set_name(&ifreq, ifp); ifreq.ifr_flags = ifp->flags; ifreq.ifr_flags &= ~flags; ret = vrf_if_ioctl(SIOCSIFFLAGS, (caddr_t)&ifreq, ifp->vrf->vrf_id); if (ret < 0) { zlog_warn("can't unset interface %s(%u) flags %" PRIu64, ifp->name, ifp->vrf->vrf_id, flags); return ret; } return 0; } #ifndef LINUX_IPV6 /* Netlink has its own code */ #ifdef HAVE_STRUCT_IN6_ALIASREQ #ifndef ND6_INFINITE_LIFETIME #define ND6_INFINITE_LIFETIME 0xffffffffL #endif /* ND6_INFINITE_LIFETIME */ /* * Helper for interface-addr install, non-netlink */ static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct in6_aliasreq addreq; struct sockaddr_in6 addr; struct sockaddr_in6 mask; struct prefix_ipv6 *p; p = (struct prefix_ipv6 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(addr)); addr.sin6_addr = p->prefix; addr.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in6)); memset(&mask, 0, sizeof(mask)); masklen2ip6(p->prefixlen, &mask.sin6_addr); mask.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_prefixmask, &mask, sizeof(struct sockaddr_in6)); addreq.ifra_lifetime.ia6t_vltime = 0xffffffff; addreq.ifra_lifetime.ia6t_pltime = 0xffffffff; #ifdef HAVE_STRUCT_IF6_ALIASREQ_IFRA_LIFETIME addreq.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; addreq.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; #endif ret = if_ioctl_ipv6(SIOCAIFADDR_IN6, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } /* * Helper for interface-addr un-install, non-netlink */ static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { int ret; struct in6_aliasreq addreq; struct sockaddr_in6 addr; struct sockaddr_in6 mask; struct prefix_ipv6 *p; p = (struct prefix_ipv6 *)dplane_ctx_get_intf_addr(ctx); memset(&addreq, 0, sizeof(addreq)); strlcpy((char *)&addreq.ifra_name, dplane_ctx_get_ifname(ctx), sizeof(addreq.ifra_name)); memset(&addr, 0, sizeof(addr)); addr.sin6_addr = p->prefix; addr.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN addr.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_addr, &addr, sizeof(struct sockaddr_in6)); memset(&mask, 0, sizeof(mask)); masklen2ip6(p->prefixlen, &mask.sin6_addr); mask.sin6_family = p->family; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN mask.sin6_len = sizeof(struct sockaddr_in6); #endif memcpy(&addreq.ifra_prefixmask, &mask, sizeof(struct sockaddr_in6)); #ifdef HAVE_STRUCT_IF6_ALIASREQ_IFRA_LIFETIME addreq.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; addreq.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; #endif ret = if_ioctl_ipv6(SIOCDIFADDR_IN6, (caddr_t)&addreq); if (ret < 0) return ret; return 0; } #else /* The old, pre-dataplane code here just returned, so we're retaining that * choice. */ static int if_set_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { return 0; } static int if_unset_prefix6_ctx(const struct zebra_dplane_ctx *ctx) { return 0; } #endif /* HAVE_STRUCT_IN6_ALIASREQ */ #endif /* LINUX_IPV6 */