diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
commit | 8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch) | |
tree | c7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/source-netmap.c | |
parent | 13d05bc8458758ee39cb829098241e89616717ee (diff) |
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/source-netmap.c')
-rw-r--r-- | framework/src/suricata/src/source-netmap.c | 1098 |
1 files changed, 1098 insertions, 0 deletions
diff --git a/framework/src/suricata/src/source-netmap.c b/framework/src/suricata/src/source-netmap.c new file mode 100644 index 00000000..73930118 --- /dev/null +++ b/framework/src/suricata/src/source-netmap.c @@ -0,0 +1,1098 @@ +/* Copyright (C) 2011-2014 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. + */ + +/** +* \defgroup netmap Netmap running mode +* +* @{ +*/ + +/** +* \file +* +* \author Aleksey Katargin <gureedo@gmail.com> +* +* Netmap socket acquisition support +* +*/ + +#include "suricata-common.h" +#include "config.h" +#include "suricata.h" +#include "decode.h" +#include "packet-queue.h" +#include "threads.h" +#include "threadvars.h" +#include "tm-queuehandlers.h" +#include "tm-modules.h" +#include "tm-threads.h" +#include "tm-threads-common.h" +#include "conf.h" +#include "util-debug.h" +#include "util-device.h" +#include "util-error.h" +#include "util-privs.h" +#include "util-optimize.h" +#include "util-checksum.h" +#include "util-ioctl.h" +#include "util-host-info.h" +#include "tmqh-packetpool.h" +#include "source-netmap.h" +#include "runmodes.h" + +#ifdef __SC_CUDA_SUPPORT__ + +#include "util-cuda.h" +#include "util-cuda-buffer.h" +#include "util-mpm-ac.h" +#include "util-cuda-handlers.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "util-cuda-vars.h" + +#endif /* __SC_CUDA_SUPPORT__ */ + +#ifdef HAVE_NETMAP + +#if HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#if HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#include <net/netmap_user.h> + +#endif /* HAVE_NETMAP */ + +extern int max_pending_packets; + +#ifndef HAVE_NETMAP + +TmEcode NoNetmapSupportExit(ThreadVars *, void *, void **); + +void TmModuleReceiveNetmapRegister (void) +{ + tmm_modules[TMM_RECEIVENETMAP].name = "ReceiveNetmap"; + tmm_modules[TMM_RECEIVENETMAP].ThreadInit = NoNetmapSupportExit; + tmm_modules[TMM_RECEIVENETMAP].Func = NULL; + tmm_modules[TMM_RECEIVENETMAP].ThreadExitPrintStats = NULL; + tmm_modules[TMM_RECEIVENETMAP].ThreadDeinit = NULL; + tmm_modules[TMM_RECEIVENETMAP].RegisterTests = NULL; + tmm_modules[TMM_RECEIVENETMAP].cap_flags = 0; + tmm_modules[TMM_RECEIVENETMAP].flags = TM_FLAG_RECEIVE_TM; +} + +/** +* \brief Registration Function for DecodeNetmap. +* \todo Unit tests are needed for this module. +*/ +void TmModuleDecodeNetmapRegister (void) +{ + tmm_modules[TMM_DECODENETMAP].name = "DecodeNetmap"; + tmm_modules[TMM_DECODENETMAP].ThreadInit = NoNetmapSupportExit; + tmm_modules[TMM_DECODENETMAP].Func = NULL; + tmm_modules[TMM_DECODENETMAP].ThreadExitPrintStats = NULL; + tmm_modules[TMM_DECODENETMAP].ThreadDeinit = NULL; + tmm_modules[TMM_DECODENETMAP].RegisterTests = NULL; + tmm_modules[TMM_DECODENETMAP].cap_flags = 0; + tmm_modules[TMM_DECODENETMAP].flags = TM_FLAG_DECODE_TM; +} + +/** +* \brief this function prints an error message and exits. +*/ +TmEcode NoNetmapSupportExit(ThreadVars *tv, void *initdata, void **data) +{ + SCLogError(SC_ERR_NO_NETMAP,"Error creating thread %s: you do not have " + "support for netmap enabled, please recompile " + "with --enable-netmap", tv->name); + exit(EXIT_FAILURE); +} + +#else /* We have NETMAP support */ + +#define max(a, b) (((a) > (b)) ? (a) : (b)) + +#define POLL_TIMEOUT 100 + +#if defined(__linux__) +#define POLL_EVENTS (POLLHUP|POLLRDHUP|POLLERR|POLLNVAL) +#else +#define POLL_EVENTS (POLLHUP|POLLERR|POLLNVAL) +#endif + +enum { + NETMAP_OK, + NETMAP_FAILURE, +}; + +enum { + NETMAP_FLAG_ZERO_COPY = 1, +}; + +/** + * \brief Netmap ring isntance. + */ +typedef struct NetmapRing +{ + int fd; + struct netmap_ring *rx; + struct netmap_ring *tx; + int dst_ring_from; + int dst_ring_to; + int dst_next_ring; + SCSpinlock tx_lock; +} NetmapRing; + +/** + * \brief Netmap device instance. + */ +typedef struct NetmapDevice_ +{ + char ifname[IFNAMSIZ]; + void *mem; + size_t memsize; + struct netmap_if *nif; + int rings_cnt; + int rx_rings_cnt; + int tx_rings_cnt; + /* hw rings + sw ring */ + NetmapRing *rings; + unsigned int ref; + SC_ATOMIC_DECLARE(unsigned int, threads_run); + TAILQ_ENTRY(NetmapDevice_) next; +} NetmapDevice; + +/** + * \brief Module thread local variables. + */ +typedef struct NetmapThreadVars_ +{ + /* receive inteface */ + NetmapDevice *ifsrc; + /* dst interface for IPS mode */ + NetmapDevice *ifdst; + + int src_ring_from; + int src_ring_to; + int thread_idx; + int flags; + struct bpf_program bpf_prog; + + /* internal shit */ + TmSlot *slot; + ThreadVars *tv; + LiveDevice *livedev; + + /* copy from config */ + int copy_mode; + ChecksumValidationMode checksum_mode; + + /* counters */ + uint64_t pkts; + uint64_t bytes; + uint64_t drops; + uint16_t capture_kernel_packets; + uint16_t capture_kernel_drops; + + +} NetmapThreadVars; + +typedef TAILQ_HEAD(NetmapDeviceList_, NetmapDevice_) NetmapDeviceList; + +static NetmapDeviceList netmap_devlist = TAILQ_HEAD_INITIALIZER(netmap_devlist); +static SCMutex netmap_devlist_lock = SCMUTEX_INITIALIZER; + +/** + * \brief Get interface flags. + * \param fd Network susbystem file descritor. + * \param ifname Inteface name. + * \return Interface flags or -1 on error + */ +static int NetmapGetIfaceFlags(int fd, const char *ifname) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Unable to get flags for iface \"%s\": %s", + ifname, strerror(errno)); + return -1; + } + +#ifdef OS_FREEBSD + int flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16); + return flags; +#else + return ifr.ifr_flags; +#endif +} + +/** + * \brief Set interface flags. + * \param fd Network susbystem file descritor. + * \param ifname Inteface name. + * \param flags Flags to set. + * \return Zero on success. + */ +static int NetmapSetIfaceFlags(int fd, const char *ifname, int flags) +{ + struct ifreq ifr; + + 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 = flags; +#endif + + if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Unable to set flags for iface \"%s\": %s", + ifname, strerror(errno)); + return -1; + } + + return 0; +} + +/** + * \brief Open interface in netmap mode. + * \param ifname Interface name. + * \param promisc Enable promiscuous mode. + * \param dev Pointer to requested netmap device instance. + * \param verbose Verbose error logging. + * \return Zero on success. + */ +static int NetmapOpen(char *ifname, int promisc, NetmapDevice **pdevice, int verbose) +{ + NetmapDevice *pdev = NULL; + struct nmreq nm_req; + + *pdevice = NULL; + + SCMutexLock(&netmap_devlist_lock); + + /* search interface in our already opened list */ + TAILQ_FOREACH(pdev, &netmap_devlist, next) { + if (strcmp(ifname, pdev->ifname) == 0) { + *pdevice = pdev; + pdev->ref++; + SCMutexUnlock(&netmap_devlist_lock); + return 0; + } + } + + /* not found, create new record */ + pdev = SCMalloc(sizeof(*pdev)); + if (unlikely(pdev == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed"); + goto error; + } + + memset(pdev, 0, sizeof(*pdev)); + SC_ATOMIC_INIT(pdev->threads_run); + strlcpy(pdev->ifname, ifname, sizeof(pdev->ifname)); + + /* open netmap */ + int fd = open("/dev/netmap", O_RDWR); + if (fd == -1) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Couldn't open netmap device, error %s", + strerror(errno)); + goto error_pdev; + } + + /* check interface is up */ + int if_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (if_fd < 0) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Couldn't create control socket for '%s' interface", + ifname); + goto error_fd; + } + int if_flags = NetmapGetIfaceFlags(if_fd, ifname); + if (if_flags == -1) { + if (verbose) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Can not access to interface '%s'", + ifname); + } + close(if_fd); + goto error_fd; + } + if ((if_flags & IFF_UP) == 0) { + if (verbose) { + SCLogError(SC_ERR_NETMAP_CREATE, "Interface '%s' is down", ifname); + } + close(if_fd); + goto error_fd; + } + if (promisc) { + if_flags |= IFF_PROMISC; + NetmapSetIfaceFlags(if_fd, ifname, if_flags); + } + close(if_fd); + + /* query netmap info */ + memset(&nm_req, 0, sizeof(nm_req)); + strlcpy(nm_req.nr_name, ifname, sizeof(nm_req.nr_name)); + nm_req.nr_version = NETMAP_API; + + if (ioctl(fd, NIOCGINFO, &nm_req) != 0) { + if (verbose) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Couldn't query netmap for %s, error %s", + ifname, strerror(errno)); + } + goto error_fd; + }; + + pdev->memsize = nm_req.nr_memsize; + pdev->rx_rings_cnt = nm_req.nr_rx_rings; + pdev->tx_rings_cnt = nm_req.nr_tx_rings; + pdev->rings_cnt = max(pdev->rx_rings_cnt, pdev->tx_rings_cnt); + + /* hw rings + sw ring */ + pdev->rings = SCMalloc(sizeof(*pdev->rings) * (pdev->rings_cnt + 1)); + if (unlikely(pdev->rings == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed"); + goto error_fd; + } + memset(pdev->rings, 0, sizeof(*pdev->rings) * (pdev->rings_cnt + 1)); + + /* open individual instance for each ring */ + int success_cnt = 0; + for (int i = 0; i <= pdev->rings_cnt; i++) { + NetmapRing *pring = &pdev->rings[i]; + pring->fd = open("/dev/netmap", O_RDWR); + if (pring->fd == -1) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Couldn't open netmap device: %s", + strerror(errno)); + break; + } + + if (i < pdev->rings_cnt) { + nm_req.nr_flags = NR_REG_ONE_NIC; + nm_req.nr_ringid = i | NETMAP_NO_TX_POLL; + } else { + nm_req.nr_flags = NR_REG_SW; + nm_req.nr_ringid = NETMAP_NO_TX_POLL; + } + if (ioctl(pring->fd, NIOCREGIF, &nm_req) != 0) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Couldn't register %s with netmap: %s", + ifname, strerror(errno)); + break; + } + + if (pdev->mem == NULL) { + pdev->mem = mmap(0, pdev->memsize, PROT_WRITE | PROT_READ, + MAP_SHARED, pring->fd, 0); + if (pdev->mem == MAP_FAILED) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Couldn't mmap netmap device: %s", + strerror(errno)); + break; + } + pdev->nif = NETMAP_IF(pdev->mem, nm_req.nr_offset); + } + + if ((i < pdev->rx_rings_cnt) || (i == pdev->rings_cnt)) { + pring->rx = NETMAP_RXRING(pdev->nif, i); + } + if ((i < pdev->tx_rings_cnt) || (i == pdev->rings_cnt)) { + pring->tx = NETMAP_TXRING(pdev->nif, i); + } + SCSpinInit(&pring->tx_lock, 0); + success_cnt++; + } + + if (success_cnt != (pdev->rings_cnt + 1)) { + for(int i = 0; i < success_cnt; i++) { + close(pdev->rings[i].fd); + } + if (pdev->mem) { + munmap(pdev->mem, pdev->memsize); + } + SCFree(pdev->rings); + goto error_fd; + } + + close(fd); + *pdevice = pdev; + + TAILQ_INSERT_TAIL(&netmap_devlist, pdev, next); + SCMutexUnlock(&netmap_devlist_lock); + + return 0; + +error_fd: + close(fd); +error_pdev: + SCFree(pdev); +error: + SCMutexUnlock(&netmap_devlist_lock); + return -1; +} + +/** + * \brief Close or dereference netmap device instance. + * \param pdev Netmap device instance. + * \return Zero on success. + */ +static int NetmapClose(NetmapDevice *dev) +{ + NetmapDevice *pdev, *tmp; + + SCMutexLock(&netmap_devlist_lock); + + TAILQ_FOREACH_SAFE(pdev, &netmap_devlist, next, tmp) { + if (pdev == dev) { + pdev->ref--; + if (!pdev->ref) { + munmap(pdev->mem, pdev->memsize); + for (int i = 0; i <= pdev->rings_cnt; i++) { + NetmapRing *pring = &pdev->rings[i]; + close(pring->fd); + SCSpinDestroy(&pring->tx_lock); + } + SCFree(pdev->rings); + TAILQ_REMOVE(&netmap_devlist, pdev, next); + SCFree(pdev); + } + SCMutexUnlock(&netmap_devlist_lock); + return 0; + } + } + + SCMutexUnlock(&netmap_devlist_lock); + return -1; +} + +/** + * \brief PcapDumpCounters + * \param ntv + */ +static inline void NetmapDumpCounters(NetmapThreadVars *ntv) +{ + StatsAddUI64(ntv->tv, ntv->capture_kernel_packets, ntv->pkts); + StatsAddUI64(ntv->tv, ntv->capture_kernel_drops, ntv->drops); + (void) SC_ATOMIC_ADD(ntv->livedev->drop, ntv->drops); + (void) SC_ATOMIC_ADD(ntv->livedev->pkts, ntv->pkts); + ntv->drops = 0; + ntv->pkts = 0; +} + +/** + * \brief Init function for ReceiveNetmap. + * \param tv pointer to ThreadVars + * \param initdata pointer to the interface passed from the user + * \param data pointer gets populated with NetmapThreadVars + */ +static TmEcode ReceiveNetmapThreadInit(ThreadVars *tv, void *initdata, void **data) +{ + SCEnter(); + NetmapIfaceConfig *aconf = initdata; + + if (initdata == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "initdata == NULL"); + SCReturnInt(TM_ECODE_FAILED); + } + + NetmapThreadVars *ntv = SCMalloc(sizeof(*ntv)); + if (unlikely(ntv == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed"); + goto error; + } + memset(ntv, 0, sizeof(*ntv)); + + ntv->tv = tv; + ntv->checksum_mode = aconf->checksum_mode; + ntv->copy_mode = aconf->copy_mode; + + ntv->livedev = LiveGetDevice(aconf->iface_name); + if (ntv->livedev == NULL) { + SCLogError(SC_ERR_INVALID_VALUE, "Unable to find Live device"); + goto error_ntv; + } + + if (NetmapOpen(aconf->iface, aconf->promisc, &ntv->ifsrc, 1) != 0) { + goto error_ntv; + } + + if (unlikely(!aconf->iface_sw && !ntv->ifsrc->rx_rings_cnt)) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Input interface '%s' does not have Rx rings", + aconf->iface_name); + goto error_src; + } + + if (unlikely(aconf->iface_sw && aconf->threads > 1)) { + SCLogError(SC_ERR_INVALID_VALUE, + "Interface '%s+'. " + "Thread count can't be greater than 1 for SW ring.", + aconf->iface_name); + goto error_src; + } else if (unlikely(aconf->threads > ntv->ifsrc->rx_rings_cnt)) { + SCLogError(SC_ERR_INVALID_VALUE, + "Thread count can't be greater than Rx ring count. " + "Configured %d threads for interface '%s' with %d Rx rings.", + aconf->threads, aconf->iface_name, ntv->ifsrc->rx_rings_cnt); + goto error_src; + } + + if (aconf->iface_sw) { + ntv->thread_idx = 0; + } else { + do { + ntv->thread_idx = SC_ATOMIC_GET(ntv->ifsrc->threads_run); + } while (SC_ATOMIC_CAS(&ntv->ifsrc->threads_run, ntv->thread_idx, ntv->thread_idx + 1) == 0); + } + + /* calculate thread rings binding */ + if (aconf->iface_sw) { + ntv->src_ring_from = ntv->src_ring_to = ntv->ifsrc->rings_cnt; + } else { + int tmp = (ntv->ifsrc->rx_rings_cnt + 1) / aconf->threads; + ntv->src_ring_from = ntv->thread_idx * tmp; + ntv->src_ring_to = ntv->src_ring_from + tmp - 1; + if (ntv->thread_idx == (aconf->threads - 1)) { + ntv->src_ring_to = ntv->ifsrc->rx_rings_cnt - 1; + } + } + SCLogDebug("netmap: %s thread:%d rings:%d-%d", aconf->iface_name, + ntv->thread_idx, ntv->src_ring_from, ntv->src_ring_to); + + if (aconf->copy_mode != NETMAP_COPY_MODE_NONE) { + if (NetmapOpen(aconf->out_iface, 0, &ntv->ifdst, 1) != 0) { + goto error_src; + } + + if (unlikely(!aconf->out_iface_sw && !ntv->ifdst->tx_rings_cnt)) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Output interface '%s' does not have Tx rings", + aconf->out_iface_name); + goto error_dst; + } + + /* calculate dst rings bindings */ + for (int i = ntv->src_ring_from; i <= ntv->src_ring_to; i++) { + NetmapRing *ring = &ntv->ifsrc->rings[i]; + if (aconf->out_iface_sw) { + ring->dst_ring_from = ring->dst_ring_to = ntv->ifdst->rings_cnt; + } else if (ntv->ifdst->tx_rings_cnt > ntv->ifsrc->rx_rings_cnt) { + int tmp = (ntv->ifdst->tx_rings_cnt + 1) / ntv->ifsrc->rx_rings_cnt; + ring->dst_ring_from = i * tmp; + ring->dst_ring_to = ring->dst_ring_from + tmp - 1; + if (i == (ntv->src_ring_to - 1)) { + ring->dst_ring_to = ntv->ifdst->tx_rings_cnt - 1; + } + } else { + ring->dst_ring_from = ring->dst_ring_to = + i % ntv->ifdst->tx_rings_cnt; + } + ring->dst_next_ring = ring->dst_ring_from; + + SCLogDebug("netmap: %s(%d)->%s(%d-%d)", + aconf->iface_name, i, aconf->out_iface_name, + ring->dst_ring_from, ring->dst_ring_to); + } + } + + /* basic counters */ + ntv->capture_kernel_packets = StatsRegisterCounter("capture.kernel_packets", + ntv->tv); + ntv->capture_kernel_drops = StatsRegisterCounter("capture.kernel_drops", + ntv->tv); + + /* enable zero-copy mode for workers runmode */ + char const *active_runmode = RunmodeGetActive(); + if ((aconf->copy_mode != NETMAP_COPY_MODE_NONE) && active_runmode + && !strcmp("workers", active_runmode)) { + if (likely(ntv->ifsrc->mem == ntv->ifdst->mem)) { + ntv->flags |= NETMAP_FLAG_ZERO_COPY; + SCLogInfo("Enabling zero copy mode for %s->%s", + aconf->iface_name, aconf->out_iface_name); + } else { + SCLogInfo("Unable to set zero copy mode for %s->%s", + aconf->iface_name, aconf->out_iface_name); + } + } + + if (aconf->bpf_filter) { + SCLogInfo("Using BPF '%s' on iface '%s'", + aconf->bpf_filter, ntv->ifsrc->ifname); + if (pcap_compile_nopcap(default_packet_size, /* snaplen_arg */ + LINKTYPE_ETHERNET, /* linktype_arg */ + &ntv->bpf_prog, /* program */ + aconf->bpf_filter, /* const char *buf */ + 1, /* optimize */ + PCAP_NETMASK_UNKNOWN /* mask */ + ) == -1) { + SCLogError(SC_ERR_NETMAP_CREATE, "Filter compilation failed."); + goto error_dst; + } + } + + if (GetIfaceOffloading(aconf->iface) == 1) { + SCLogWarning(SC_ERR_NETMAP_CREATE, + "Using mmap mode with GRO or LRO activated can lead to capture problems"); + } + + *data = (void *)ntv; + aconf->DerefFunc(aconf); + SCReturnInt(TM_ECODE_OK); + +error_dst: + if (aconf->copy_mode != NETMAP_COPY_MODE_NONE) { + NetmapClose(ntv->ifdst); + } +error_src: + NetmapClose(ntv->ifsrc); +error_ntv: + SCFree(ntv); +error: + aconf->DerefFunc(aconf); + SCReturnInt(TM_ECODE_FAILED); +} + +/** + * \brief Output packet to destination interface or drop. + * \param ntv Thread local variables. + * \param p Source packet. + */ +static TmEcode NetmapWritePacket(NetmapThreadVars *ntv, Packet *p) +{ + if (ntv->copy_mode == NETMAP_COPY_MODE_IPS) { + if (PACKET_TEST_ACTION(p, ACTION_DROP)) { + return TM_ECODE_OK; + } + } + + /* map src ring_id to dst ring_id */ + NetmapRing *rxring = &ntv->ifsrc->rings[p->netmap_v.ring_id]; + NetmapRing *txring = &ntv->ifdst->rings[p->netmap_v.dst_ring_id]; + + SCSpinLock(&txring->tx_lock); + + if (!nm_ring_space(txring->tx)) { + ntv->drops++; + SCSpinUnlock(&txring->tx_lock); + return TM_ECODE_FAILED; + } + + struct netmap_slot *ts = &txring->tx->slot[txring->tx->cur]; + + if (ntv->flags & NETMAP_FLAG_ZERO_COPY) { + struct netmap_slot *rs = &rxring->rx->slot[p->netmap_v.slot_id]; + + /* swap slot buffers */ + uint32_t tmp_idx; + tmp_idx = ts->buf_idx; + ts->buf_idx = rs->buf_idx; + rs->buf_idx = tmp_idx; + + ts->len = rs->len; + + ts->flags |= NS_BUF_CHANGED; + rs->flags |= NS_BUF_CHANGED; + } else { + unsigned char *slot_data = (unsigned char *)NETMAP_BUF(txring->tx, ts->buf_idx); + memcpy(slot_data, GET_PKT_DATA(p), GET_PKT_LEN(p)); + ts->len = GET_PKT_LEN(p); + ts->flags |= NS_BUF_CHANGED; + } + + txring->tx->head = txring->tx->cur = nm_ring_next(txring->tx, txring->tx->cur); + if ((ntv->flags & NETMAP_FLAG_ZERO_COPY) == 0) { + ioctl(txring->fd, NIOCTXSYNC, 0); + } + + SCSpinUnlock(&txring->tx_lock); + + return TM_ECODE_OK; +} + +/** + * \brief Packet release routine. + * \param p Packet. + */ +static void NetmapReleasePacket(Packet *p) +{ + NetmapThreadVars *ntv = (NetmapThreadVars *)p->netmap_v.ntv; + + /* Need to be in copy mode and need to detect early release + where Ethernet header could not be set (and pseudo packet) */ + if ((ntv->copy_mode != NETMAP_COPY_MODE_NONE) && !PKT_IS_PSEUDOPKT(p)) { + NetmapWritePacket(ntv, p); + } + + PacketFreeOrRelease(p); +} + +/** + * \brief Read packets from ring and pass them further. + * \param ntv Thread local variables. + * \param ring_id Ring id to read. + */ +static int NetmapRingRead(NetmapThreadVars *ntv, int ring_id) +{ + SCEnter(); + + NetmapRing *ring = &ntv->ifsrc->rings[ring_id]; + struct netmap_ring *rx = ring->rx; + uint32_t avail = nm_ring_space(rx); + uint32_t cur = rx->cur; + + while (likely(avail-- > 0)) { + struct netmap_slot *slot = &rx->slot[cur]; + unsigned char *slot_data = (unsigned char *)NETMAP_BUF(rx, slot->buf_idx); + + if (ntv->bpf_prog.bf_len) { + struct pcap_pkthdr pkthdr = { {0, 0}, slot->len, slot->len }; + if (pcap_offline_filter(&ntv->bpf_prog, &pkthdr, slot_data) == 0) { + /* rejected by bpf */ + cur = nm_ring_next(rx, cur); + continue; + } + } + + Packet *p = PacketGetFromQueueOrAlloc(); + if (unlikely(p == NULL)) { + SCReturnInt(NETMAP_FAILURE); + } + + PKT_SET_SRC(p, PKT_SRC_WIRE); + p->livedev = ntv->livedev; + p->datalink = LINKTYPE_ETHERNET; + p->ts = rx->ts; + ntv->pkts++; + ntv->bytes += slot->len; + + /* checksum validation */ + if (ntv->checksum_mode == CHECKSUM_VALIDATION_DISABLE) { + p->flags |= PKT_IGNORE_CHECKSUM; + } else if (ntv->checksum_mode == CHECKSUM_VALIDATION_AUTO) { + if (ntv->livedev->ignore_checksum) { + p->flags |= PKT_IGNORE_CHECKSUM; + } else if (ChecksumAutoModeCheck(ntv->pkts, + SC_ATOMIC_GET(ntv->livedev->pkts), + SC_ATOMIC_GET(ntv->livedev->invalid_checksums))) { + ntv->livedev->ignore_checksum = 1; + p->flags |= PKT_IGNORE_CHECKSUM; + } + } + + if (ntv->flags & NETMAP_FLAG_ZERO_COPY) { + if (PacketSetData(p, slot_data, slot->len) == -1) { + TmqhOutputPacketpool(ntv->tv, p); + SCReturnInt(NETMAP_FAILURE); + } + } else { + if (PacketCopyData(p, slot_data, slot->len) == -1) { + TmqhOutputPacketpool(ntv->tv, p); + SCReturnInt(NETMAP_FAILURE); + } + } + + p->ReleasePacket = NetmapReleasePacket; + p->netmap_v.ring_id = ring_id; + p->netmap_v.slot_id = cur; + p->netmap_v.dst_ring_id = ring->dst_next_ring; + p->netmap_v.ntv = ntv; + + if (ring->dst_ring_from != ring->dst_ring_to) { + ring->dst_next_ring++; + if (ring->dst_next_ring == ring->dst_ring_to) { + ring->dst_next_ring = ring->dst_ring_from; + } + } + + SCLogDebug("pktlen: %" PRIu32 " (pkt %p, pkt data %p)", + GET_PKT_LEN(p), p, GET_PKT_DATA(p)); + + if (TmThreadsSlotProcessPkt(ntv->tv, ntv->slot, p) != TM_ECODE_OK) { + TmqhOutputPacketpool(ntv->tv, p); + SCReturnInt(NETMAP_FAILURE); + } + + cur = nm_ring_next(rx, cur); + } + rx->head = rx->cur = cur; + + SCReturnInt(NETMAP_OK); +} + +/** + * \brief Main netmap reading loop function + */ +static TmEcode ReceiveNetmapLoop(ThreadVars *tv, void *data, void *slot) +{ + SCEnter(); + + TmSlot *s = (TmSlot *)slot; + NetmapThreadVars *ntv = (NetmapThreadVars *)data; + struct pollfd *fds; + int rings_count = ntv->src_ring_to - ntv->src_ring_from + 1; + + ntv->slot = s->slot_next; + + fds = SCMalloc(sizeof(*fds) * rings_count); + if (unlikely(fds == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed"); + SCReturnInt(TM_ECODE_FAILED); + } + + for (int i = 0; i < rings_count; i++) { + fds[i].fd = ntv->ifsrc->rings[ntv->src_ring_from + i].fd; + fds[i].events = POLLIN; + } + + for(;;) { + if (suricata_ctl_flags != 0) { + break; + } + + /* make sure we have at least one packet in the packet pool, + * to prevent us from alloc'ing packets at line rate */ + PacketPoolWait(); + + int r = poll(fds, rings_count, POLL_TIMEOUT); + + if (r < 0) { + /* error */ + if(errno != EINTR) + SCLogError(SC_ERR_NETMAP_READ, + "Error polling netmap from iface '%s': (%d" PRIu32 ") %s", + ntv->ifsrc->ifname, errno, strerror(errno)); + continue; + } else if (r == 0) { + /* no events, timeout */ + SCLogDebug("(%s:%d-%d) Poll timeout", ntv->ifsrc->ifname, + ntv->src_ring_from, ntv->src_ring_to); + continue; + } + + for (int i = 0; i < rings_count; i++) { + if (fds[i].revents & POLL_EVENTS) { + if (fds[i].revents & POLLERR) { + SCLogError(SC_ERR_NETMAP_READ, + "Error reading data from iface '%s': (%d" PRIu32 ") %s", + ntv->ifsrc->ifname, errno, strerror(errno)); + } else if (fds[i].revents & POLLNVAL) { + SCLogError(SC_ERR_NETMAP_READ, + "Invalid polling request"); + } + continue; + } + + if (likely(fds[i].revents & POLLIN)) { + int src_ring_id = ntv->src_ring_from + i; + NetmapRingRead(ntv, src_ring_id); + + if ((ntv->copy_mode != NETMAP_COPY_MODE_NONE) && + (ntv->flags & NETMAP_FLAG_ZERO_COPY)) { + + NetmapRing *src_ring = &ntv->ifsrc->rings[src_ring_id]; + + /* sync dst tx rings */ + for (int j = src_ring->dst_ring_from; j <= src_ring->dst_ring_to; j++) { + NetmapRing *dst_ring = &ntv->ifdst->rings[j]; + /* if locked, another loop already do sync */ + if (SCSpinTrylock(&dst_ring->tx_lock) == 0) { + ioctl(dst_ring->fd, NIOCTXSYNC, 0); + SCSpinUnlock(&dst_ring->tx_lock); + } + } + } + } + } + + NetmapDumpCounters(ntv); + StatsSyncCountersIfSignalled(tv); + } + + SCFree(fds); + StatsSyncCountersIfSignalled(tv); + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief This function prints stats to the screen at exit. + * \param tv pointer to ThreadVars + * \param data pointer that gets cast into NetmapThreadVars for ntv + */ +static void ReceiveNetmapThreadExitStats(ThreadVars *tv, void *data) +{ + SCEnter(); + NetmapThreadVars *ntv = (NetmapThreadVars *)data; + + NetmapDumpCounters(ntv); + SCLogInfo("(%s) Kernel: Packets %" PRIu64 ", dropped %" PRIu64 ", bytes %" PRIu64 "", + tv->name, + StatsGetLocalCounterValue(tv, ntv->capture_kernel_packets), + StatsGetLocalCounterValue(tv, ntv->capture_kernel_drops), + ntv->bytes); +} + +/** + * \brief + * \param tv + * \param data Pointer to NetmapThreadVars. + */ +static TmEcode ReceiveNetmapThreadDeinit(ThreadVars *tv, void *data) +{ + SCEnter(); + + NetmapThreadVars *ntv = (NetmapThreadVars *)data; + + if (ntv->ifsrc) { + NetmapClose(ntv->ifsrc); + ntv->ifsrc = NULL; + } + if (ntv->ifdst) { + NetmapClose(ntv->ifdst); + ntv->ifdst = NULL; + } + if (ntv->bpf_prog.bf_insns) { + pcap_freecode(&ntv->bpf_prog); + } + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief Prepare netmap decode thread. + * \param tv Thread local avariables. + * \param initdata Thread config. + * \param data Pointer to DecodeThreadVars placed here. + */ +static TmEcode DecodeNetmapThreadInit(ThreadVars *tv, void *initdata, void **data) +{ + SCEnter(); + DecodeThreadVars *dtv = NULL; + + dtv = DecodeThreadVarsAlloc(tv); + + if (dtv == NULL) + SCReturnInt(TM_ECODE_FAILED); + + DecodeRegisterPerfCounters(dtv, tv); + + *data = (void *)dtv; + +#ifdef __SC_CUDA_SUPPORT__ + if (CudaThreadVarsInit(&dtv->cuda_vars) < 0) + SCReturnInt(TM_ECODE_FAILED); +#endif + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief This function passes off to link type decoders. + * + * DecodeNetmap reads packets from the PacketQueue and passes + * them off to the proper link type decoder. + * + * \param t pointer to ThreadVars + * \param p pointer to the current packet + * \param data pointer that gets cast into NetmapThreadVars for ntv + * \param pq pointer to the current PacketQueue + * \param postpq + */ +static TmEcode DecodeNetmap(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq) +{ + SCEnter(); + + DecodeThreadVars *dtv = (DecodeThreadVars *)data; + + /* XXX HACK: flow timeout can call us for injected pseudo packets + * see bug: https://redmine.openinfosecfoundation.org/issues/1107 */ + if (p->flags & PKT_PSEUDO_STREAM_END) + SCReturnInt(TM_ECODE_OK); + + /* update counters */ + DecodeUpdatePacketCounters(tv, dtv, p); + + DecodeEthernet(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq); + + PacketDecodeFinalize(tv, dtv, p); + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief + * \param tv + * \param data Pointer to DecodeThreadVars. + */ +static TmEcode DecodeNetmapThreadDeinit(ThreadVars *tv, void *data) +{ + SCEnter(); + + if (data != NULL) + DecodeThreadVarsFree(tv, data); + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief Registration Function for RecieveNetmap. + */ +void TmModuleReceiveNetmapRegister(void) +{ + tmm_modules[TMM_RECEIVENETMAP].name = "ReceiveNetmap"; + tmm_modules[TMM_RECEIVENETMAP].ThreadInit = ReceiveNetmapThreadInit; + tmm_modules[TMM_RECEIVENETMAP].Func = NULL; + tmm_modules[TMM_RECEIVENETMAP].PktAcqLoop = ReceiveNetmapLoop; + tmm_modules[TMM_RECEIVENETMAP].ThreadExitPrintStats = ReceiveNetmapThreadExitStats; + tmm_modules[TMM_RECEIVENETMAP].ThreadDeinit = ReceiveNetmapThreadDeinit; + tmm_modules[TMM_RECEIVENETMAP].RegisterTests = NULL; + tmm_modules[TMM_RECEIVENETMAP].cap_flags = SC_CAP_NET_RAW; + tmm_modules[TMM_RECEIVENETMAP].flags = TM_FLAG_RECEIVE_TM; +} + +/** + * \brief Registration Function for DecodeNetmap. + */ +void TmModuleDecodeNetmapRegister(void) +{ + tmm_modules[TMM_DECODENETMAP].name = "DecodeNetmap"; + tmm_modules[TMM_DECODENETMAP].ThreadInit = DecodeNetmapThreadInit; + tmm_modules[TMM_DECODENETMAP].Func = DecodeNetmap; + tmm_modules[TMM_DECODENETMAP].ThreadExitPrintStats = NULL; + tmm_modules[TMM_DECODENETMAP].ThreadDeinit = DecodeNetmapThreadDeinit; + tmm_modules[TMM_DECODENETMAP].RegisterTests = NULL; + tmm_modules[TMM_DECODENETMAP].cap_flags = 0; + tmm_modules[TMM_DECODENETMAP].flags = TM_FLAG_DECODE_TM; +} + +#endif /* HAVE_NETMAP */ +/* eof */ +/** +* @} +*/ |