/* Copyright (C) 2010 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * This program 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 * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ /** * \file * * \author Eric Leblond * \author Victor Julien */ #include "suricata-common.h" #include "util-ioctl.h" #include "conf.h" #include "decode.h" #include "decode-sll.h" #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_LINUX_ETHTOOL_H #include #include #ifdef HAVE_LINUX_SOCKIOS_H #include #else #error "ethtool.h present but sockios.h is missing" #endif /* HAVE_LINUX_SOCKIOS_H */ #endif /* HAVE_LINUX_ETHTOOL_H */ #ifdef HAVE_NET_IF_H #include #endif #ifdef OS_WIN32 #include "win32-syscall.h" #endif /** * \brief output a majorant of hardware header length * * \param Name of a network interface */ static int GetIfaceMaxHWHeaderLength(const char *dev) { if ((!strcmp("eth", dev)) || (!strcmp("br", dev)) || (!strcmp("bond", dev)) || (!strcmp("wlan", dev)) || (!strcmp("tun", dev)) || (!strcmp("tap", dev)) || (!strcmp("lo", dev))) { /* Add possible VLAN tag or Qing headers */ return 8 + ETHERNET_HEADER_LEN; } if (!strcmp("ppp", dev)) return SLL_HEADER_LEN; /* SLL_HEADER_LEN is the biggest one and add possible VLAN tag and Qing headers */ return 8 + SLL_HEADER_LEN; } /** * \brief output the link MTU * * \param Name of link * \retval -1 in case of error, 0 if MTU can not be found */ int GetIfaceMTU(const char *dev) { #if defined SIOCGIFMTU struct ifreq ifr; int fd; (void)strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { return -1; } if (ioctl(fd, SIOCGIFMTU, (char *)&ifr) < 0) { SCLogWarning("Failure when trying to get MTU via ioctl for '%s': %s (%d)", dev, strerror(errno), errno); close(fd); return -1; } close(fd); SCLogInfo("%s: MTU %d", dev, ifr.ifr_mtu); return ifr.ifr_mtu; #elif defined OS_WIN32 return GetIfaceMTUWin32(dev); #else /* ioctl is not defined, let's pretend returning 0 is ok */ return 0; #endif } /** * \brief output max packet size for a link * * This does a best effort to find the maximum packet size * for the link. In case of uncertainty, it will output a * majorant to be sure avoid the cost of dynamic allocation. * * \param LiveDevice object * \retval 0 in case of error */ int GetIfaceMaxPacketSize(LiveDevice *ld) { if (ld == NULL) return 0; const char *dev = ld->dev; if ((dev == NULL) || strlen(dev) == 0) return 0; int mtu = GetIfaceMTU(dev); switch (mtu) { case 0: case -1: return 0; } ld->mtu = mtu; int ll_header = GetIfaceMaxHWHeaderLength(dev); return ll_header + mtu; } #ifdef SIOCGIFFLAGS /** * \brief Get interface flags. * \param ifname Interface name. * \return Interface flags or -1 on error */ int GetIfaceFlags(const char *ifname) { struct ifreq ifr; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { return -1; } memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) { SCLogError("%s: failed to get device flags: %s", ifname, strerror(errno)); close(fd); return -1; } close(fd); #ifdef OS_FREEBSD int flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16); return flags; #else return ifr.ifr_flags; #endif } #endif #ifdef SIOCSIFFLAGS /** * \brief Set interface flags. * \param ifname Interface name. * \param flags Flags to set. * \return Zero on success. */ int SetIfaceFlags(const char *ifname, int flags) { struct ifreq ifr; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { return -1; } memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); #ifdef OS_FREEBSD ifr.ifr_flags = flags & 0xffff; ifr.ifr_flagshigh = flags >> 16; #else ifr.ifr_flags = (uint16_t)flags; #endif if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) { SCLogError("%s: unable to set device flags: %s", ifname, strerror(errno)); close(fd); return -1; } close(fd); return 0; } #endif /* SIOCGIFFLAGS */ #ifdef SIOCGIFCAP int GetIfaceCaps(const char *ifname) { struct ifreq ifr; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { return -1; } memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(fd, SIOCGIFCAP, &ifr) == -1) { SCLogError("%s: unable to get device caps: %s", ifname, strerror(errno)); close(fd); return -1; } close(fd); return ifr.ifr_curcap; } #endif #ifdef SIOCSIFCAP int SetIfaceCaps(const char *ifname, int caps) { struct ifreq ifr; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { return -1; } memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ifr.ifr_reqcap = caps; if (ioctl(fd, SIOCSIFCAP, &ifr) == -1) { SCLogError("%s: unable to set caps: %s", ifname, strerror(errno)); close(fd); return -1; } close(fd); return 0; } #endif #if defined HAVE_LINUX_ETHTOOL_H && defined SIOCETHTOOL static int GetEthtoolValue(const char *dev, int cmd, uint32_t *value) { struct ifreq ifr; int fd; struct ethtool_value ethv; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { return -1; } (void)strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); ethv.cmd = cmd; ifr.ifr_data = (void *) ðv; if (ioctl(fd, SIOCETHTOOL, (char *)&ifr) < 0) { SCLogWarning("%s: failed to get SIOCETHTOOL ioctl: %s", dev, strerror(errno)); close(fd); return -1; } *value = ethv.data; close(fd); return 0; } static int SetEthtoolValue(const char *dev, int cmd, uint32_t value) { struct ifreq ifr; int fd; struct ethtool_value ethv; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { return -1; } (void)strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); ethv.cmd = cmd; ethv.data = value; ifr.ifr_data = (void *) ðv; if (ioctl(fd, SIOCETHTOOL, (char *)&ifr) < 0) { SCLogWarning("%s: failed to set SIOCETHTOOL ioctl: %s", dev, strerror(errno)); close(fd); return -1; } close(fd); return 0; } static int GetIfaceOffloadingLinux(const char *dev, int csum, int other) { int ret = 0; uint32_t value = 0; if (csum) { const char *rx = "unset", *tx = "unset"; int csum_ret = 0; #ifdef ETHTOOL_GRXCSUM if (GetEthtoolValue(dev, ETHTOOL_GRXCSUM, &value) == 0 && value != 0) { rx = "SET"; csum_ret = 1; } #endif #ifdef ETHTOOL_GTXCSUM if (GetEthtoolValue(dev, ETHTOOL_GTXCSUM, &value) == 0 && value != 0) { tx = "SET"; csum_ret = 1; } #endif if (csum_ret == 0) SCLogPerf("%s: NIC offloading: RX %s TX %s", dev, rx, tx); else { SCLogWarning("%s: NIC offloading: RX %s TX %s. Run: ethtool -K %s rx off tx off", dev, rx, tx, dev); ret = 1; } } if (other) { const char *lro = "unset", *gro = "unset", *tso = "unset", *gso = "unset"; const char *sg = "unset"; int other_ret = 0; #ifdef ETHTOOL_GGRO if (GetEthtoolValue(dev, ETHTOOL_GGRO, &value) == 0 && value != 0) { gro = "SET"; other_ret = 1; } #endif #ifdef ETHTOOL_GTSO if (GetEthtoolValue(dev, ETHTOOL_GTSO, &value) == 0 && value != 0) { tso = "SET"; other_ret = 1; } #endif #ifdef ETHTOOL_GGSO if (GetEthtoolValue(dev, ETHTOOL_GGSO, &value) == 0 && value != 0) { gso = "SET"; other_ret = 1; } #endif #ifdef ETHTOOL_GSG if (GetEthtoolValue(dev, ETHTOOL_GSG, &value) == 0 && value != 0) { sg = "SET"; other_ret = 1; } #endif #ifdef ETHTOOL_GFLAGS if (GetEthtoolValue(dev, ETHTOOL_GFLAGS, &value) == 0) { if (value & ETH_FLAG_LRO) { lro = "SET"; other_ret = 1; } } #endif if (other_ret == 0) { SCLogPerf("%s: NIC offloading: SG: %s, GRO: %s, LRO: %s, TSO: %s, GSO: %s", dev, sg, gro, lro, tso, gso); } else { SCLogWarning("%s: NIC offloading: SG: %s, GRO: %s, LRO: %s, TSO: %s, GSO: %s. Run: " "ethtool -K %s sg off gro off lro off tso off gso off", dev, sg, gro, lro, tso, gso, dev); ret = 1; } } return ret; } static int DisableIfaceOffloadingLinux(LiveDevice *ldev, int csum, int other) { int ret = 0; uint32_t value = 0; if (ldev == NULL) return -1; const char *dev = ldev->dev; if (csum) { #ifdef ETHTOOL_GRXCSUM if (GetEthtoolValue(dev, ETHTOOL_GRXCSUM, &value) == 0 && value != 0) { SCLogPerf("%s: disabling rxcsum offloading", dev); SetEthtoolValue(dev, ETHTOOL_SRXCSUM, 0); ldev->offload_orig |= OFFLOAD_FLAG_RXCSUM; } #endif #ifdef ETHTOOL_GTXCSUM if (GetEthtoolValue(dev, ETHTOOL_GTXCSUM, &value) == 0 && value != 0) { SCLogPerf("%s: disabling txcsum offloading", dev); SetEthtoolValue(dev, ETHTOOL_STXCSUM, 0); ldev->offload_orig |= OFFLOAD_FLAG_TXCSUM; } #endif } if (other) { #ifdef ETHTOOL_GGRO if (GetEthtoolValue(dev, ETHTOOL_GGRO, &value) == 0 && value != 0) { SCLogPerf("%s: disabling gro offloading", dev); SetEthtoolValue(dev, ETHTOOL_SGRO, 0); ldev->offload_orig |= OFFLOAD_FLAG_GRO; } #endif #ifdef ETHTOOL_GTSO if (GetEthtoolValue(dev, ETHTOOL_GTSO, &value) == 0 && value != 0) { SCLogPerf("%s: disabling tso offloading", dev); SetEthtoolValue(dev, ETHTOOL_STSO, 0); ldev->offload_orig |= OFFLOAD_FLAG_TSO; } #endif #ifdef ETHTOOL_GGSO if (GetEthtoolValue(dev, ETHTOOL_GGSO, &value) == 0 && value != 0) { SCLogPerf("%s: disabling gso offloading", dev); SetEthtoolValue(dev, ETHTOOL_SGSO, 0); ldev->offload_orig |= OFFLOAD_FLAG_GSO; } #endif #ifdef ETHTOOL_GSG if (GetEthtoolValue(dev, ETHTOOL_GSG, &value) == 0 && value != 0) { SCLogPerf("%s: disabling sg offloading", dev); SetEthtoolValue(dev, ETHTOOL_SSG, 0); ldev->offload_orig |= OFFLOAD_FLAG_SG; } #endif #ifdef ETHTOOL_GFLAGS if (GetEthtoolValue(dev, ETHTOOL_GFLAGS, &value) == 0) { if (value & ETH_FLAG_LRO) { SCLogPerf("%s: disabling lro offloading", dev); SetEthtoolValue(dev, ETHTOOL_SFLAGS, value & ~ETH_FLAG_LRO); ldev->offload_orig |= OFFLOAD_FLAG_LRO; } } #endif } return ret; } static int RestoreIfaceOffloadingLinux(LiveDevice *ldev) { if (ldev == NULL) return -1; const char *dev = ldev->dev; #ifdef ETHTOOL_GRXCSUM if (ldev->offload_orig & OFFLOAD_FLAG_RXCSUM) { SCLogPerf("%s: restoring rxcsum offloading", dev); SetEthtoolValue(dev, ETHTOOL_SRXCSUM, 1); } #endif #ifdef ETHTOOL_GTXCSUM if (ldev->offload_orig & OFFLOAD_FLAG_TXCSUM) { SCLogPerf("%s: restoring txcsum offloading", dev); SetEthtoolValue(dev, ETHTOOL_STXCSUM, 1); } #endif #ifdef ETHTOOL_GGRO if (ldev->offload_orig & OFFLOAD_FLAG_GRO) { SCLogPerf("%s: restoring gro offloading", dev); SetEthtoolValue(dev, ETHTOOL_SGRO, 1); } #endif #ifdef ETHTOOL_GTSO if (ldev->offload_orig & OFFLOAD_FLAG_TSO) { SCLogPerf("%s: restoring tso offloading", dev); SetEthtoolValue(dev, ETHTOOL_STSO, 1); } #endif #ifdef ETHTOOL_GGSO if (ldev->offload_orig & OFFLOAD_FLAG_GSO) { SCLogPerf("%s: restoring gso offloading", dev); SetEthtoolValue(dev, ETHTOOL_SGSO, 1); } #endif #ifdef ETHTOOL_GSG if (ldev->offload_orig & OFFLOAD_FLAG_SG) { SCLogPerf("%s: restoring sg offloading", dev); SetEthtoolValue(dev, ETHTOOL_SSG, 1); } #endif #ifdef ETHTOOL_GFLAGS if (ldev->offload_orig & OFFLOAD_FLAG_LRO) { uint32_t value = 0; if (GetEthtoolValue(dev, ETHTOOL_GFLAGS, &value) == 0) { SCLogPerf("%s: restoring lro offloading", dev); SetEthtoolValue(dev, ETHTOOL_SFLAGS, value & ETH_FLAG_LRO); } } #endif return 0; } #endif /* defined HAVE_LINUX_ETHTOOL_H && defined SIOCETHTOOL */ #ifdef SIOCGIFCAP static int GetIfaceOffloadingBSD(const char *ifname) { int ret = 0; int if_caps = GetIfaceCaps(ifname); if (if_caps == -1) { return -1; } SCLogDebug("if_caps %X", if_caps); if (if_caps & IFCAP_RXCSUM) { SCLogWarning("%s: RXCSUM activated can lead to capture problems. Run: ifconfig %s -rxcsum", ifname, ifname); ret = 1; } #ifdef IFCAP_TOE if (if_caps & (IFCAP_TSO|IFCAP_TOE|IFCAP_LRO)) { SCLogWarning("%s: TSO, TOE or LRO activated can lead to capture problems. Run: ifconfig %s " "-tso -toe -lro", ifname, ifname); ret = 1; } #else if (if_caps & (IFCAP_TSO|IFCAP_LRO)) { SCLogWarning( "%s: TSO or LRO activated can lead to capture problems. Run: ifconfig %s -tso -lro", ifname, ifname); ret = 1; } #endif return ret; } #endif #ifdef SIOCSIFCAP static int DisableIfaceOffloadingBSD(LiveDevice *ldev) { int ret = 0; if (ldev == NULL) return -1; const char *ifname = ldev->dev; int if_caps = GetIfaceCaps(ifname); int set_caps = if_caps; if (if_caps == -1) { return -1; } SCLogDebug("if_caps %X", if_caps); if (if_caps & IFCAP_RXCSUM) { SCLogPerf("%s: disabling rxcsum offloading", ifname); set_caps &= ~IFCAP_RXCSUM; } if (if_caps & IFCAP_TXCSUM) { SCLogPerf("%s: disabling txcsum offloading", ifname); set_caps &= ~IFCAP_TXCSUM; } #ifdef IFCAP_RXCSUM_IPV6 if (if_caps & IFCAP_RXCSUM_IPV6) { SCLogPerf("%s: disabling rxcsum6 offloading", ifname); set_caps &= ~IFCAP_RXCSUM_IPV6; } #endif #ifdef IFCAP_TXCSUM_IPV6 if (if_caps & IFCAP_TXCSUM_IPV6) { SCLogPerf("%s: disabling txcsum6 offloading", ifname); set_caps &= ~IFCAP_TXCSUM_IPV6; } #endif #ifdef IFCAP_TOE if (if_caps & (IFCAP_TSO|IFCAP_TOE|IFCAP_LRO)) { SCLogPerf("%s: disabling tso|toe|lro offloading", ifname); set_caps &= ~(IFCAP_TSO|IFCAP_LRO); } #else if (if_caps & (IFCAP_TSO|IFCAP_LRO)) { SCLogPerf("%s: disabling tso|lro offloading", ifname); set_caps &= ~(IFCAP_TSO|IFCAP_LRO); } #endif if (set_caps != if_caps) { if (if_caps & IFCAP_RXCSUM) ldev->offload_orig |= OFFLOAD_FLAG_RXCSUM; if (if_caps & IFCAP_TSO) ldev->offload_orig |= OFFLOAD_FLAG_TSO; #ifdef IFCAP_TOE if (if_caps & IFCAP_TOE) ldev->offload_orig |= OFFLOAD_FLAG_TOE; #endif if (if_caps & IFCAP_LRO) ldev->offload_orig |= OFFLOAD_FLAG_LRO; SetIfaceCaps(ifname, set_caps); } return ret; } static int RestoreIfaceOffloadingBSD(LiveDevice *ldev) { int ret = 0; if (ldev == NULL) return -1; const char *ifname = ldev->dev; int if_caps = GetIfaceCaps(ifname); int set_caps = if_caps; if (if_caps == -1) { return -1; } SCLogDebug("if_caps %X", if_caps); if (ldev->offload_orig & OFFLOAD_FLAG_RXCSUM) { SCLogPerf("%s: restoring rxcsum offloading", ifname); set_caps |= IFCAP_RXCSUM; } if (ldev->offload_orig & OFFLOAD_FLAG_TSO) { SCLogPerf("%s: restoring tso offloading", ifname); set_caps |= IFCAP_TSO; } #ifdef IFCAP_TOE if (ldev->offload_orig & OFFLOAD_FLAG_TOE) { SCLogPerf("%s: restoring toe offloading", ifname); set_caps |= IFCAP_TOE; } #endif if (ldev->offload_orig & OFFLOAD_FLAG_LRO) { SCLogPerf("%s: restoring lro offloading", ifname); set_caps |= IFCAP_LRO; } if (set_caps != if_caps) { SetIfaceCaps(ifname, set_caps); } return ret; } #endif /** * \brief output offloading status of the link * * Test interface for offloading features. If one of them is * activated then suricata mays received packets merge at reception. * The result is oversized packets and this may cause some serious * problem in some capture mode where the size of the packet is * limited (AF_PACKET in V2 more for example). * * \param Name of link * \param csum check if checksums are offloaded * \param other check if other things are offloaded: TSO, GRO, etc. * \retval -1 in case of error, 0 if none, 1 if some */ int GetIfaceOffloading(const char *dev, int csum, int other) { #if defined HAVE_LINUX_ETHTOOL_H && defined SIOCETHTOOL return GetIfaceOffloadingLinux(dev, csum, other); #elif defined SIOCGIFCAP return GetIfaceOffloadingBSD(dev); #elif defined OS_WIN32 return GetIfaceOffloadingWin32(dev, csum, other); #else return 0; #endif } int DisableIfaceOffloading(LiveDevice *dev, int csum, int other) { /* already set */ if (dev->offload_orig != 0) return 0; #if defined HAVE_LINUX_ETHTOOL_H && defined SIOCETHTOOL return DisableIfaceOffloadingLinux(dev, csum, other); #elif defined SIOCSIFCAP return DisableIfaceOffloadingBSD(dev); #elif defined OS_WIN32 return DisableIfaceOffloadingWin32(dev, csum, other); #else return 0; #endif } void RestoreIfaceOffloading(LiveDevice *dev) { if (dev->offload_orig != 0) { #if defined HAVE_LINUX_ETHTOOL_H && defined SIOCETHTOOL RestoreIfaceOffloadingLinux(dev); #elif defined SIOCSIFCAP RestoreIfaceOffloadingBSD(dev); #elif defined OS_WIN32 RestoreIfaceOffloadingWin32(dev); #endif } } int GetIfaceRSSQueuesNum(const char *dev) { #if defined HAVE_LINUX_ETHTOOL_H && defined ETHTOOL_GRXRINGS struct ifreq ifr; struct ethtool_rxnfc nfccmd; int fd; (void)strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { SCLogWarning("%s: failed to open socket for ioctl: %s", dev, strerror(errno)); return -1; } nfccmd.cmd = ETHTOOL_GRXRINGS; ifr.ifr_data = (void*) &nfccmd; if (ioctl(fd, SIOCETHTOOL, (char *)&ifr) < 0) { if (errno != ENOTSUP) { SCLogWarning("%s: failed get number of RSS queue ioctl: %s", dev, strerror(errno)); } close(fd); return 0; } close(fd); SCLogInfo("%s: RX RSS queues: %d", dev, (int)nfccmd.data); return (int)nfccmd.data; #else return 0; #endif }