From 10104066e17a59769467cc5350eac59ee37404f1 Mon Sep 17 00:00:00 2001 From: gureedo Date: Sat, 13 Dec 2014 16:12:40 +0500 Subject: [PATCH] netmap support --- configure.ac | 17 + src/Makefile.am | 2 + src/decode.h | 4 + src/runmode-netmap.c | 439 ++++++++++++++++++ src/runmode-netmap.h | 33 ++ src/runmodes.c | 10 + src/runmodes.h | 2 + src/source-netmap.c | 999 ++++++++++++++++++++++++++++++++++++++++ src/source-netmap.h | 62 +++ src/suricata.c | 62 +++ src/tm-modules.c | 2 + src/tm-threads-common.h | 2 + src/util-error.c | 3 + src/util-error.h | 3 + suricata.yaml.in | 33 ++ 15 files changed, 1673 insertions(+) create mode 100644 src/runmode-netmap.c create mode 100644 src/runmode-netmap.h create mode 100644 src/source-netmap.c create mode 100644 src/source-netmap.h diff --git a/configure.ac b/configure.ac index fb4dfefe70..0cbba44771 100644 --- a/configure.ac +++ b/configure.ac @@ -1055,6 +1055,22 @@ [[#include ]]) ]) + # Netmap support + AC_ARG_ENABLE(netmap, + AS_HELP_STRING([--enable-netmap], [Enable Netmap support]),,[enable_netmap=no]) + AC_ARG_WITH(netmap_includes, + [ --with-netmap-includes=DIR netmap include directory], + [with_netmap_includes="$withval"],[with_netmap_includes=no]) + + AS_IF([test "x$enable_netmap" = "xyes"], [ + CFLAGS="$CFLAGS -DHAVE_NETMAP" + + if test "$with_netmap_includes" != "no"; then + CPPFLAGS="${CPPFLAGS} -I${with_netmap_includes}" + fi + + AC_CHECK_HEADER(net/netmap_user.h,,[AC_ERROR(net/netmap_user.h not found ...)],) + ]) # libhtp AC_ARG_ENABLE(non-bundled-htp, @@ -1717,6 +1733,7 @@ SURICATA_BUILD_CONF="Suricata Configuration: NFQueue support: ${enable_nfqueue} NFLOG support: ${enable_nflog} IPFW support: ${enable_ipfw} + Netmap support: ${enable_netmap} DAG enabled: ${enable_dag} Napatech enabled: ${enable_napatech} Unix socket enabled: ${enable_unixsocket} diff --git a/src/Makefile.am b/src/Makefile.am index d60f56c4b9..582247e762 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -249,6 +249,7 @@ runmode-erf-dag.c runmode-erf-dag.h \ runmode-erf-file.c runmode-erf-file.h \ runmode-ipfw.c runmode-ipfw.h \ runmode-napatech.c runmode-napatech.h \ +runmode-netmap.c runmode-netmap.h \ runmode-nfq.c runmode-nfq.h \ runmode-nflog.c runmode-nflog.h \ runmode-pcap.c runmode-pcap.h \ @@ -264,6 +265,7 @@ source-erf-file.c source-erf-file.h \ source-ipfw.c source-ipfw.h \ source-mpipe.c source-mpipe.h \ source-napatech.c source-napatech.h \ +source-netmap.c source-netmap.h \ source-nfq.c source-nfq.h \ source-nflog.c source-nflog.h \ source-pcap.c source-pcap.h \ diff --git a/src/decode.h b/src/decode.h index bd9aeef61b..9b16e036cc 100644 --- a/src/decode.h +++ b/src/decode.h @@ -61,6 +61,7 @@ enum PktSrcEnum { #include "source-pcap.h" #include "source-af-packet.h" #include "source-mpipe.h" +#include "source-netmap.h" #include "action-globals.h" @@ -414,6 +415,9 @@ typedef struct Packet_ /* tilegx mpipe stuff */ MpipePacketVars mpipe_v; #endif +#ifdef HAVE_NETMAP + NetmapPacketVars netmap_v; +#endif /** libpcap vars: shared by Pcap Live mode and Pcap File mode */ PcapPacketVars pcap_v; diff --git a/src/runmode-netmap.c b/src/runmode-netmap.c new file mode 100644 index 0000000000..f5172aba1a --- /dev/null +++ b/src/runmode-netmap.c @@ -0,0 +1,439 @@ +/* Copyright (C) 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. + */ + +/** +* \ingroup netmap +* +* @{ +*/ + +/** +* \file +* +* \author Aleksey Katargin +* +* Netmap runmode +* +*/ + +#include "suricata-common.h" +#include "config.h" +#include "tm-threads.h" +#include "conf.h" +#include "runmodes.h" +#include "runmode-netmap.h" +#include "log-httplog.h" +#include "output.h" +#include "detect-engine-mpm.h" + +#include "alert-fastlog.h" +#include "alert-prelude.h" +#include "alert-unified2-alert.h" +#include "alert-debuglog.h" + +#include "util-debug.h" +#include "util-time.h" +#include "util-cpu.h" +#include "util-affinity.h" +#include "util-device.h" +#include "util-runmodes.h" +#include "util-ioctl.h" + +#include "source-netmap.h" + +extern int max_pending_packets; + +static const char *default_mode_workers = NULL; + +const char *RunModeNetmapGetDefaultMode(void) +{ + return default_mode_workers; +} + +void RunModeIdsNetmapRegister(void) +{ + RunModeRegisterNewRunMode(RUNMODE_NETMAP, "single", + "Single threaded netmap mode", + RunModeIdsNetmapSingle); + RunModeRegisterNewRunMode(RUNMODE_NETMAP, "workers", + "Workers netmap mode, each thread does all" + " tasks from acquisition to logging", + RunModeIdsNetmapWorkers); + default_mode_workers = "workers"; + RunModeRegisterNewRunMode(RUNMODE_NETMAP, "autofp", + "Multi threaded netmap mode. Packets from " + "each flow are assigned to a single detect " + "thread.", + RunModeIdsNetmapAutoFp); + return; +} + +#ifdef HAVE_NETMAP + +static void NetmapDerefConfig(void *conf) +{ + NetmapIfaceConfig *pfp = (NetmapIfaceConfig *)conf; + /* config is used only once but cost of this low. */ + if (SC_ATOMIC_SUB(pfp->ref, 1) == 0) { + SCFree(pfp); + } +} + +/** +* \brief extract information from config file +* +* The returned structure will be freed by the thread init function. +* This is thus necessary to or copy the structure before giving it +* to thread or to reparse the file for each thread (and thus have +* new structure. +* +* \return a NetmapIfaceConfig corresponding to the interface name +*/ +static void *ParseNetmapConfig(const char *iface) +{ + char *threadsstr = NULL; + ConfNode *if_root; + ConfNode *if_default = NULL; + ConfNode *netmap_node; + NetmapIfaceConfig *aconf = SCMalloc(sizeof(*aconf)); + char *tmpctype; + char *copymodestr; + int boolval; + char *bpf_filter = NULL; + char *out_iface = NULL; + + if (unlikely(aconf == NULL)) { + return NULL; + } + + if (iface == NULL) { + SCFree(aconf); + return NULL; + } + + memset(aconf, 0, sizeof(*aconf)); + strlcpy(aconf->iface, iface, sizeof(aconf->iface)); + SC_ATOMIC_INIT(aconf->ref); + (void) SC_ATOMIC_ADD(aconf->ref, 1); + aconf->DerefFunc = NetmapDerefConfig; + aconf->threads = 1; + aconf->promisc = 1; + aconf->checksum_mode = CHECKSUM_VALIDATION_AUTO; + aconf->copy_mode = NETMAP_COPY_MODE_NONE; + + if (ConfGet("bpf-filter", &bpf_filter) == 1) { + if (strlen(bpf_filter) > 0) { + aconf->bpf_filter = bpf_filter; + SCLogInfo("Going to use command-line provided bpf filter '%s'", + aconf->bpf_filter); + } + } + + /* Find initial node */ + netmap_node = ConfGetNode("netmap"); + if (netmap_node == NULL) { + SCLogInfo("Unable to find netmap config using default value"); + return aconf; + } + + if_root = ConfNodeLookupKeyValue(netmap_node, "interface", iface); + + if_default = ConfNodeLookupKeyValue(netmap_node, "interface", "default"); + + if (if_root == NULL && if_default == NULL) { + SCLogInfo("Unable to find netmap config for " + "interface \"%s\" or \"default\", using default value", + iface); + return aconf; + } + + /* If there is no setting for current interface use default one as main iface */ + if (if_root == NULL) { + if_root = if_default; + if_default = NULL; + } + + if (ConfGetChildValueWithDefault(if_root, if_default, "threads", &threadsstr) != 1) { + aconf->threads = 1; + } else { + if (strcmp(threadsstr, "auto") == 0) { + aconf->threads = GetIfaceRSSQueuesNum(iface); + } else { + aconf->threads = (uint8_t)atoi(threadsstr); + } + } + + if (aconf->threads <= 0) { + aconf->threads = 1; + } + if (aconf->threads) { + SCLogInfo("Using %d threads for interface %s", aconf->threads, iface); + } + + if (ConfGetChildValueWithDefault(if_root, if_default, "copy-iface", &out_iface) == 1) { + if (strlen(out_iface) > 0) { + aconf->out_iface = out_iface; + } + } + + if (ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == 1) { + if (aconf->out_iface == NULL) { + SCLogInfo("Copy mode activated but no destination" + " iface. Disabling feature"); + } else if (strlen(copymodestr) <= 0) { + aconf->out_iface = NULL; + } else if (strcmp(copymodestr, "ips") == 0) { + SCLogInfo("Netmap IPS mode activated %s->%s", + iface, + aconf->out_iface); + aconf->copy_mode = NETMAP_COPY_MODE_IPS; + } else if (strcmp(copymodestr, "tap") == 0) { + SCLogInfo("Netmap TAP mode activated %s->%s", + iface, + aconf->out_iface); + aconf->copy_mode = NETMAP_COPY_MODE_TAP; + } else { + SCLogInfo("Invalid mode (not in tap, ips)"); + } + } + + SC_ATOMIC_RESET(aconf->ref); + (void) SC_ATOMIC_ADD(aconf->ref, aconf->threads); + + /* load netmap bpf filter */ + /* command line value has precedence */ + if (ConfGet("bpf-filter", &bpf_filter) != 1) { + if (ConfGetChildValueWithDefault(if_root, if_default, "bpf-filter", &bpf_filter) == 1) { + if (strlen(bpf_filter) > 0) { + aconf->bpf_filter = bpf_filter; + SCLogInfo("Going to use bpf filter %s", aconf->bpf_filter); + } + } + } + + (void)ConfGetChildValueBoolWithDefault(if_root, if_default, "disable-promisc", (int *)&boolval); + if (boolval) { + SCLogInfo("Disabling promiscuous mode on iface %s", aconf->iface); + aconf->promisc = 0; + } + + if (ConfGetChildValueWithDefault(if_root, if_default, "checksum-checks", &tmpctype) == 1) { + if (strcmp(tmpctype, "auto") == 0) { + aconf->checksum_mode = CHECKSUM_VALIDATION_AUTO; + } else if (strcmp(tmpctype, "yes") == 0) { + aconf->checksum_mode = CHECKSUM_VALIDATION_ENABLE; + } else if (strcmp(tmpctype, "no") == 0) { + aconf->checksum_mode = CHECKSUM_VALIDATION_DISABLE; + } else { + SCLogError(SC_ERR_INVALID_ARGUMENT, "Invalid value for checksum-checks for %s", aconf->iface); + } + } + + return aconf; +} + +static int NetmapConfigGeThreadsCount(void *conf) +{ + NetmapIfaceConfig *aconf = (NetmapIfaceConfig *)conf; + return aconf->threads; +} + +int NetmapRunModeIsIPS() +{ + int nlive = LiveGetDeviceCount(); + int ldev; + ConfNode *if_root; + ConfNode *if_default = NULL; + ConfNode *netmap_node; + int has_ips = 0; + int has_ids = 0; + + /* Find initial node */ + netmap_node = ConfGetNode("netmap"); + if (netmap_node == NULL) { + return 0; + } + + if_default = ConfNodeLookupKeyValue(netmap_node, "interface", "default"); + + for (ldev = 0; ldev < nlive; ldev++) { + char *live_dev = LiveGetDeviceName(ldev); + if (live_dev == NULL) { + SCLogError(SC_ERR_INVALID_VALUE, "Problem with config file"); + return 0; + } + char *copymodestr = NULL; + if_root = ConfNodeLookupKeyValue(netmap_node, "interface", live_dev); + + if (if_root == NULL) { + if (if_default == NULL) { + SCLogError(SC_ERR_INVALID_VALUE, "Problem with config file"); + return 0; + } + if_root = if_default; + } + + if (ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == 1) { + if (strcmp(copymodestr, "ips") == 0) { + has_ips = 1; + } else { + has_ids = 1; + } + } else { + has_ids = 1; + } + } + + if (has_ids && has_ips) { + SCLogInfo("Netmap mode using IPS and IDS mode"); + for (ldev = 0; ldev < nlive; ldev++) { + char *live_dev = LiveGetDeviceName(ldev); + if (live_dev == NULL) { + SCLogError(SC_ERR_INVALID_VALUE, "Problem with config file"); + return 0; + } + if_root = ConfNodeLookupKeyValue(netmap_node, "interface", live_dev); + char *copymodestr = NULL; + + if (if_root == NULL) { + if (if_default == NULL) { + SCLogError(SC_ERR_INVALID_VALUE, "Problem with config file"); + return 0; + } + if_root = if_default; + } + + if (! ((ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == 1) && + (strcmp(copymodestr, "ips") == 0))) { + SCLogError(SC_ERR_INVALID_ARGUMENT, + "Netmap IPS mode used and interface '%s' is in IDS or TAP mode. " + "Sniffing '%s' but expect bad result as stream-inline is activated.", + live_dev, live_dev); + } + } + } + + return has_ips; +} + +#endif // #ifdef HAVE_NETMAP + +int RunModeIdsNetmapAutoFp(DetectEngineCtx *de_ctx) +{ + SCEnter(); + +#ifdef HAVE_NETMAP + int ret; + char *live_dev = NULL; + + RunModeInitialize(); + + TimeModeSetLive(); + + (void)ConfGet("netmap.live-interface", &live_dev); + + SCLogDebug("live_dev %s", live_dev); + + ret = RunModeSetLiveCaptureAutoFp(de_ctx, + ParseNetmapConfig, + NetmapConfigGeThreadsCount, + "ReceiveNetmap", + "DecodeNetmap", "RxNetmap", + live_dev); + if (ret != 0) { + SCLogError(SC_ERR_RUNMODE, "Unable to start runmode"); + exit(EXIT_FAILURE); + } + + SCLogInfo("RunModeIdsNetmapAutoFp initialised"); +#endif /* HAVE_NETMAP */ + + SCReturnInt(0); +} + +/** +* \brief Single thread version of the netmap processing. +*/ +int RunModeIdsNetmapSingle(DetectEngineCtx *de_ctx) +{ + SCEnter(); + +#ifdef HAVE_NETMAP + int ret; + char *live_dev = NULL; + + RunModeInitialize(); + TimeModeSetLive(); + + (void)ConfGet("netmap.live-interface", &live_dev); + + ret = RunModeSetLiveCaptureSingle(de_ctx, + ParseNetmapConfig, + NetmapConfigGeThreadsCount, + "ReceiveNetmap", + "DecodeNetmap", "NetmapPkt", + live_dev); + if (ret != 0) { + SCLogError(SC_ERR_RUNMODE, "Unable to start runmode"); + exit(EXIT_FAILURE); + } + + SCLogInfo("RunModeIdsNetmapSingle initialised"); + +#endif /* HAVE_NETMAP */ + SCReturnInt(0); +} + +/** +* \brief Workers version of the netmap processing. +* +* Start N threads with each thread doing all the work. +* +*/ +int RunModeIdsNetmapWorkers(DetectEngineCtx *de_ctx) +{ + SCEnter(); + +#ifdef HAVE_NETMAP + int ret; + char *live_dev = NULL; + + RunModeInitialize(); + TimeModeSetLive(); + + (void)ConfGet("netmap.live-interface", &live_dev); + + ret = RunModeSetLiveCaptureWorkers(de_ctx, + ParseNetmapConfig, + NetmapConfigGeThreadsCount, + "ReceiveNetmap", + "DecodeNetmap", "NetmapPkt", + live_dev); + if (ret != 0) { + SCLogError(SC_ERR_RUNMODE, "Unable to start runmode"); + exit(EXIT_FAILURE); + } + + SCLogInfo("RunModeIdsNetmapWorkers initialised"); + +#endif /* HAVE_NETMAP */ + SCReturnInt(0); +} + +/** +* @} +*/ diff --git a/src/runmode-netmap.h b/src/runmode-netmap.h new file mode 100644 index 0000000000..392e9781ca --- /dev/null +++ b/src/runmode-netmap.h @@ -0,0 +1,33 @@ +/* Copyright (C) 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. + */ + +/** \file +* +* \author Aleksey Katargin +*/ + +#ifndef __RUNMODE_NETMAP_H__ +#define __RUNMODE_NETMAP_H__ + +int RunModeIdsNetmapSingle(DetectEngineCtx *); +int RunModeIdsNetmapAutoFp(DetectEngineCtx *); +int RunModeIdsNetmapWorkers(DetectEngineCtx *); +void RunModeIdsNetmapRegister(void); +const char *RunModeNetmapGetDefaultMode(void); +int NetmapRunModeIsIPS(); + +#endif /* __RUNMODE_NETMAP_H__ */ diff --git a/src/runmodes.c b/src/runmodes.c index 35f4e0739a..4ce80d7a93 100644 --- a/src/runmodes.c +++ b/src/runmodes.c @@ -133,6 +133,12 @@ static const char *RunModeTranslateModeToName(int runmode) return "MPIPE"; case RUNMODE_AFP_DEV: return "AF_PACKET_DEV"; + case RUNMODE_NETMAP: +#ifdef HAVE_NETMAP + return "NETMAP"; +#else + return "NETMAP(DISABLED)"; +#endif case RUNMODE_UNIX_SOCKET: return "UNIX_SOCKET"; default: @@ -205,6 +211,7 @@ void RunModeRegisterRunModes(void) RunModeErfDagRegister(); RunModeNapatechRegister(); RunModeIdsAFPRegister(); + RunModeIdsNetmapRegister(); RunModeIdsNflogRegister(); RunModeTileMpipeRegister(); RunModeUnixSocketRegister(); @@ -306,6 +313,9 @@ void RunModeDispatch(int runmode, const char *custom_mode, DetectEngineCtx *de_c case RUNMODE_AFP_DEV: custom_mode = RunModeAFPGetDefaultMode(); break; + case RUNMODE_NETMAP: + custom_mode = RunModeNetmapGetDefaultMode(); + break; case RUNMODE_UNIX_SOCKET: custom_mode = RunModeUnixSocketGetDefaultMode(); break; diff --git a/src/runmodes.h b/src/runmodes.h index ba155c7134..80ad652b34 100644 --- a/src/runmodes.h +++ b/src/runmodes.h @@ -35,6 +35,7 @@ enum { RUNMODE_ERF_FILE, RUNMODE_DAG, RUNMODE_AFP_DEV, + RUNMODE_NETMAP, RUNMODE_TILERA_MPIPE, RUNMODE_UNITTEST, RUNMODE_NAPATECH, @@ -89,6 +90,7 @@ int RunModeOutputFiledataEnabled(void); #include "runmode-af-packet.h" #include "runmode-nflog.h" #include "runmode-unix-socket.h" +#include "runmode-netmap.h" int threading_set_cpu_affinity; extern float threading_detect_ratio; diff --git a/src/source-netmap.c b/src/source-netmap.c new file mode 100644 index 0000000000..8e36d284d2 --- /dev/null +++ b/src/source-netmap.c @@ -0,0 +1,999 @@ +/* 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 +* +* 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 +#endif + +#if HAVE_SYS_MMAN_H +#include +#endif + +#include + +#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 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; + 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; + 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 ring_from; + int 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; + }; + if (nm_req.nr_rx_rings != nm_req.nr_tx_rings) { + SCLogError(SC_ERR_NETMAP_CREATE, + "Interface '%s' have non-equeal Tx/Rx rings (%"PRIu16"/%"PRIu16")", + ifname, nm_req.nr_rx_rings, nm_req.nr_tx_rings); + goto error_fd; + } + + pdev->rings_cnt = nm_req.nr_rx_rings; + pdev->memsize = nm_req.nr_memsize; + + pdev->rings = SCMalloc(sizeof(*pdev->rings) * pdev->rings_cnt); + 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); + + /* 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; + } + + nm_req.nr_flags = NR_REG_ONE_NIC; + nm_req.nr_ringid = i | 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)); + goto error_fd; + } + pdev->nif = NETMAP_IF(pdev->mem, nm_req.nr_offset); + } + + pring->rx = NETMAP_RXRING(pdev->nif, i); + pring->tx = NETMAP_TXRING(pdev->nif, i); + SCSpinInit(&pring->tx_lock, 0); + success_cnt++; + } + + if (success_cnt != pdev->rings_cnt) { + for(int i = 0; i < success_cnt; i++) { + close(pdev->rings[i].fd); + } + SCFree(pdev->rings); + goto error_mem; + } + + close(fd); + *pdevice = pdev; + + TAILQ_INSERT_TAIL(&netmap_devlist, pdev, next); + SCMutexUnlock(&netmap_devlist_lock); + + return 0; + +error_mem: + munmap(pdev->mem, pdev->memsize); +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) +{ + SCPerfCounterAddUI64(ntv->capture_kernel_packets, ntv->tv->sc_perf_pca, ntv->pkts); + SCPerfCounterAddUI64(ntv->capture_kernel_drops, ntv->tv->sc_perf_pca, 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); + 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 (aconf->threads > ntv->ifsrc->rings_cnt) { + SCLogError(SC_ERR_INVALID_VALUE, + "Thread count can't be greater than ring count. " + "Configured %d threads for interfaces '%s' with %u rings.", + aconf->threads, aconf->iface, ntv->ifsrc->rings_cnt); + goto error_src; + } + + 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 rings borders */ + int tmp = ntv->ifsrc->rings_cnt / aconf->threads; + ntv->ring_from = ntv->thread_idx * tmp; + ntv->ring_to = ntv->ring_from + tmp - 1; + if (ntv->ring_to >= ntv->ifsrc->rings_cnt) + ntv->ring_to = ntv->ifsrc->rings_cnt - 1; + + if (aconf->copy_mode != NETMAP_COPY_MODE_NONE) { + if (NetmapOpen(aconf->out_iface, 0, &ntv->ifdst, 1) != 0) { + goto error_src; + } + } + + /* basic counters */ + ntv->capture_kernel_packets = SCPerfTVRegisterCounter("capture.kernel_packets", + ntv->tv, + SC_PERF_TYPE_UINT64, + "NULL"); + ntv->capture_kernel_drops = SCPerfTVRegisterCounter("capture.kernel_drops", + ntv->tv, + SC_PERF_TYPE_UINT64, + "NULL"); + + char const *active_runmode = RunmodeGetActive(); + if (active_runmode && !strcmp("workers", active_runmode)) { + ntv->flags |= NETMAP_FLAG_ZERO_COPY; + SCLogInfo("Enabling zero copy mode"); + } + + 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_src; + } + } + + 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_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 */ + int dst_ring_id = p->netmap_v.ring_id % ntv->ifdst->rings_cnt; + NetmapRing *txring = &ntv->ifdst->rings[dst_ring_id]; + NetmapRing *rxring = &ntv->ifsrc->rings[p->netmap_v.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 *rs = &rxring->rx->slot[p->netmap_v.slot_id]; + struct netmap_slot *ts = &txring->tx->slot[txring->tx->cur]; + + /* 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; + + txring->tx->head = txring->tx->cur = nm_ring_next(txring->tx, txring->tx->cur); + + 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(); + + struct netmap_ring *ring = ntv->ifsrc->rings[ring_id].rx; + uint32_t avail = nm_ring_space(ring); + uint32_t cur = ring->cur; + + while (likely(avail-- > 0)) { + struct netmap_slot *slot = &ring->slot[cur]; + unsigned char *slot_data = (unsigned char *)NETMAP_BUF(ring, 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(ring, 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 = ring->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 { + p->ReleasePacket = NetmapReleasePacket; + p->netmap_v.ring_id = ring_id; + p->netmap_v.slot_id = cur; + p->netmap_v.ntv = ntv; + } + } else { + if (PacketCopyData(p, slot_data, slot->len) == -1) { + TmqhOutputPacketpool(ntv->tv, p); + SCReturnInt(NETMAP_FAILURE); + } + } + + 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(ring, cur); + } + ring->head = ring->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->ring_to - ntv->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->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->ring_from, ntv->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->ring_from + i; + NetmapRingRead(ntv, src_ring_id); + + if (ntv->copy_mode != NETMAP_COPY_MODE_NONE) { + /* sync dst tx rings */ + int dst_ring_id = src_ring_id % ntv->ifdst->rings_cnt; + NetmapRing *dst_ring = &ntv->ifdst->rings[dst_ring_id]; + if (SCSpinTrylock(&dst_ring->tx_lock) == 0) { + ioctl(dst_ring->fd, NIOCTXSYNC, 0); + SCSpinUnlock(&dst_ring->tx_lock); + } + } + } + } + + NetmapDumpCounters(ntv); + SCPerfSyncCountersIfSignalled(tv); + } + + SCFree(fds); + SCPerfSyncCountersIfSignalled(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, + (uint64_t) SCPerfGetLocalCounterValue(ntv->capture_kernel_packets, tv->sc_perf_pca), + (uint64_t) SCPerfGetLocalCounterValue(ntv->capture_kernel_drops, tv->sc_perf_pca), + 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 */ + SCPerfCounterIncr(dtv->counter_pkts, tv->sc_perf_pca); + SCPerfCounterAddUI64(dtv->counter_bytes, tv->sc_perf_pca, GET_PKT_LEN(p)); + SCPerfCounterAddUI64(dtv->counter_avg_pkt_size, tv->sc_perf_pca, GET_PKT_LEN(p)); + SCPerfCounterSetUI64(dtv->counter_max_pkt_size, tv->sc_perf_pca, GET_PKT_LEN(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 */ +/** +* @} +*/ diff --git a/src/source-netmap.h b/src/source-netmap.h new file mode 100644 index 0000000000..42ed91feef --- /dev/null +++ b/src/source-netmap.h @@ -0,0 +1,62 @@ +/* Copyright (C) 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. + */ + +/** +* \file +* +* \author Aleksey Katargin +*/ + +#ifndef __SOURCE_NETMAP_H__ +#define __SOURCE_NETMAP_H__ + +#include "queue.h" + +/* copy modes */ +enum { + NETMAP_COPY_MODE_NONE, + NETMAP_COPY_MODE_TAP, + NETMAP_COPY_MODE_IPS, +}; + +#define NETMAP_IFACE_NAME_LENGTH 48 + +typedef struct NetmapIfaceConfig_ +{ + char iface[NETMAP_IFACE_NAME_LENGTH]; + int threads; + int promisc; + int copy_mode; + ChecksumValidationMode checksum_mode; + char *bpf_filter; + char *out_iface; + SC_ATOMIC_DECLARE(unsigned int, ref); + void (*DerefFunc)(void *); +} NetmapIfaceConfig; + +typedef struct NetmapPacketVars_ +{ + int ring_id; + int slot_id; + /* NetmapThreadVars */ + void *ntv; +} NetmapPacketVars; + +void TmModuleReceiveNetmapRegister (void); +void TmModuleDecodeNetmapRegister (void); + +#endif /* __SOURCE_NETMAP_H__ */ diff --git a/src/suricata.c b/src/suricata.c index b71d540f3e..894b927aa6 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -119,6 +119,7 @@ #include "source-napatech.h" #include "source-af-packet.h" +#include "source-netmap.h" #include "source-mpipe.h" #include "respond-reject.h" @@ -605,6 +606,9 @@ void usage(const char *progname) #ifdef HAVE_AF_PACKET printf("\t--af-packet[=] : run in af-packet mode, no value select interfaces from suricata.yaml\n"); #endif +#ifdef HAVE_NETMAP + printf("\t--netmap[=] : run in netmap mode, no value select interfaces from suricata.yaml\n"); +#endif #ifdef HAVE_PFRING printf("\t--pfring[=] : run in pfring mode, use interfaces from suricata.yaml\n"); printf("\t--pfring-int : run in pfring mode, use interface \n"); @@ -683,6 +687,9 @@ void SCPrintBuildInfo(void) #ifdef HAVE_AF_PACKET strlcat(features, "AF_PACKET ", sizeof(features)); #endif +#ifdef HAVE_NETMAP + strlcat(features, "NETMAP ", sizeof(features)); +#endif #ifdef HAVE_PACKET_FANOUT strlcat(features, "HAVE_PACKET_FANOUT ", sizeof(features)); #endif @@ -844,6 +851,9 @@ void RegisterAllModules() /* af-packet */ TmModuleReceiveAFPRegister(); TmModuleDecodeAFPRegister(); + /* netmap */ + TmModuleReceiveNetmapRegister(); + TmModuleDecodeNetmapRegister(); /* pfring */ TmModuleReceivePfringRegister(); TmModuleDecodePfringRegister(); @@ -1015,6 +1025,26 @@ static TmEcode ParseInterfacesList(int run_mode, char *pcap_dev) EngineModeSetIPS(); } } +#ifdef HAVE_NETMAP + } else if (run_mode == RUNMODE_NETMAP) { + /* iface has been set on command line */ + if (strlen(pcap_dev)) { + if (ConfSetFinal("netmap.live-interface", pcap_dev) != 1) { + SCLogError(SC_ERR_INITIALIZATION, "Failed to set netmap.live-interface"); + SCReturnInt(TM_ECODE_FAILED); + } + } else { + int ret = LiveBuildDeviceList("netmap"); + if (ret == 0) { + SCLogError(SC_ERR_INITIALIZATION, "No interface found in config for netmap"); + SCReturnInt(TM_ECODE_FAILED); + } + if (NetmapRunModeIsIPS()) { + SCLogInfo("Netmap: Setting IPS mode"); + EngineModeSetIPS(); + } + } +#endif #ifdef HAVE_NFLOG } else if (run_mode == RUNMODE_NFLOG) { int ret = LiveBuildDeviceListCustom("nflog", "group"); @@ -1129,6 +1159,7 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri) {"pfring-cluster-id", required_argument, 0, 0}, {"pfring-cluster-type", required_argument, 0, 0}, {"af-packet", optional_argument, 0, 0}, + {"netmap", optional_argument, 0, 0}, {"pcap", optional_argument, 0, 0}, #ifdef BUILD_UNIX_SOCKET {"unix-socket", optional_argument, 0, 0}, @@ -1248,6 +1279,36 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri) "host, make sure to pass --enable-af-packet to " "configure when building."); return TM_ECODE_FAILED; +#endif + } else if (strcmp((long_opts[option_index]).name , "netmap") == 0){ +#ifdef HAVE_NETMAP + if (suri->run_mode == RUNMODE_UNKNOWN) { + suri->run_mode = RUNMODE_NETMAP; + if (optarg) { + LiveRegisterDevice(optarg); + memset(suri->pcap_dev, 0, sizeof(suri->pcap_dev)); + strlcpy(suri->pcap_dev, optarg, + ((strlen(optarg) < sizeof(suri->pcap_dev)) ? + (strlen(optarg) + 1) : sizeof(suri->pcap_dev))); + } + } else if (suri->run_mode == RUNMODE_NETMAP) { + SCLogWarning(SC_WARN_PCAP_MULTI_DEV_EXPERIMENTAL, "using " + "multiple devices to get packets is experimental."); + if (optarg) { + LiveRegisterDevice(optarg); + } else { + SCLogInfo("Multiple netmap option without interface on each is useless"); + break; + } + } else { + SCLogError(SC_ERR_MULTIPLE_RUN_MODE, "more than one run mode " + "has been specified"); + usage(argv[0]); + return TM_ECODE_FAILED; + } +#else + SCLogError(SC_ERR_NO_NETMAP, "NETMAP not enabled."); + return TM_ECODE_FAILED; #endif } else if (strcmp((long_opts[option_index]).name, "nflog") == 0) { #ifdef HAVE_NFLOG @@ -1956,6 +2017,7 @@ static int ConfigGetCaptureValue(SCInstance *suri) switch (suri->run_mode) { case RUNMODE_PCAP_DEV: case RUNMODE_AFP_DEV: + case RUNMODE_NETMAP: case RUNMODE_PFRING: /* FIXME this don't work effficiently in multiinterface */ /* find payload for interface and use it */ diff --git a/src/tm-modules.c b/src/tm-modules.c index 19caeca727..bd990b4e11 100644 --- a/src/tm-modules.c +++ b/src/tm-modules.c @@ -269,6 +269,8 @@ const char * TmModuleTmmIdToString(TmmId id) CASE_CODE (TMM_FLOWRECYCLER); CASE_CODE (TMM_LUALOG); CASE_CODE (TMM_LOGSTATSLOG); + CASE_CODE (TMM_RECEIVENETMAP); + CASE_CODE (TMM_DECODENETMAP); CASE_CODE (TMM_SIZE); } diff --git a/src/tm-threads-common.h b/src/tm-threads-common.h index 059ba7b12d..c18e508765 100644 --- a/src/tm-threads-common.h +++ b/src/tm-threads-common.h @@ -74,6 +74,8 @@ typedef enum { TMM_DECODEERFDAG, TMM_RECEIVEAFP, TMM_DECODEAFP, + TMM_RECEIVENETMAP, + TMM_DECODENETMAP, TMM_ALERTPCAPINFO, TMM_RECEIVEMPIPE, TMM_DECODEMPIPE, diff --git a/src/util-error.c b/src/util-error.c index e8dd993902..4ae917191e 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -302,6 +302,9 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_WARN_LUA_SCRIPT); CASE_CODE (SC_ERR_LUA_SCRIPT); CASE_CODE (SC_WARN_NO_STATS_LOGGERS); + CASE_CODE (SC_ERR_NO_NETMAP); + CASE_CODE (SC_ERR_NETMAP_CREATE); + CASE_CODE (SC_ERR_NETMAP_READ); } return "UNKNOWN_ERROR"; diff --git a/src/util-error.h b/src/util-error.h index 8d5577d8ef..9e9008a66c 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -291,6 +291,9 @@ typedef enum { SC_WARN_LUA_SCRIPT, SC_ERR_LUA_SCRIPT, SC_WARN_NO_STATS_LOGGERS, + SC_ERR_NO_NETMAP, + SC_ERR_NETMAP_CREATE, + SC_ERR_NETMAP_READ, } SCError; const char *SCErrorToString(SCError); diff --git a/suricata.yaml.in b/suricata.yaml.in index 2bb208381e..5caa0031e6 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -453,6 +453,39 @@ af-packet: #threads: auto #use-mmap: yes +# netmap support +netmap: + - interface: eth2 + # Number of receive threads. "auto" uses number of RSS queues on interface. + threads: auto + # You can use the following variables to activate netmap tap or IPS mode. + # If copy-mode is set to ips or tap, the traffic coming to the current + # interface will be copied to the copy-iface interface. If 'tap' is set, the + # copy is complete. If 'ips' is set, the packet matching a 'drop' action + # will not be copied. + #copy-mode: tap + #copy-iface: eth3 + # Set to yes to disable promiscuous mode + # disable-promisc: no + # Choose checksum verification mode for the interface. At the moment + # of the capture, some packets may be with an invalid checksum due to + # offloading to the network card of the checksum computation. + # Possible values are: + # - yes: checksum validation is forced + # - no: checksum validation is disabled + # - auto: suricata uses a statistical approach to detect when + # checksum off-loading is used. + # Warning: 'checksum-validation' must be set to yes to have any validation + #checksum-checks: auto + # BPF filter to apply to this interface. The pcap filter syntax apply here. + #bpf-filter: port 80 or udp + #- interface: eth3 + #threads: auto + #copy-mode: tap + #copy-iface: eth2 + # Put default values here + - interface: default + legacy: uricontent: enabled -- 2.47.2