aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/suricata/src/source-netmap.c
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:21:41 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:21:41 -0700
commit8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch)
treec7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/source-netmap.c
parent13d05bc8458758ee39cb829098241e89616717ee (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.c1098
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 */
+/**
+* @}
+*/