You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/src/source-netmap.c

1098 lines
32 KiB
C

/* 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);
char const *active_runmode = RunmodeGetActive();
if (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 */
/**
* @}
*/