diff --git a/configure.in b/configure.in index 25e6222eea..866decfe65 100644 --- a/configure.in +++ b/configure.in @@ -676,7 +676,6 @@ esac CFLAGS="${STORE_CFLAGS}" - if test "$pfring_recv_uchar_buff" = "yes"; then AC_DEFINE([HAVE_PFRING_RECV_UCHAR],[1],[PF_RING pfring_recv buffer is u_char**]) AC_MSG_RESULT(yes) @@ -685,6 +684,7 @@ esac fi ]) + # libpcap AC_ARG_WITH(libpcap_includes, [ --with-libpcap-includes=DIR libpcap include directory], @@ -747,6 +747,12 @@ AC_CHECK_HEADER(pcap.h,,[AC_ERROR(pcap.h not found ...)]) AM_CONDITIONAL([BUILD_UNITTESTS], [test "x$enable_unittests" = "xyes"]) +# AF_PACKET support + AC_CHECK_DECL([PACKET_FANOUT], + AC_DEFINE([HAVE_PACKET_FANOUT],[1],[Packet fanout support is available]), + [], + [[#include ]]) + # enable native timeval for unified alert output AC_ARG_ENABLE(unified-native-timeval, AS_HELP_STRING([--enable-unified-native-timeval], [Use native timeval for unified outputs]),,[enable_unified_native_timeval=no]) diff --git a/src/Makefile.am b/src/Makefile.am index 2efb0c91a2..aa8bcf528e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ runmode-nfq.c runmode-nfq.h \ runmode-ipfw.c runmode-ipfw.h \ runmode-erf-file.c runmode-erf-file.h \ runmode-erf-dag.c runmode-erf-dag.h \ +runmode-af-packet.c runmode-af-packet.h \ packet-queue.c packet-queue.h \ data-queue.c data-queue.h \ threads.c threads.h \ @@ -24,6 +25,7 @@ source-pfring.c source-pfring.h \ source-ipfw.c source-ipfw.h \ source-erf-file.c source-erf-file.h \ source-erf-dag.c source-erf-dag.h \ +source-af-packet.c source-af-packet.h \ decode.c decode.h \ decode-ethernet.c decode-ethernet.h \ decode-vlan.c decode-vlan.h \ diff --git a/src/runmode-af-packet.c b/src/runmode-af-packet.c new file mode 100644 index 0000000000..e65bd4ce8e --- /dev/null +++ b/src/runmode-af-packet.c @@ -0,0 +1,401 @@ +/* Copyright (C) 2011 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. + */ + +#include "suricata-common.h" +#include "tm-threads.h" +#include "conf.h" +#include "runmodes.h" +#include "runmode-af-packet.h" +#include "log-httplog.h" +#include "output.h" +#include "cuda-packet-batcher.h" +#include "source-pfring.h" +#include "detect-engine-mpm.h" + +#include "alert-fastlog.h" +#include "alert-prelude.h" +#include "alert-unified-log.h" +#include "alert-unified-alert.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" + +static const char *default_mode = NULL; + +const char *RunModeAFPGetDefaultMode(void) +{ + return default_mode; +} + +void RunModeIdsAFPRegister(void) +{ + RunModeRegisterNewRunMode(RUNMODE_AFP_DEV, "auto", + "Multi threaded af-packet mode", + RunModeIdsAFPAuto); + default_mode = "auto"; + + return; +} + +/** + * \brief RunModeIdsAFPAuto set up the following thread packet handlers: + * - Receive thread (from iface pcap) + * - Decode thread + * - Stream thread + * - Detect: If we have only 1 cpu, it will setup one Detect thread + * If we have more than one, it will setup num_cpus - 1 + * starting from the second cpu available. + * - Respond/Reject thread + * - Outputs thread + * By default the threads will use the first cpu available + * except the Detection threads if we have more than one cpu. + * + * \param de_ctx Pointer to the Detection Engine. + * + * \retval 0 If all goes well. (If any problem is detected the engine will + * exit()). + */ +int RunModeIdsAFPAuto(DetectEngineCtx *de_ctx) +{ + SCEnter(); + /* tname = Detect + cpuid, this is 11bytes length as max */ + char tname[16]; + uint16_t cpu = 0; + TmModule *tm_module; + uint16_t thread; + + RunModeInitialize(); + TimeModeSetLive(); + + /* Available cpus */ + uint16_t ncpus = UtilCpuGetNumProcessorsOnline(); + /* TODO must not use PCAP function */ + int npcap = PcapLiveGetDeviceCount(); + + if (npcap == 1) { + char *pcap_dev = NULL; + if (ConfGet("pcap.single_pcap_dev", &pcap_dev) == 0) { + SCLogError(SC_ERR_RUNMODE, "Failed retrieving " + "pcap.single_pcap_dev from Conf"); + exit(EXIT_FAILURE); + } + SCLogDebug("pcap_dev %s", pcap_dev); + + char *pcap_devc = SCStrdup(pcap_dev); + + /* create the threads */ + ThreadVars *tv_receivepcap = + TmThreadCreatePacketHandler("ReceiveAFP", + "packetpool", "packetpool", + "pickup-queue", "simple", + "1slot"); + if (tv_receivepcap == NULL) { + printf("ERROR: TmThreadsCreate failed\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("ReceiveAFP"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName failed for ReceiveAFP\n"); + exit(EXIT_FAILURE); + } + Tm1SlotSetFunc(tv_receivepcap, tm_module, (void *)pcap_devc); + + TmThreadSetCPU(tv_receivepcap, RECEIVE_CPU_SET); + + if (TmThreadSpawn(tv_receivepcap) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + } else { + SCLogInfo("Using %d pcap device(s).", npcap); + + for (thread = 0; thread < npcap; thread++) { + char *pcap_dev = PcapLiveGetDevice(thread); + if (pcap_dev == NULL) { + printf("Failed to lookup pcap dev %d\n", thread); + exit(EXIT_FAILURE); + } + SCLogDebug("pcap_dev %s", pcap_dev); + + snprintf(tname, sizeof(tname),"RecvPcap-%s", pcap_dev); + char *tnamec = SCStrdup(tname); + char *pcap_devc = SCStrdup(pcap_dev); + + /* create the threads */ + ThreadVars *tv_receiveafp = + TmThreadCreatePacketHandler(tnamec, + "packetpool", "packetpool", + "pickup-queue", "simple", + "1slot"); + if (tv_receiveafp == NULL) { + printf("ERROR: TmThreadsCreate failed\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("ReceiveAFP"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName failed for ReceiveAFP\n"); + exit(EXIT_FAILURE); + } + Tm1SlotSetFunc(tv_receiveafp, tm_module, (void *)pcap_devc); + + TmThreadSetCPU(tv_receiveafp, RECEIVE_CPU_SET); + + if (TmThreadSpawn(tv_receiveafp) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + } + } + +#if defined(__SC_CUDA_SUPPORT__) + if (PatternMatchDefaultMatcher() == MPM_B2G_CUDA) { + ThreadVars *tv_decode1 = + TmThreadCreatePacketHandler("Decode", + "pickup-queue", "simple", + "decode-queue1", "simple", + "1slot"); + if (tv_decode1 == NULL) { + printf("ERROR: TmThreadsCreate failed for Decode1\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("DecodeAFP"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName DecodeAFP failed\n"); + exit(EXIT_FAILURE); + } + Tm1SlotSetFunc(tv_decode1, tm_module, NULL); + + TmThreadSetCPU(tv_decode1, DECODE_CPU_SET); + + if (TmThreadSpawn(tv_decode1) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + + ThreadVars *tv_cuda_PB = + TmThreadCreate("CUDA_PB", + "decode-queue1", "simple", + "cuda-pb-queue1", "simple", + "custom", SCCudaPBTmThreadsSlot1, 0); + if (tv_cuda_PB == NULL) { + printf("ERROR: TmThreadsCreate failed for CUDA_PB\n"); + exit(EXIT_FAILURE); + } + tv_cuda_PB->type = TVT_PPT; + + tm_module = TmModuleGetByName("CudaPacketBatcher"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName CudaPacketBatcher failed\n"); + exit(EXIT_FAILURE); + } + Tm1SlotSetFunc(tv_cuda_PB, tm_module, (void *)de_ctx); + + TmThreadSetCPU(tv_cuda_PB, DETECT_CPU_SET); + + if (TmThreadSpawn(tv_cuda_PB) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + + ThreadVars *tv_stream1 = + TmThreadCreatePacketHandler("Stream1", + "cuda-pb-queue1", "simple", + "stream-queue1", "simple", + "1slot"); + if (tv_stream1 == NULL) { + printf("ERROR: TmThreadsCreate failed for Stream1\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("StreamTcp"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName StreamTcp failed\n"); + exit(EXIT_FAILURE); + } + Tm1SlotSetFunc(tv_stream1, tm_module, NULL); + + TmThreadSetCPU(tv_stream1, STREAM_CPU_SET); + + if (TmThreadSpawn(tv_stream1) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + } else { + ThreadVars *tv_decode1 = + TmThreadCreatePacketHandler("Decode & Stream", + "pickup-queue", "simple", + "stream-queue1", "simple", + "varslot"); + if (tv_decode1 == NULL) { + printf("ERROR: TmThreadsCreate failed for Decode1\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("DecodeAFP"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName DecodeAFP failed\n"); + exit(EXIT_FAILURE); + } + TmVarSlotSetFuncAppend(tv_decode1, tm_module, NULL); + + tm_module = TmModuleGetByName("StreamTcp"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName StreamTcp failed\n"); + exit(EXIT_FAILURE); + } + TmVarSlotSetFuncAppend(tv_decode1, tm_module, NULL); + + TmThreadSetCPU(tv_decode1, DECODE_CPU_SET); + + if (TmThreadSpawn(tv_decode1) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + } + +#else + ThreadVars *tv_decode1 = + TmThreadCreatePacketHandler("Decode & Stream", + "pickup-queue", "simple", + "stream-queue1", "simple", + "varslot"); + if (tv_decode1 == NULL) { + printf("ERROR: TmThreadsCreate failed for Decode1\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("DecodeAFP"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName DecodeAFP failed\n"); + exit(EXIT_FAILURE); + } + TmVarSlotSetFuncAppend(tv_decode1, tm_module, NULL); + + tm_module = TmModuleGetByName("StreamTcp"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName StreamTcp failed\n"); + exit(EXIT_FAILURE); + } + TmVarSlotSetFuncAppend(tv_decode1, tm_module, NULL); + + TmThreadSetCPU(tv_decode1, DECODE_CPU_SET); + + if (TmThreadSpawn(tv_decode1) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } +#endif + + /* start with cpu 1 so that if we're creating an odd number of detect + * threads we're not creating the most on CPU0. */ + if (ncpus > 0) + cpu = 1; + + /* always create at least one thread */ + int thread_max = TmThreadGetNbThreads(DETECT_CPU_SET); + if (thread_max == 0) + thread_max = ncpus * threading_detect_ratio; + if (thread_max < 1) + thread_max = 1; + + for (thread = 0; thread < thread_max; thread++) { + snprintf(tname, sizeof(tname),"Detect%"PRIu16, thread+1); + + char *thread_name = SCStrdup(tname); + SCLogDebug("Assigning %s affinity to cpu %u", thread_name, cpu); + + ThreadVars *tv_detect_ncpu = + TmThreadCreatePacketHandler(thread_name, + "stream-queue1", "simple", + "verdict-queue", "simple", + "1slot"); + if (tv_detect_ncpu == NULL) { + printf("ERROR: TmThreadsCreate failed\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("Detect"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName Detect failed\n"); + exit(EXIT_FAILURE); + } + Tm1SlotSetFunc(tv_detect_ncpu, tm_module, (void *)de_ctx); + + TmThreadSetCPU(tv_detect_ncpu, DETECT_CPU_SET); + + char *thread_group_name = SCStrdup("Detect"); + if (thread_group_name == NULL) { + printf("Error allocating memory\n"); + exit(EXIT_FAILURE); + } + tv_detect_ncpu->thread_group_name = thread_group_name; + + if (TmThreadSpawn(tv_detect_ncpu) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + + if ((cpu + 1) == ncpus) + cpu = 0; + else + cpu++; + } + + ThreadVars *tv_rreject = + TmThreadCreatePacketHandler("RespondReject", + "verdict-queue", "simple", + "alert-queue", "simple", + "1slot"); + if (tv_rreject == NULL) { + printf("ERROR: TmThreadsCreate failed\n"); + exit(EXIT_FAILURE); + } + tm_module = TmModuleGetByName("RespondReject"); + if (tm_module == NULL) { + printf("ERROR: TmModuleGetByName for RespondReject failed\n"); + exit(EXIT_FAILURE); + } + Tm1SlotSetFunc(tv_rreject, tm_module, NULL); + + TmThreadSetCPU(tv_rreject, REJECT_CPU_SET); + + if (TmThreadSpawn(tv_rreject) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + + ThreadVars *tv_outputs = + TmThreadCreatePacketHandler("Outputs", + "alert-queue", "simple", + "packetpool", "packetpool", + "varslot"); + if (tv_outputs == NULL) { + printf("ERROR: TmThreadCreatePacketHandler for Outputs failed\n"); + exit(EXIT_FAILURE); + } + + SetupOutputs(tv_outputs); + + TmThreadSetCPU(tv_outputs, OUTPUT_CPU_SET); + + if (TmThreadSpawn(tv_outputs) != TM_ECODE_OK) { + printf("ERROR: TmThreadSpawn failed\n"); + exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/src/runmode-af-packet.h b/src/runmode-af-packet.h new file mode 100644 index 0000000000..02344ff017 --- /dev/null +++ b/src/runmode-af-packet.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2011 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** \file + * + * \author Eric Leblond + */ + +#ifndef __RUNMODE_AFP_H__ +#define __RUNMODE_AFP_H__ + +int RunModeIdsAFPAuto(DetectEngineCtx *); +void RunModeIdsAFPRegister(void); +const char *RunModeAFPGetDefaultMode(void); + +#endif /* __RUNMODE_AFP_H__ */ diff --git a/src/runmodes.c b/src/runmodes.c index 83b08a9220..14b6485e18 100644 --- a/src/runmodes.c +++ b/src/runmodes.c @@ -114,6 +114,8 @@ static const char *RunModeTranslateModeToName(int runmode) return "ERF_DAG"; case RUNMODE_UNITTEST: return "UNITTEST"; + case RUNMODE_AFP_DEV: + return "AF_PACKET_DEV"; default: SCLogError(SC_ERR_UNKNOWN_RUN_MODE, "Unknown runtime mode. Aborting"); exit(EXIT_FAILURE); @@ -155,6 +157,7 @@ void RunModeRegisterRunModes(void) RunModeIpsIPFWRegister(); RunModeErfFileRegister(); RunModeErfDagRegister(); + RunModeIdsAFPRegister(); UtRunModeRegister(); return; @@ -238,6 +241,9 @@ void RunModeDispatch(int runmode, const char *custom_mode, DetectEngineCtx *de_c case RUNMODE_DAG: custom_mode = RunModeErfDagGetDefaultMode(); break; + case RUNMODE_AFP_DEV: + custom_mode = RunModeAFPGetDefaultMode(); + break; default: SCLogError(SC_ERR_UNKNOWN_RUN_MODE, "Unknown runtime mode. Aborting"); exit(EXIT_FAILURE); diff --git a/src/runmodes.h b/src/runmodes.h index 1aa0722553..7555b45eb4 100644 --- a/src/runmodes.h +++ b/src/runmodes.h @@ -33,6 +33,7 @@ enum { RUNMODE_IPFW, RUNMODE_ERF_FILE, RUNMODE_DAG, + RUNMODE_AFP_DEV, RUNMODE_UNITTEST, RUNMODE_MAX, }; @@ -54,6 +55,7 @@ void RunModeShutDown(void); #include "runmode-ipfw.h" #include "runmode-erf-file.h" #include "runmode-erf-dag.h" +#include "runmode-af-packet.h" int threading_set_cpu_affinity; extern float threading_detect_ratio; diff --git a/src/source-af-packet.c b/src/source-af-packet.c new file mode 100644 index 0000000000..b535f46b17 --- /dev/null +++ b/src/source-af-packet.c @@ -0,0 +1,646 @@ +/* Copyright (C) 2011 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Eric Leblond + * + * AF_PACKET socket acquisition support + * + * Fanouts socket from David Miller: + * we need to support the split of flow in different socket + * option: + - packet_fanout type + - fanout ID ?? seems it could be useful + - protocol is the IEEE 802.3 protocol number in network order (filtering is great) + - runmode -> family of threads in parallel (acccount) + - add a new ratio or threads number (overwritten by cpu_affinity) + - add af_max_read_packets for batched reading + * + * architecture + * loop with read + * code needed for iface name to int mapping + * socket opening + * socket call + * bind + * must switch to promiscous mode -> use PACKET_MR_PROMISC socket option + * + * \todo watch other interface event to detect suppression of the monitored interface + */ + +#include "suricata-common.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-error.h" +#include "util-privs.h" +#include "tmqh-packetpool.h" +#include "source-af-packet.h" + +#include +#include +#include +#include + +extern uint8_t suricata_ctl_flags; +extern int max_pending_packets; + +/** control how many packets we may read in one go */ +static int afp_max_read_packets = 0; +/** max packets < 65536 */ +#define AFP_FILE_MAX_PKTS 256 +#define AFP_IFACE_NAME_LENGTH 48 + +#define AFP_STATE_DOWN 0 +#define AFP_STATE_UP 1 + +#define AFP_RECONNECT_TIMEOUT 500000 + +#define POLL_TIMEOUT 100 + +#define AFP_BUFSIZE 4096 + +/** + * \brief Structure to hold thread specific variables. + */ +typedef struct AFPThreadVars_ +{ + /* thread specific socket */ + int socket; + /* handle state */ + unsigned char afp_state; + char iface[AFP_IFACE_NAME_LENGTH]; + + /* data link type for the thread */ + int datalink; + int cooked; + + /* counters */ + uint32_t pkts; + uint64_t bytes; + uint32_t errs; + + /* AFP buffer size */ + int AFP_buffer_size; + + ThreadVars *tv; + + Packet *in_p; + + Packet *array[AFP_FILE_MAX_PKTS]; + uint16_t array_idx; + + int fanout; + + char *data; /** Per function and thread data */ + int datalen; /** Length of per function and thread data */ +} AFPThreadVars; + +TmEcode ReceiveAFP(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *); +TmEcode ReceiveAFPThreadInit(ThreadVars *, void *, void **); +void ReceiveAFPThreadExitStats(ThreadVars *, void *); +TmEcode ReceiveAFPThreadDeinit(ThreadVars *, void *); + +TmEcode DecodeAFPThreadInit(ThreadVars *, void *, void **); +TmEcode DecodeAFP(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *); + +/** + * \brief Registration Function for RecieveAFP. + * \todo Unit tests are needed for this module. + */ +void TmModuleReceiveAFPRegister (void) { + tmm_modules[TMM_RECEIVEAFP].name = "ReceiveAFP"; + tmm_modules[TMM_RECEIVEAFP].ThreadInit = ReceiveAFPThreadInit; + tmm_modules[TMM_RECEIVEAFP].Func = ReceiveAFP; + tmm_modules[TMM_RECEIVEAFP].ThreadExitPrintStats = ReceiveAFPThreadExitStats; + tmm_modules[TMM_RECEIVEAFP].ThreadDeinit = NULL; + tmm_modules[TMM_RECEIVEAFP].RegisterTests = NULL; + tmm_modules[TMM_RECEIVEAFP].cap_flags = SC_CAP_NET_RAW; +} + +/** + * \brief Registration Function for DecodeAFP. + * \todo Unit tests are needed for this module. + */ +void TmModuleDecodeAFPRegister (void) { + tmm_modules[TMM_DECODEAFP].name = "DecodeAFP"; + tmm_modules[TMM_DECODEAFP].ThreadInit = DecodeAFPThreadInit; + tmm_modules[TMM_DECODEAFP].Func = DecodeAFP; + tmm_modules[TMM_DECODEAFP].ThreadExitPrintStats = NULL; + tmm_modules[TMM_DECODEAFP].ThreadDeinit = NULL; + tmm_modules[TMM_DECODEAFP].RegisterTests = NULL; + tmm_modules[TMM_DECODEAFP].cap_flags = 0; +} + +static int createsocket(AFPThreadVars *ptv, char *devname, int verbose); + +/** + * \brief AF packet read function. + * + * This function fills + * From here the packets are picked up by the DecodeAFP thread. + * + * \param user pointer to AFPThreadVars + * \retval TM_ECODE_FAILED on failure and TM_ECODE_OK on success + */ +TmEcode AFPRead(AFPThreadVars *ptv) +{ + Packet *p = NULL; + /* XXX should try to use read that get directly to packet */ + uint8_t buf[AFP_BUFSIZE]; + int offset = 0; + int caplen; + struct sockaddr_ll from; + struct iovec iov; + struct msghdr msg; +#if 0 + struct cmsghdr *cmsg; + union { + struct cmsghdr cmsg; + char buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))]; + } cmsg_buf; +#endif + + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; +#if 0 + msg.msg_control = &cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); +#endif + msg.msg_flags = 0; + + if (ptv->cooked) + offset = SLL_HEADER_LEN; + else + offset = 0; + iov.iov_len = AFP_BUFSIZE - offset; + iov.iov_base = buf + offset; + + caplen = recvmsg(ptv->socket, &msg, MSG_TRUNC); + + if (caplen < 0) { + SCLogWarning(SC_ERR_AFP_READ, "recvmsg failed with error code %" PRId32, + errno); + SCReturnInt(TM_ECODE_FAILED); + } + if (ptv->array_idx == 0) { + p = ptv->in_p; + } else { + p = PacketGetFromQueueOrAlloc(); + } + + if (p == NULL) { + TmqhOutputPacketpool(ptv->tv, p); + SCReturnInt(TM_ECODE_FAILED); + } + + /* get timestamp of packet via ioctl */ + if (ioctl(ptv->socket, SIOCGSTAMP, &p->ts) == -1) { + SCLogWarning(SC_ERR_AFP_READ, "recvmsg failed with error code %" PRId32, + errno); + TmqhOutputPacketpool(ptv->tv, p); + SCReturnInt(TM_ECODE_FAILED); + } + + ptv->pkts++; + ptv->bytes += caplen + offset; + + /* add forged header */ + if (ptv->cooked) { + SllHdr * hdrp = (SllHdr *)buf; + /* XXX this is minimalist, but this seems enough */ + hdrp->sll_protocol = from.sll_protocol; + } + + p->datalink = ptv->datalink; + SET_PKT_LEN(p, caplen + offset); + if (PacketCopyData(p, buf, GET_PKT_LEN(p)) == -1) { + TmqhOutputPacketpool(ptv->tv, p); + SCReturnInt(TM_ECODE_FAILED); + } + SCLogDebug("pktlen: %" PRIu32 " (pkt %02x, pkt data %02x)", + GET_PKT_LEN(p), *pkt, *GET_PKT_DATA(p)); + + /* store the packet in our array */ + ptv->array[ptv->array_idx] = p; + ptv->array_idx++; + SCReturnInt(TM_ECODE_OK); +} + +static int AFPTryReopen(AFPThreadVars *ptv) +{ + int afp_activate_r; + + ptv->afp_state = AFP_STATE_DOWN; + + afp_activate_r = createsocket(ptv, ptv->iface, 0); + if (afp_activate_r != 0) { + return afp_activate_r; + } + + SCLogInfo("Recovering interface listening"); + ptv->afp_state = AFP_STATE_UP; + return 0; +} + +/** + * \brief Recieves packets from an interface via AF_PACKET socket + * + * This function recieves packets from an interface and passes + * the packet on to the AFP callback function. + * + * \param tv pointer to ThreadVars + * \param data pointer that gets cast into AFPThreadVars for ptv + * \param pq pointer to the PacketQueue (not used here but part of the api) + * \retval TM_ECODE_FAILED on failure and TM_ECODE_OK on success + */ +TmEcode ReceiveAFP(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq) { + SCEnter(); + uint16_t packet_q_len = 0; + struct pollfd fds; + TmEcode ret; + uint16_t cnt = 0; + + AFPThreadVars *ptv = (AFPThreadVars *)data; + + /* test AFP handle */ + if (ptv->afp_state == AFP_STATE_DOWN) { + int r; + do { + usleep(AFP_RECONNECT_TIMEOUT); + if (suricata_ctl_flags != 0) { + break; + } + r = AFPTryReopen(ptv); + } while (r < 0); + } + + /* make sure we have at least one packet in the packet pool, to prevent + * us from alloc'ing packets at line rate */ + while (packet_q_len == 0) { + packet_q_len = PacketPoolSize(); + if (packet_q_len == 0) { + PacketPoolWait(); + } + } + + if (postpq == NULL) + afp_max_read_packets = 1; + + ptv->array_idx = 0; + ptv->in_p = p; + + fds.fd = ptv->socket; + fds.events = POLLIN; + + int r = 0; + while (r >= 0) { + r = poll(&fds, 1, POLL_TIMEOUT); + + if (r > 0) { + + ret = AFPRead(ptv); + if (ret != TM_ECODE_OK) { + SCReturnInt(TM_ECODE_FAILED); + } + if (suricata_ctl_flags != 0) { + break; + } + if (cnt++ >= afp_max_read_packets) + break; + } + if (r < 0) { + int dbreak = 0; + SCLogError(SC_ERR_AFP_READ, "Error reading data from socket: (%d" PRIu32 ") %s", + errno, strerror(errno)); + do { + usleep(AFP_RECONNECT_TIMEOUT); + if (suricata_ctl_flags != 0) { + dbreak = 1; + break; + } + r = AFPTryReopen(ptv); + } while (r < 0); + if (dbreak) { + r = 0; + break; + } + } + if ( r == 0) { + if (suricata_ctl_flags != 0) { + break; + } + if (cnt > 0) + break; + } + } + + for (cnt = 0; cnt < ptv->array_idx; cnt++) { + Packet *pp = ptv->array[cnt]; + + /* enqueue all but the first in the postpq, the first + * pkt is handled by the tv "out handler" */ + if (cnt > 0) { + PacketEnqueue(postpq, pp); + } + } + + if (r < 0) { + SCLogError(SC_ERR_AFP_DISPATCH, "error code %" PRId32, + r); + + SCReturnInt(TM_ECODE_OK); + } + + if (suricata_ctl_flags != 0) { + SCReturnInt(TM_ECODE_FAILED); + } + + SCReturnInt(TM_ECODE_OK); +} + +static int getifnumbydev(int fd, const char *ifname, int verbose) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if (ioctl(fd, SIOCGIFINDEX, &ifr) == -1) { + if (verbose) + SCLogError(SC_ERR_AFP_CREATE, "Unable to find iface %s: %s", + ifname, strerror(errno)); + return -1; + } + + return ifr.ifr_ifindex; +} + +static int getdevlinktype(int fd, const char *ifname) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) { + SCLogError(SC_ERR_AFP_CREATE, "Unable to find type for iface \"%s\": %s", + ifname, strerror(errno)); + return -1; + } + + return ifr.ifr_hwaddr.sa_family; +} + +static int createsocket(AFPThreadVars *ptv, char *devname, int verbose) +{ + int r; + struct packet_mreq sock_params; + struct sockaddr_ll bind_address; + /* open socket */ + ptv->socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (ptv->socket == -1) { + SCLogError(SC_ERR_AFP_CREATE, "Coudn't create a AF_PACKET socket, error %s", strerror(errno)); + return -1; + } + SCLogInfo("using interface %s", (char *)devname); + /* bind socket */ + memset(&bind_address, 0, sizeof(bind_address)); + bind_address.sll_family = AF_PACKET; + bind_address.sll_protocol = htons(ETH_P_ALL); + bind_address.sll_ifindex = getifnumbydev(ptv->socket, devname, verbose); + if (bind_address.sll_ifindex == -1) { + if (verbose) + SCLogError(SC_ERR_AFP_CREATE, "Coudn't find iface %s", devname); + return -1; + } + r = bind(ptv->socket, (struct sockaddr *)&bind_address, sizeof(bind_address)); + if (r < 0) { + if (verbose) { + if (errno == ENETDOWN) { + SCLogError(SC_ERR_AFP_CREATE, + "Couldn't bind AF_PACKET socket, iface %s is down", + devname); + } else { + SCLogError(SC_ERR_AFP_CREATE, + "Couldn't bind AF_PACKET socket to iface %s, error %s", + devname, + strerror(errno)); + } + } + close(ptv->socket); + return -1; + } + /* Force promiscuous mode */ + memset(&sock_params, 0, sizeof(sock_params)); + sock_params.mr_type = PACKET_MR_PROMISC; + sock_params.mr_ifindex = bind_address.sll_ifindex; + r = setsockopt(ptv->socket, SOL_PACKET, PACKET_ADD_MEMBERSHIP,(void *)&sock_params, sizeof(sock_params)); + if (r < 0) { + SCLogError(SC_ERR_AFP_CREATE, + "Coudn't switch iface %s to promiscuous, error %s", + devname, + strerror(errno)); + return -1; + } +#ifdef HAVE_PACKET_FANOUT + /* add binded socket to fanout group */ + if (ptv->fanout) { + uint32_t option = 0; + uint16_t mode = PACKET_FANOUT_HASH; + uint16_t id = 1; + option = (mode << 16) | (id & 0xffff); + r = setsockopt(ptv->socket, SOL_PACKET, PACKET_FANOUT,(void *)&option, sizeof(option)); + if (r < 0) { + SCLogError(SC_ERR_AFP_CREATE, + "Coudn't set fanout mode, error %s", + strerror(errno)); + return -1; + } + } +#endif + + ptv->afp_state = AFP_STATE_UP; + return 0; +} + + +/** + * \brief Init function for ReceiveAFP. + * + * \param tv pointer to ThreadVars + * \param initdata pointer to the interface passed from the user + * \param data pointer gets populated with AFPThreadVars + * + * \todo Create a general AFP setup function. + */ +TmEcode ReceiveAFPThreadInit(ThreadVars *tv, void *initdata, void **data) { + SCEnter(); + int r; + int value; + + /* use max_pending_packets as AFP read size unless it's bigger than + * our size limit */ + afp_max_read_packets = (AFP_FILE_MAX_PKTS < max_pending_packets) ? + AFP_FILE_MAX_PKTS : max_pending_packets; + + if (initdata == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "initdata == NULL"); + SCReturnInt(TM_ECODE_FAILED); + } + + AFPThreadVars *ptv = SCMalloc(sizeof(AFPThreadVars)); + if (ptv == NULL) + SCReturnInt(TM_ECODE_FAILED); + memset(ptv, 0, sizeof(AFPThreadVars)); + + ptv->tv = tv; + ptv->cooked = 0; + + strncpy(ptv->iface, initdata, AFP_IFACE_NAME_LENGTH); + ptv->iface[AFP_IFACE_NAME_LENGTH - 1]= '\0'; + + r = createsocket(ptv, initdata, 1); + if (r < 0) { + SCLogError(SC_ERR_AFP_CREATE, "Coudn't init AF_PACKET socket"); + SCFree(ptv); + SCReturnInt(TM_ECODE_FAILED); + } + + ptv->datalink = getdevlinktype(ptv->socket, ptv->iface); + switch (ptv->datalink) { + case ARPHRD_PPP: + case ARPHRD_ATM: + ptv->cooked = 1; + } + + if ((ConfGetBool("af-packet.fanout", &value)) == 1) { + ptv->fanout = value; + } else { + ptv->fanout = 0; + } + + *data = (void *)ptv; + 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 AFPThreadVars for ptv + */ +void ReceiveAFPThreadExitStats(ThreadVars *tv, void *data) { + SCEnter(); + AFPThreadVars *ptv = (AFPThreadVars *)data; +/** +* \todo Counter output +*/ +} + +/** + * \brief DeInit function closes af packet socket at exit. + * \param tv pointer to ThreadVars + * \param data pointer that gets cast into AFPThreadVars for ptv + */ +TmEcode ReceiveAFPThreadDeinit(ThreadVars *tv, void *data) { + AFPThreadVars *ptv = (AFPThreadVars *)data; + + close(ptv->socket); + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief This function passes off to link type decoders. + * + * DecodeAFP 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 AFPThreadVars for ptv + * \param pq pointer to the current PacketQueue + */ +TmEcode DecodeAFP(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq) +{ + SCEnter(); + DecodeThreadVars *dtv = (DecodeThreadVars *)data; + + /* update counters */ + SCPerfCounterIncr(dtv->counter_pkts, tv->sc_perf_pca); + SCPerfCounterIncr(dtv->counter_pkts_per_sec, tv->sc_perf_pca); + + SCPerfCounterAddUI64(dtv->counter_bytes, tv->sc_perf_pca, GET_PKT_LEN(p)); +#if 0 + SCPerfCounterAddDouble(dtv->counter_bytes_per_sec, tv->sc_perf_pca, GET_PKT_LEN(p)); + SCPerfCounterAddDouble(dtv->counter_mbit_per_sec, tv->sc_perf_pca, + (GET_PKT_LEN(p) * 8)/1000000.0); +#endif + + 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)); + + /* call the decoder */ + switch(p->datalink) { + case LINKTYPE_LINUX_SLL: + DecodeSll(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq); + break; + case LINKTYPE_ETHERNET: + DecodeEthernet(tv, dtv, p,GET_PKT_DATA(p), GET_PKT_LEN(p), pq); + break; + case LINKTYPE_PPP: + DecodePPP(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq); + break; + case LINKTYPE_RAW: + DecodeRaw(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq); + break; + default: + SCLogError(SC_ERR_DATALINK_UNIMPLEMENTED, "Error: datalink type %" PRId32 " not yet supported in module DecodeAFP", p->datalink); + break; + } + + SCReturnInt(TM_ECODE_OK); +} + +TmEcode DecodeAFPThreadInit(ThreadVars *tv, void *initdata, void **data) +{ + SCEnter(); + DecodeThreadVars *dtv = NULL; + + dtv = DecodeThreadVarsAlloc(); + + if (dtv == NULL) + SCReturnInt(TM_ECODE_FAILED); + + DecodeRegisterPerfCounters(dtv, tv); + + *data = (void *)dtv; + + SCReturnInt(TM_ECODE_OK); +} + +/* eof */ diff --git a/src/source-af-packet.h b/src/source-af-packet.h new file mode 100644 index 0000000000..e7119e487f --- /dev/null +++ b/src/source-af-packet.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2011 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Eric Leblond + */ + +#ifndef __SOURCE_AFP_H__ +#define __SOURCE_AFP_H__ + +void TmModuleReceiveAFPRegister (void); +void TmModuleDecodeAFPRegister (void); + +#endif /* __SOURCE_AFP_H__ */ diff --git a/src/suricata.c b/src/suricata.c index 18a0e13b94..9d2cab17fe 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -100,6 +100,8 @@ #include "source-erf-file.h" #include "source-erf-dag.h" +#include "source-af-packet.h" + #include "respond-reject.h" #include "flow.h" @@ -390,6 +392,8 @@ void usage(const char *progname) printf("USAGE: %s\n\n", progname); printf("\t-c : path to configuration file\n"); printf("\t-i : run in pcap live mode\n"); +/* TODO add condition ifdef for af_packet */ + printf("\t-a : run in af-packet mode\n"); printf("\t-F : bpf filter file\n"); printf("\t-r : run in pcap file/offline mode\n"); #ifdef NFQ @@ -658,7 +662,7 @@ int main(int argc, char **argv) /* getopt_long stores the option index here. */ int option_index = 0; - char short_opts[] = "c:Dhi:l:q:d:r:us:U:VF:"; + char short_opts[] = "c:Dhi:l:q:d:r:us:U:VF:a:"; while ((opt = getopt_long(argc, argv, short_opts, long_opts, &option_index)) != -1) { switch (opt) { @@ -849,6 +853,30 @@ int main(int argc, char **argv) SCLogWarning(SC_WARN_PCAP_MULTI_DEV_EXPERIMENTAL, "using " "multiple pcap devices to get packets is experimental."); PcapLiveRegisterDevice(optarg); +#endif + } else { + SCLogError(SC_ERR_MULTIPLE_RUN_MODE, "more than one run mode " + "has been specified"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + memset(pcap_dev, 0, sizeof(pcap_dev)); + strlcpy(pcap_dev, optarg, ((strlen(optarg) < sizeof(pcap_dev)) ? (strlen(optarg)+1) : (sizeof(pcap_dev)))); + break; + case 'a': + /* TODO fix parasiting of pcap mode */ + if (run_mode == RUNMODE_UNKNOWN) { + run_mode = RUNMODE_AFP_DEV; + PcapLiveRegisterDevice(optarg); + } else if (run_mode == RUNMODE_AFP_DEV) { +#ifdef OS_WIN32 + SCLogError(SC_ERR_PCAP_MULTI_DEV_NO_SUPPORT, "pcap multi dev " + "support is not (yet) supported on Windows."); + exit(EXIT_FAILURE); +#else + SCLogWarning(SC_WARN_PCAP_MULTI_DEV_EXPERIMENTAL, "using " + "multiple pcap devices to get packets is experimental."); + PcapLiveRegisterDevice(optarg); #endif } else { SCLogError(SC_ERR_MULTIPLE_RUN_MODE, "more than one run mode " @@ -1028,6 +1056,7 @@ int main(int argc, char **argv) if (ConfGetInt("default-packet-size", &default_packet_size) != 1) { switch (run_mode) { case RUNMODE_PCAP_DEV: + case RUNMODE_AFP_DEV: case RUNMODE_PFRING: /* find payload for interface and use it */ default_packet_size = GetIfaceMaxPayloadSize(pcap_dev); @@ -1111,6 +1140,8 @@ int main(int argc, char **argv) TmModuleDecodeIPFWRegister(); TmModuleReceivePcapRegister(); TmModuleDecodePcapRegister(); + TmModuleReceiveAFPRegister(); + TmModuleDecodeAFPRegister(); TmModuleReceivePfringRegister(); TmModuleDecodePfringRegister(); TmModuleReceivePcapFileRegister(); @@ -1383,6 +1414,13 @@ int main(int argc, char **argv) } else if (run_mode == RUNMODE_PFRING) { PfringLoadConfig(); #endif /* HAVE_PFRING */ + } else if (run_mode == RUNMODE_AFP_DEV) { + /* TODO fix parasiting */ + PcapTranslateIPToDevice(pcap_dev, sizeof(pcap_dev)); + if (ConfSet("pcap.single_pcap_dev", pcap_dev, 0) != 1) { + fprintf(stderr, "ERROR: Failed to set pcap.single_pcap_dev\n"); + exit(EXIT_FAILURE); + } } RunModeDispatch(run_mode, runmode_custom_mode, de_ctx); diff --git a/src/tm-threads-common.h b/src/tm-threads-common.h index 2b2a3eaab2..7b075daeb9 100644 --- a/src/tm-threads-common.h +++ b/src/tm-threads-common.h @@ -70,6 +70,8 @@ typedef enum { TMM_DECODEERFFILE, TMM_RECEIVEERFDAG, TMM_DECODEERFDAG, + TMM_RECEIVEAFP, + TMM_DECODEAFP, TMM_SIZE, } TmmId; diff --git a/src/util-error.h b/src/util-error.h index 545c7cc1ca..805bd3d5f4 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -217,6 +217,9 @@ typedef enum { SC_ERR_RUNMODE, SC_ERR_SHUTDOWN, SC_ERR_INVALID_DIRECTION, + SC_ERR_AFP_CREATE, + SC_ERR_AFP_READ, + SC_ERR_AFP_DISPATCH, } SCError; const char *SCErrorToString(SCError); diff --git a/suricata.yaml b/suricata.yaml index c97d5162d4..8398025b01 100644 --- a/suricata.yaml +++ b/suricata.yaml @@ -154,6 +154,26 @@ nfq: # repeat_mask: 1 # route_queue: 2 +# af-packet support +# Set threads to > 1 to use PACKET_FANOUT support +af-packet: + # Number of receive threads (>1 will enable experimental flow pinned + # runmode) + threads: 1 + + # Default interface we will listen on. + interface: eth0 + + # Default clusterid. AF_PACKET will load balance packets based on flow. + # All threads/processes that will participate need to have the same + # clusterid. + cluster-id: 99 + + # Default AF_PACKET cluster type. AF_PACKET can load balance per flow or per hash. + # This is only supported for Linux kernel > 3.1 + # possible value are: "cluster_round_robin" and "cluster_flow" + cluster-type: cluster_round_robin + defrag: max-frags: 65535 prealloc: yes