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-nflog.c

558 lines
17 KiB
C

/* 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 Giuseppe Longo <giuseppelng@gmail.com>
*
* Netfilter's netfilter_log support
*/
#include "suricata-common.h"
#include "suricata.h"
#include "decode.h"
#include "packet-queue.h"
#include "threads.h"
#include "threadvars.h"
#include "tm-threads.h"
#include "tm-modules.h"
#include "tm-queuehandlers.h"
#include "tmqh-packetpool.h"
#include "runmodes.h"
#include "util-error.h"
#include "util-device.h"
#ifndef HAVE_NFLOG
/** Handle the case where no NFLOG support is compiled in.
*
*/
TmEcode NoNFLOGSupportExit(ThreadVars *, void *, void **);
void TmModuleReceiveNFLOGRegister (void)
{
tmm_modules[TMM_RECEIVENFLOG].name = "ReceiveNFLOG";
tmm_modules[TMM_RECEIVENFLOG].ThreadInit = NoNFLOGSupportExit;
}
void TmModuleDecodeNFLOGRegister (void)
{
tmm_modules[TMM_DECODENFLOG].name = "DecodeNFLOG";
tmm_modules[TMM_DECODENFLOG].ThreadInit = NoNFLOGSupportExit;
}
TmEcode NoNFLOGSupportExit(ThreadVars *tv, void *initdata, void **data)
{
SCLogError(SC_ERR_NFLOG_NOSUPPORT,"Error creating thread %s: you do not have support for nflog "
"enabled please recompile with --enable-nflog", tv->name);
exit(EXIT_FAILURE);
}
#else /* implied we do have NFLOG support */
#include "source-nflog.h"
TmEcode ReceiveNFLOGThreadInit(ThreadVars *, void *, void **);
TmEcode ReceiveNFLOGThreadDeinit(ThreadVars *, void *);
TmEcode ReceiveNFLOGLoop(ThreadVars *, void *, void *);
void ReceiveNFLOGThreadExitStats(ThreadVars *, void *);
TmEcode DecodeNFLOGThreadInit(ThreadVars *, void *, void **);
TmEcode DecodeNFLOGThreadDeinit(ThreadVars *tv, void *data);
TmEcode DecodeNFLOG(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
static int runmode_workers;
/* Structure to hold thread specific variables */
typedef struct NFLOGThreadVars_ {
ThreadVars *tv;
TmSlot *slot;
char *data;
int datalen;
uint16_t group;
uint32_t nlbufsiz;
uint32_t nlbufsiz_max;
uint32_t qthreshold;
uint32_t qtimeout;
struct nflog_handle *h;
struct nflog_g_handle *gh;
LiveDevice *livedev;
int nful_overrun_warned;
/* counters */
uint32_t pkts;
uint64_t bytes;
uint32_t errs;
uint16_t capture_kernel_packets;
uint16_t capture_kernel_drops;
} NFLOGThreadVars;
/**
* \brief Registration function for ReceiveNFLOG
*/
void TmModuleReceiveNFLOGRegister (void)
{
tmm_modules[TMM_RECEIVENFLOG].name = "ReceiveNFLOG";
tmm_modules[TMM_RECEIVENFLOG].ThreadInit = ReceiveNFLOGThreadInit;
tmm_modules[TMM_RECEIVENFLOG].Func = NULL;
tmm_modules[TMM_RECEIVENFLOG].PktAcqLoop = ReceiveNFLOGLoop;
tmm_modules[TMM_RECEIVENFLOG].ThreadExitPrintStats = ReceiveNFLOGThreadExitStats;
tmm_modules[TMM_RECEIVENFLOG].ThreadDeinit = ReceiveNFLOGThreadDeinit;
tmm_modules[TMM_RECEIVENFLOG].RegisterTests = NULL;
tmm_modules[TMM_RECEIVENFLOG].flags = TM_FLAG_RECEIVE_TM;
}
/**
* \brief Registration function for DecodeNFLOG
*/
void TmModuleDecodeNFLOGRegister (void)
{
tmm_modules[TMM_DECODENFLOG].name = "DecodeNFLOG";
tmm_modules[TMM_DECODENFLOG].ThreadInit = DecodeNFLOGThreadInit;
tmm_modules[TMM_DECODENFLOG].Func = DecodeNFLOG;
tmm_modules[TMM_DECODENFLOG].ThreadExitPrintStats = NULL;
tmm_modules[TMM_DECODENFLOG].ThreadDeinit = DecodeNFLOGThreadDeinit;
tmm_modules[TMM_DECODENFLOG].RegisterTests = NULL;
tmm_modules[TMM_DECODENFLOG].flags = TM_FLAG_DECODE_TM;
}
/**
* \brief NFLOG callback function
* This function setup a packet from a nflog message
*/
static int NFLOGCallback(struct nflog_g_handle *gh, struct nfgenmsg *msg,
struct nflog_data *nfa, void *data)
{
NFLOGThreadVars *ntv = (NFLOGThreadVars *) data;
struct nfulnl_msg_packet_hdr *ph;
char *payload;
int ret;
/* grab a packet*/
Packet *p = PacketGetFromQueueOrAlloc();
if (p == NULL)
return -1;
PKT_SET_SRC(p, PKT_SRC_WIRE);
ph = nflog_get_msg_packet_hdr(nfa);
if (ph != NULL) {
p->nflog_v.hw_protocol = ph->hw_protocol;
}
p->nflog_v.ifi = nflog_get_indev(nfa);
p->nflog_v.ifo = nflog_get_outdev(nfa);
ret = nflog_get_payload(nfa, &payload);
if (ret > 0) {
if (ret > 65536) {
SCLogWarning(SC_ERR_INVALID_ARGUMENTS, "NFLOG sent too big packet");
SET_PKT_LEN(p, 0);
} else if (runmode_workers)
PacketSetData(p, (uint8_t *)payload, ret);
else
PacketCopyData(p, (uint8_t *)payload, ret);
} else if (ret == -1)
SET_PKT_LEN(p, 0);
ret = nflog_get_timestamp(nfa, &p->ts);
if (ret != 0) {
memset(&p->ts, 0, sizeof(struct timeval));
gettimeofday(&p->ts, NULL);
}
p->datalink = DLT_RAW;
#ifdef COUNTERS
ntv->pkts++;
ntv->bytes += GET_PKT_LEN(p);
#endif
(void) SC_ATOMIC_ADD(ntv->livedev->pkts, 1);
if (TmThreadsSlotProcessPkt(ntv->tv, ntv->slot, p) != TM_ECODE_OK) {
TmqhOutputPacketpool(ntv->tv, p);
return -1;
}
return 0;
}
/**
* \brief Receives packet from a nflog group via libnetfilter_log
* This is a setup function for recieving packets via libnetfilter_log.
* \param tv pointer to ThreadVars
* \param initdata pointer to the group passed from the user
* \param data pointer gets populated with NFLOGThreadVars
* \retvalTM_ECODE_OK on success
* \retval TM_ECODE_FAILED on error
*/
TmEcode ReceiveNFLOGThreadInit(ThreadVars *tv, void *initdata, void **data)
{
NflogGroupConfig *nflconfig = initdata;
if (initdata == NULL) {
SCLogError(SC_ERR_INVALID_ARGUMENT, "initdata == NULL");
SCReturnInt(TM_ECODE_FAILED);
}
NFLOGThreadVars *ntv = SCMalloc(sizeof(NFLOGThreadVars));
if (unlikely(ntv == NULL)) {
nflconfig->DerefFunc(nflconfig);
SCReturnInt(TM_ECODE_FAILED);
}
memset(ntv, 0, sizeof(NFLOGThreadVars));
ntv->tv = tv;
ntv->group = nflconfig->group;
ntv->nlbufsiz = nflconfig->nlbufsiz;
ntv->nlbufsiz_max = nflconfig->nlbufsiz_max;
ntv->qthreshold = nflconfig->qthreshold;
ntv->qtimeout = nflconfig->qtimeout;
ntv->nful_overrun_warned = nflconfig->nful_overrun_warned;
ntv->h = nflog_open();
if (ntv->h == NULL) {
SCLogError(SC_ERR_NFLOG_OPEN, "nflog_open() failed");
SCFree(ntv);
return TM_ECODE_FAILED;
}
SCLogDebug("binding netfilter_log as nflog handler for AF_INET and AF_INET6");
if (nflog_bind_pf(ntv->h, AF_INET) < 0) {
SCLogError(SC_ERR_NFLOG_BIND, "nflog_bind_pf() for AF_INET failed");
exit(EXIT_FAILURE);
}
if (nflog_bind_pf(ntv->h, AF_INET6) < 0) {
SCLogError(SC_ERR_NFLOG_BIND, "nflog_bind_pf() for AF_INET6 failed");
exit(EXIT_FAILURE);
}
ntv->gh = nflog_bind_group(ntv->h, ntv->group);
if (!ntv->gh) {
SCLogError(SC_ERR_NFLOG_OPEN, "nflog_bind_group() failed");
SCFree(ntv);
return TM_ECODE_FAILED;
}
if (nflog_set_mode(ntv->gh, NFULNL_COPY_PACKET, 0xFFFF) < 0) {
SCLogError(SC_ERR_NFLOG_SET_MODE, "can't set packet_copy mode");
SCFree(ntv);
return TM_ECODE_FAILED;
}
nflog_callback_register(ntv->gh, &NFLOGCallback, (void *)ntv);
if (ntv->nlbufsiz < ntv->nlbufsiz_max)
ntv->nlbufsiz = nfnl_rcvbufsiz(nflog_nfnlh(ntv->h), ntv->nlbufsiz);
else {
SCLogError(SC_ERR_NFLOG_MAX_BUFSIZ, "Maximum buffer size (%d) in NFLOG "
"has been reached", ntv->nlbufsiz);
return TM_ECODE_FAILED;
}
if (nflog_set_qthresh(ntv->gh, ntv->qthreshold) >= 0)
SCLogDebug("NFLOG netlink queue threshold has been set to %d",
ntv->qthreshold);
else
SCLogDebug("NFLOG netlink queue threshold can't be set to %d",
ntv->qthreshold);
if (nflog_set_timeout(ntv->gh, ntv->qtimeout) >= 0)
SCLogDebug("NFLOG netlink queue timeout has been set to %d",
ntv->qtimeout);
else
SCLogDebug("NFLOG netlink queue timeout can't be set to %d",
ntv->qtimeout);
ntv->livedev = LiveGetDevice(nflconfig->numgroup);
if (ntv->livedev == NULL) {
SCLogError(SC_ERR_INVALID_VALUE, "Unable to find Live device");
SCFree(ntv);
SCReturnInt(TM_ECODE_FAILED);
}
/* set a timeout to the socket so we can check for a signal
* in case we don't get packets for a longer period. */
struct timeval timev;
timev.tv_sec = 1;
timev.tv_usec = 0;
int fd = nflog_fd(ntv->h);
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)) == -1) {
SCLogWarning(SC_WARN_NFLOG_SETSOCKOPT, "can't set socket "
"timeout: %s", strerror(errno));
}
#ifdef PACKET_STATISTICS
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");
#endif
char *active_runmode = RunmodeGetActive();
if (active_runmode && !strcmp("workers", active_runmode))
runmode_workers = 1;
else
runmode_workers = 0;
#define T_DATA_SIZE 70000
ntv->data = SCMalloc(T_DATA_SIZE);
if (ntv->data == NULL) {
nflconfig->DerefFunc(nflconfig);
SCFree(ntv);
SCReturnInt(TM_ECODE_FAILED);
}
ntv->datalen = T_DATA_SIZE;
#undef T_DATA_SIZE
*data = (void *)ntv;
nflconfig->DerefFunc(nflconfig);
SCReturnInt(TM_ECODE_OK);
}
/**
* \brief DeInit function unbind group and close nflog's handle
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into NFLogThreadVars
* \retval TM_ECODE_OK is always returned
*/
TmEcode ReceiveNFLOGThreadDeinit(ThreadVars *tv, void *data)
{
NFLOGThreadVars *ntv = (NFLOGThreadVars *)data;
SCLogDebug("closing nflog group %d", ntv->group);
if (nflog_unbind_pf(ntv->h, AF_INET) < 0) {
SCLogError(SC_ERR_NFLOG_UNBIND, "nflog_unbind_pf() for AF_INET failed");
exit(EXIT_FAILURE);
}
if (nflog_unbind_pf(ntv->h, AF_INET6) < 0) {
SCLogError(SC_ERR_NFLOG_UNBIND, "nflog_unbind_pf() for AF_INET6 failed");
exit(EXIT_FAILURE);
}
if (ntv->gh) {
nflog_unbind_group(ntv->gh);
ntv->gh = NULL;
}
if (ntv->h) {
nflog_close(ntv->h);
ntv->h = NULL;
}
SCReturnInt(TM_ECODE_OK);
}
/**
* \brief Increases netlink buffer size
*
* This function netlink's buffer size until
* the max buffer size is reached
*
* \param data pointer that gets cast into NFLOGThreadVars
* \param size netlink buffer size
*/
static int NFLOGSetnlbufsiz(void *data, unsigned int size)
{
SCEnter();
NFLOGThreadVars *ntv = (NFLOGThreadVars *)data;
if (size < ntv->nlbufsiz_max) {
ntv->nlbufsiz = nfnl_rcvbufsiz(nflog_nfnlh(ntv->h), ntv->nlbufsiz);
return 1;
}
SCLogWarning(SC_WARN_NFLOG_MAXBUFSIZ_REACHED,
"Maximum buffer size (%d) in NFLOG has been "
"reached. Please, consider raising "
"`buffer-size` and `max-size` in nflog configuration",
ntv->nlbufsiz);
return 0;
}
/**
* \brief Recieves packets from a group via libnetfilter_log.
*
* This function recieves packets from a group and passes
* the packet on to the nflog callback function.
*
* \param tv pointer to ThreadVars
* \param data pointer that gets cast into NFLOGThreadVars
* \param slot slot containing task information
* \retval TM_ECODE_OK on success
* \retval TM_ECODE_FAILED on failure
*/
TmEcode ReceiveNFLOGLoop(ThreadVars *tv, void *data, void *slot)
{
SCEnter();
NFLOGThreadVars *ntv = (NFLOGThreadVars *)data;
int rv, fd;
int ret = -1;
ntv->slot = ((TmSlot *) slot)->slot_next;
fd = nflog_fd(ntv->h);
if (fd < 0) {
SCLogError(SC_ERR_NFLOG_FD, "Can't obtain a file descriptor");
SCReturnInt(TM_ECODE_FAILED);
}
while (1) {
if (suricata_ctl_flags != 0)
break;
rv = recv(fd, ntv->data, ntv->datalen, 0);
if (rv < 0) {
/*We received an error on socket read */
if (errno == EINTR || errno == EWOULDBLOCK) {
/*Nothing for us to process */
continue;
} else if (errno == ENOBUFS) {
if (!ntv->nful_overrun_warned) {
int s = ntv->nlbufsiz * 2;
if (NFLOGSetnlbufsiz((void *)ntv, s)) {
SCLogWarning(SC_WARN_NFLOG_LOSING_EVENTS,
"We are losing events, "
"increasing buffer size "
"to %d", ntv->nlbufsiz);
} else {
ntv->nful_overrun_warned = 1;
}
}
continue;
} else {
SCLogWarning(SC_WARN_NFLOG_RECV,
"Read from NFLOG fd failed: %s",
strerror(errno));
SCReturnInt(TM_ECODE_FAILED);
}
}
ret = nflog_handle_packet(ntv->h, ntv->data, rv);
if (ret != 0)
SCLogWarning(SC_ERR_NFLOG_HANDLE_PKT,
"nflog_handle_packet error %" PRId32 "", ret);
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 NFLOGThreadVars
*/
void ReceiveNFLOGThreadExitStats(ThreadVars *tv, void *data)
{
SCEnter();
NFLOGThreadVars *ntv = (NFLOGThreadVars *)data;
SCLogNotice("(%s) Pkts %" PRIu32 ", Bytes %" PRIu64 "",
tv->name, ntv->pkts, ntv->bytes);
}
/**
* \brief Decode IPv4/v6 packets.
*
* \param tv pointer to ThreadVars
* \param p pointer to the current packet
* \param data pointer that gets cast into NFLOGThreadVars for ptv
* \param pq pointer to the current PacketQueue
*
* \retval TM_ECODE_OK is always returned
*/
TmEcode DecodeNFLOG(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
{
SCEnter();
IPV4Hdr *ip4h = (IPV4Hdr *)GET_PKT_DATA(p);
IPV6Hdr *ip6h = (IPV6Hdr *)GET_PKT_DATA(p);
DecodeThreadVars *dtv = (DecodeThreadVars *)data;
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));
if (IPV4_GET_RAW_VER(ip4h) == 4) {
SCLogDebug("IPv4 packet");
DecodeIPV4(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq);
} else if(IPV6_GET_RAW_VER(ip6h) == 6) {
SCLogDebug("IPv6 packet");
DecodeIPV6(tv, dtv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq);
} else {
SCLogDebug("packet unsupported by NFLOG, first byte: %02x", *GET_PKT_DATA(p));
}
PacketDecodeFinalize(tv, dtv, p);
SCReturnInt(TM_ECODE_OK);
}
/**
* \brief This an Init function for DecodeNFLOG
*
* \param tv pointer to ThreadVars
* \param initdata pointer to initilization data.
* \param data pointer that gets cast into NFLOGThreadVars
* \retval TM_ECODE_OK is returned on success
* \retval TM_ECODE_FAILED is returned on error
*/
TmEcode DecodeNFLOGThreadInit(ThreadVars *tv, void *initdata, void **data)
{
DecodeThreadVars *dtv = NULL;
dtv = DecodeThreadVarsAlloc(tv);
if (dtv == NULL)
SCReturnInt(TM_ECODE_FAILED);
DecodeRegisterPerfCounters(dtv, tv);
*data = (void *)dtv;
SCReturnInt(TM_ECODE_OK);
}
TmEcode DecodeNFLOGThreadDeinit(ThreadVars *tv, void *data)
{
if (data != NULL)
DecodeThreadVarsFree(tv, data);
SCReturnInt(TM_ECODE_OK);
}
#endif /* NFLOG */