/* Copyright (C) 2007-2019 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 Victor Julien <victor@inliniac.net>
 *
 * Live pcap packet acquisition support
 */

#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-threads.h"
#include "source-pcap.h"
#include "conf.h"
#include "util-bpf.h"
#include "util-debug.h"
#include "util-error.h"
#include "util-privs.h"
#include "util-datalink.h"
#include "util-device-private.h"
#include "util-optimize.h"
#include "util-checksum.h"
#include "util-ioctl.h"
#include "util-time.h"
#include "tmqh-packetpool.h"

#define PCAP_STATE_DOWN 0
#define PCAP_STATE_UP 1

#define PCAP_RECONNECT_TIMEOUT 500000

/**
 * \brief 64bit pcap stats counters.
 *
 * libpcap only supports 32bit counters. They will eventually wrap around.
 *
 * Keep track of libpcap counters as 64bit counters to keep on counting even
 * if libpcap's 32bit counters wrap around.
 * Requires pcap_stats() to be called before 32bit stats wrap around twice,
 * which we do.
 */
typedef struct PcapStats64_ {
    uint64_t ps_recv;
    uint64_t ps_drop;
    uint64_t ps_ifdrop;
} PcapStats64;

/**
 * \brief Structure to hold thread specific variables.
 */
typedef struct PcapThreadVars_
{
    /* thread specific handle */
    pcap_t *pcap_handle;
    /* handle state */
    unsigned char pcap_state;
    /* thread specific bpf */
    struct bpf_program filter;
    /* ptr to string from config */
    const char *bpf_filter;

    time_t last_stats_dump;

    /* data link type for the thread */
    int datalink;

    /* counters */
    uint64_t pkts;
    uint64_t bytes;

    uint16_t capture_kernel_packets;
    uint16_t capture_kernel_drops;
    uint16_t capture_kernel_ifdrops;

    ThreadVars *tv;
    TmSlot *slot;

    /** callback result -- set if one of the thread module failed. */
    int cb_result;

    /* pcap buffer size */
    int pcap_buffer_size;
    int pcap_snaplen;
    int promisc;

    ChecksumValidationMode checksum_mode;

    LiveDevice *livedev;

    PcapStats64 last_stats64;
} PcapThreadVars;

static TmEcode ReceivePcapThreadInit(ThreadVars *, const void *, void **);
static TmEcode ReceivePcapThreadDeinit(ThreadVars *tv, void *data);
static void ReceivePcapThreadExitStats(ThreadVars *, void *);
static TmEcode ReceivePcapLoop(ThreadVars *tv, void *data, void *slot);
static TmEcode ReceivePcapBreakLoop(ThreadVars *tv, void *data);

static TmEcode DecodePcapThreadInit(ThreadVars *, const void *, void **);
static TmEcode DecodePcapThreadDeinit(ThreadVars *tv, void *data);
static TmEcode DecodePcap(ThreadVars *, Packet *, void *);

#ifdef UNITTESTS
static void SourcePcapRegisterTests(void);
#endif

/** protect pcap_compile and pcap_setfilter, as they are not thread safe:
 *  http://seclists.org/tcpdump/2009/q1/62 */
static SCMutex pcap_bpf_compile_lock = SCMUTEX_INITIALIZER;

/**
 * \brief Registration Function for ReceivePcap.
 */
void TmModuleReceivePcapRegister (void)
{
    tmm_modules[TMM_RECEIVEPCAP].name = "ReceivePcap";
    tmm_modules[TMM_RECEIVEPCAP].ThreadInit = ReceivePcapThreadInit;
    tmm_modules[TMM_RECEIVEPCAP].ThreadDeinit = ReceivePcapThreadDeinit;
    tmm_modules[TMM_RECEIVEPCAP].PktAcqLoop = ReceivePcapLoop;
    tmm_modules[TMM_RECEIVEPCAP].PktAcqBreakLoop = ReceivePcapBreakLoop;
    tmm_modules[TMM_RECEIVEPCAP].ThreadExitPrintStats = ReceivePcapThreadExitStats;
    tmm_modules[TMM_RECEIVEPCAP].cap_flags = SC_CAP_NET_RAW;
    tmm_modules[TMM_RECEIVEPCAP].flags = TM_FLAG_RECEIVE_TM;
#ifdef UNITTESTS
    tmm_modules[TMM_RECEIVEPCAP].RegisterTests = SourcePcapRegisterTests;
#endif
}

/**
 * \brief Registration Function for DecodePcap.
 */
void TmModuleDecodePcapRegister (void)
{
    tmm_modules[TMM_DECODEPCAP].name = "DecodePcap";
    tmm_modules[TMM_DECODEPCAP].ThreadInit = DecodePcapThreadInit;
    tmm_modules[TMM_DECODEPCAP].Func = DecodePcap;
    tmm_modules[TMM_DECODEPCAP].ThreadDeinit = DecodePcapThreadDeinit;
    tmm_modules[TMM_DECODEPCAP].flags = TM_FLAG_DECODE_TM;
}

/**
 * \brief Update 64 bit |last| value from |current32| value taking one
 * wrap-around into account.
 */
static inline void UpdatePcapStatsValue64(uint64_t *last, uint32_t current32)
{
    /* uint64_t -> uint32_t is defined behaviour. It slices lower 32bits. */
    uint32_t last32 = (uint32_t)*last;

    /* Branchless code as wrap-around is defined for unsigned */
    *last += (uint32_t)(current32 - last32);

    /* Same calculation as:
    if (likely(current32 >= last32)) {
        *last += current32 - last32;
    } else {
        *last += (1ull << 32) + current32 - last32;
    }
    */
}

/**
 * \brief Update 64 bit |last| stat values with values from |current|
 * 32 bit pcap_stat.
 */
static inline void UpdatePcapStats64(
        PcapStats64 *last, const struct pcap_stat *current)
{
    UpdatePcapStatsValue64(&last->ps_recv, current->ps_recv);
    UpdatePcapStatsValue64(&last->ps_drop, current->ps_drop);
    UpdatePcapStatsValue64(&last->ps_ifdrop, current->ps_ifdrop);
}

static inline void PcapDumpCounters(PcapThreadVars *ptv)
{
    struct pcap_stat pcap_s;
    if (likely((pcap_stats(ptv->pcap_handle, &pcap_s) >= 0))) {
        UpdatePcapStats64(&ptv->last_stats64, &pcap_s);

        StatsSetUI64(ptv->tv, ptv->capture_kernel_packets,
                ptv->last_stats64.ps_recv);
        StatsSetUI64(
                ptv->tv, ptv->capture_kernel_drops, ptv->last_stats64.ps_drop);
        (void)SC_ATOMIC_SET(ptv->livedev->drop, ptv->last_stats64.ps_drop);
        StatsSetUI64(ptv->tv, ptv->capture_kernel_ifdrops,
                ptv->last_stats64.ps_ifdrop);
    }
}

static int PcapOpenInterface(PcapThreadVars *ptv)
{
    const char *iface = ptv->livedev->dev;

    if (ptv->pcap_handle) {
        pcap_close(ptv->pcap_handle);
        ptv->pcap_handle = NULL;
        if (ptv->filter.bf_insns) {
            SCBPFFree(&ptv->filter);
        }
    }

    if (LiveGetOffload() == 0) {
        (void)GetIfaceOffloading(iface, 1, 1);
    } else {
        DisableIfaceOffloading(ptv->livedev, 1, 1);
    }

    char errbuf[PCAP_ERRBUF_SIZE];
    ptv->pcap_handle = pcap_create(iface, errbuf);
    if (ptv->pcap_handle == NULL) {
        if (strlen(errbuf)) {
            SCLogError("%s: could not create a new pcap handler, error %s", iface, errbuf);
        } else {
            SCLogError("%s: could not create a new pcap handler", iface);
        }
        SCReturnInt(TM_ECODE_FAILED);
    }

    if (ptv->pcap_snaplen > 0) {
        /* set Snaplen. Must be called before pcap_activate */
        int pcap_set_snaplen_r = pcap_set_snaplen(ptv->pcap_handle, ptv->pcap_snaplen);
        if (pcap_set_snaplen_r != 0) {
            SCLogError(
                    "%s: could not set snaplen, error: %s", iface, pcap_geterr(ptv->pcap_handle));
            SCReturnInt(TM_ECODE_FAILED);
        }
        SCLogInfo("%s: snaplen set to %d", iface, ptv->pcap_snaplen);
    }

    if (ptv->promisc) {
        /* set Promisc, and Timeout. Must be called before pcap_activate */
        int pcap_set_promisc_r = pcap_set_promisc(ptv->pcap_handle, ptv->promisc);
        if (pcap_set_promisc_r != 0) {
            SCLogError("%s: could not set promisc mode, error %s", iface,
                    pcap_geterr(ptv->pcap_handle));
            SCReturnInt(TM_ECODE_FAILED);
        }
    }

    int pcap_set_timeout_r = pcap_set_timeout(ptv->pcap_handle, LIBPCAP_COPYWAIT);
    if (pcap_set_timeout_r != 0) {
        SCLogError("%s: could not set timeout, error %s", iface, pcap_geterr(ptv->pcap_handle));
        SCReturnInt(TM_ECODE_FAILED);
    }
#ifdef HAVE_PCAP_SET_BUFF
    if (ptv->pcap_buffer_size > 0) {
        SCLogInfo("%s: going to use pcap buffer size of %" PRId32, iface, ptv->pcap_buffer_size);

        int pcap_set_buffer_size_r = pcap_set_buffer_size(ptv->pcap_handle, ptv->pcap_buffer_size);
        if (pcap_set_buffer_size_r != 0) {
            SCLogError("%s: could not set pcap buffer size, error %s", iface,
                    pcap_geterr(ptv->pcap_handle));
            SCReturnInt(TM_ECODE_FAILED);
        }
    }
#endif /* HAVE_PCAP_SET_BUFF */

    /* activate the handle */
    int pcap_activate_r = pcap_activate(ptv->pcap_handle);
    if (pcap_activate_r != 0) {
        SCLogError("%s: could not activate the pcap handler, error %s", iface,
                pcap_geterr(ptv->pcap_handle));
        pcap_close(ptv->pcap_handle);
        ptv->pcap_handle = NULL;
        SCReturnInt(TM_ECODE_FAILED);
    }
    ptv->pcap_state = PCAP_STATE_UP;

    /* set bpf filter if we have one */
    if (ptv->bpf_filter) {
        SCMutexLock(&pcap_bpf_compile_lock);

        if (pcap_compile(ptv->pcap_handle, &ptv->filter, (char *)ptv->bpf_filter, 1, 0) < 0) {
            SCLogError("%s: bpf compilation error %s", iface, pcap_geterr(ptv->pcap_handle));
            SCMutexUnlock(&pcap_bpf_compile_lock);
            return TM_ECODE_FAILED;
        }

        if (pcap_setfilter(ptv->pcap_handle, &ptv->filter) < 0) {
            SCLogError("%s: could not set bpf filter %s", iface, pcap_geterr(ptv->pcap_handle));
            SCMutexUnlock(&pcap_bpf_compile_lock);
            return TM_ECODE_FAILED;
        }

        SCMutexUnlock(&pcap_bpf_compile_lock);
    }

    /* no offloading supported at all */
    (void)GetIfaceOffloading(iface, 1, 1);
    return TM_ECODE_OK;
}

static int PcapTryReopen(PcapThreadVars *ptv)
{
    ptv->pcap_state = PCAP_STATE_DOWN;

    if (PcapOpenInterface(ptv) != TM_ECODE_OK)
        return -1;

    SCLogInfo("%s: interface recovered, state is now \"up\"", ptv->livedev->dev);
    ptv->pcap_state = PCAP_STATE_UP;
    return 0;
}

static void PcapCallbackLoop(char *user, struct pcap_pkthdr *h, u_char *pkt)
{
    SCEnter();

    PcapThreadVars *ptv = (PcapThreadVars *)user;
    Packet *p = PacketGetFromQueueOrAlloc();

    if (unlikely(p == NULL)) {
        SCReturn;
    }

    PKT_SET_SRC(p, PKT_SRC_WIRE);
    p->ts = SCTIME_FROM_TIMEVAL(&h->ts);
    SCLogDebug("p->ts.tv_sec %" PRIuMAX "", (uintmax_t)SCTIME_SECS(p->ts));
    p->datalink = ptv->datalink;

    ptv->pkts++;
    ptv->bytes += h->caplen;
    (void) SC_ATOMIC_ADD(ptv->livedev->pkts, 1);
    p->livedev = ptv->livedev;

    if (unlikely(PacketCopyData(p, pkt, h->caplen))) {
        TmqhOutputPacketpool(ptv->tv, p);
        SCReturn;
    }

    switch (ptv->checksum_mode) {
        case CHECKSUM_VALIDATION_AUTO:
            if (ChecksumAutoModeCheck(ptv->pkts,
                        SC_ATOMIC_GET(ptv->livedev->pkts),
                        SC_ATOMIC_GET(ptv->livedev->invalid_checksums))) {
                ptv->checksum_mode = CHECKSUM_VALIDATION_DISABLE;
                p->flags |= PKT_IGNORE_CHECKSUM;
            }
            break;
        case CHECKSUM_VALIDATION_DISABLE:
            p->flags |= PKT_IGNORE_CHECKSUM;
            break;
        default:
            break;
    }

    if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) {
        pcap_breakloop(ptv->pcap_handle);
        ptv->cb_result = TM_ECODE_FAILED;
    }

    /* Trigger one dump of stats every second */
    SCTime_t current_time = TimeGet();
    if ((time_t)SCTIME_SECS(current_time) != ptv->last_stats_dump) {
        PcapDumpCounters(ptv);
        ptv->last_stats_dump = SCTIME_SECS(current_time);
    }

    SCReturn;
}

#ifndef PCAP_ERROR_BREAK
#define PCAP_ERROR_BREAK -2
#endif

/**
 *  \brief Main PCAP reading Loop function
 */
static TmEcode ReceivePcapLoop(ThreadVars *tv, void *data, void *slot)
{
    SCEnter();

    int packet_q_len = 64;
    PcapThreadVars *ptv = (PcapThreadVars *)data;
    TmSlot *s = (TmSlot *)slot;

    ptv->slot = s->slot_next;
    ptv->cb_result = TM_ECODE_OK;

    // Indicate that the thread is actually running its application level code (i.e., it can poll
    // packets)
    TmThreadsSetFlag(tv, THV_RUNNING);

    while (1) {
        if (suricata_ctl_flags & SURICATA_STOP) {
            SCReturnInt(TM_ECODE_OK);
        }

        /* 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 = pcap_dispatch(ptv->pcap_handle, packet_q_len,
                          (pcap_handler)PcapCallbackLoop, (u_char *)ptv);
        if (unlikely(r == 0 || r == PCAP_ERROR_BREAK || (r > 0 && r < packet_q_len))) {
            if (r == PCAP_ERROR_BREAK && ptv->cb_result == TM_ECODE_FAILED) {
                SCReturnInt(TM_ECODE_FAILED);
            }
            TmThreadsCaptureHandleTimeout(tv, NULL);
        } else if (unlikely(r < 0)) {
            int dbreak = 0;
            SCLogError("error code %" PRId32 " %s", r, pcap_geterr(ptv->pcap_handle));
            do {
                SleepUsec(PCAP_RECONNECT_TIMEOUT);
                if (suricata_ctl_flags != 0) {
                    dbreak = 1;
                    break;
                }
                r = PcapTryReopen(ptv);
            } while (r < 0);
            if (dbreak) {
                break;
            }
        } else if (ptv->cb_result == TM_ECODE_FAILED) {
            SCLogError("Pcap callback PcapCallbackLoop failed");
            SCReturnInt(TM_ECODE_FAILED);
        }

        StatsSyncCountersIfSignalled(tv);
    }

    PcapDumpCounters(ptv);
    StatsSyncCountersIfSignalled(tv);
    SCReturnInt(TM_ECODE_OK);
}

/**
 * \brief PCAP Break Loop function.
 */
static TmEcode ReceivePcapBreakLoop(ThreadVars *tv, void *data)
{
    SCEnter();
    PcapThreadVars *ptv = (PcapThreadVars *)data;
    if (ptv->pcap_handle == NULL) {
        SCReturnInt(TM_ECODE_FAILED);
    }
    pcap_breakloop(ptv->pcap_handle);
    SCReturnInt(TM_ECODE_OK);
}

/**
 * \brief Init function for ReceivePcap.
 *
 * This is a setup function for receiving packets
 * via libpcap. There are two versions of this function
 * depending on the major version of libpcap used.
 * For versions prior to 1.x we use open_pcap_live,
 * for versions 1.x and greater we use pcap_create + pcap_activate.
 *
 * \param tv pointer to ThreadVars
 * \param initdata pointer to the interface passed from the user
 * \param data pointer gets populated with PcapThreadVars
 *
 * \todo Create a general pcap setup function.
 */
static TmEcode ReceivePcapThreadInit(ThreadVars *tv, const void *initdata, void **data)
{
    SCEnter();
    PcapIfaceConfig *pcapconfig = (PcapIfaceConfig *)initdata;

    if (initdata == NULL) {
        SCLogError("initdata == NULL");
        SCReturnInt(TM_ECODE_FAILED);
    }

    PcapThreadVars *ptv = SCCalloc(1, sizeof(PcapThreadVars));
    if (unlikely(ptv == NULL)) {
        pcapconfig->DerefFunc(pcapconfig);
        SCReturnInt(TM_ECODE_FAILED);
    }

    ptv->tv = tv;

    ptv->livedev = LiveGetDevice(pcapconfig->iface);
    if (ptv->livedev == NULL) {
        SCLogError("unable to find Live device");
        ReceivePcapThreadDeinit(tv, ptv);
        SCReturnInt(TM_ECODE_FAILED);
    }

    if (LiveGetOffload() == 0) {
        (void)GetIfaceOffloading((char *)pcapconfig->iface, 1, 1);
    } else {
        DisableIfaceOffloading(ptv->livedev, 1, 1);
    }

    ptv->checksum_mode = pcapconfig->checksum_mode;
    if (ptv->checksum_mode == CHECKSUM_VALIDATION_AUTO) {
        SCLogInfo("%s: running in 'auto' checksum mode. Detection of interface "
                  "state will require %llu packets",
                ptv->livedev->dev, CHECKSUM_SAMPLE_COUNT);
    }

    if (pcapconfig->snaplen == 0) {
        /* We set snaplen if we can get the MTU */
        ptv->pcap_snaplen = GetIfaceMaxPacketSize(ptv->livedev);
    } else {
        ptv->pcap_snaplen = pcapconfig->snaplen;
    }

    ptv->promisc = pcapconfig->promisc;
    ptv->pcap_buffer_size = pcapconfig->buffer_size;
    ptv->bpf_filter = pcapconfig->bpf_filter;

    if (PcapOpenInterface(ptv) != TM_ECODE_OK) {
        ReceivePcapThreadDeinit(tv, ptv);
        pcapconfig->DerefFunc(pcapconfig);
        SCReturnInt(TM_ECODE_FAILED);
    }
    ptv->pcap_state = PCAP_STATE_UP;

    ptv->datalink = pcap_datalink(ptv->pcap_handle);
    DatalinkSetGlobalType(ptv->datalink);

    pcapconfig->DerefFunc(pcapconfig);

    ptv->capture_kernel_packets = StatsRegisterCounter("capture.kernel_packets",
            ptv->tv);
    ptv->capture_kernel_drops = StatsRegisterCounter("capture.kernel_drops",
            ptv->tv);
    ptv->capture_kernel_ifdrops = StatsRegisterCounter("capture.kernel_ifdrops",
            ptv->tv);

    *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 PcapThreadVars for ptv
 */
static void ReceivePcapThreadExitStats(ThreadVars *tv, void *data)
{
    SCEnter();
    PcapThreadVars *ptv = (PcapThreadVars *)data;
    struct pcap_stat pcap_s;

    if (pcap_stats(ptv->pcap_handle, &pcap_s) < 0) {
        SCLogError("%s: failed to get pcap_stats: %s", ptv->livedev->dev,
                pcap_geterr(ptv->pcap_handle));
        SCLogInfo("%s: packets %" PRIu64 ", bytes %" PRIu64 "", ptv->livedev->dev, ptv->pkts,
                ptv->bytes);
    } else {
        SCLogInfo("%s: packets %" PRIu64 ", bytes %" PRIu64 "", ptv->livedev->dev, ptv->pkts,
                ptv->bytes);

        /* these numbers are not entirely accurate as ps_recv contains packets
         * that are still waiting to be processed at exit. ps_drop only contains
         * packets dropped by the driver and not any packets dropped by the interface.
         * Additionally see http://tracker.icir.org/bro/ticket/18
         *
         * Note: ps_recv includes dropped packets and should be considered total.
         * Unless we start to look at ps_ifdrop which isn't supported everywhere.
         */
        UpdatePcapStats64(&ptv->last_stats64, &pcap_s);
        float drop_percent =
                likely(ptv->last_stats64.ps_recv > 0)
                        ? (((float)ptv->last_stats64.ps_drop) /
                                  (float)ptv->last_stats64.ps_recv) *
                                  100
                        : 0;
        SCLogInfo("%s: pcap total:%" PRIu64 " recv:%" PRIu64 " drop:%" PRIu64 " (%02.1f%%)",
                ptv->livedev->dev, ptv->last_stats64.ps_recv,
                ptv->last_stats64.ps_recv - ptv->last_stats64.ps_drop, ptv->last_stats64.ps_drop,
                drop_percent);
    }
}

static TmEcode ReceivePcapThreadDeinit(ThreadVars *tv, void *data)
{
    SCEnter();
    PcapThreadVars *ptv = (PcapThreadVars *)data;
    if (ptv != NULL) {
        if (ptv->pcap_handle != NULL) {
            pcap_close(ptv->pcap_handle);
        }
        if (ptv->filter.bf_insns) {
            SCBPFFree(&ptv->filter);
        }
        SCFree(ptv);
    }
    SCReturnInt(TM_ECODE_OK);
}

/**
 * \brief This function passes off to link type decoders.
 *
 * DecodePcap decodes packets from libpcap 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 PcapThreadVars for ptv
 */
static TmEcode DecodePcap(ThreadVars *tv, Packet *p, void *data)
{
    SCEnter();
    DecodeThreadVars *dtv = (DecodeThreadVars *)data;

    BUG_ON(PKT_IS_PSEUDOPKT(p));

    /* update counters */
    DecodeUpdatePacketCounters(tv, dtv, p);

    DecodeLinkLayer(tv, dtv, p->datalink, p, GET_PKT_DATA(p), GET_PKT_LEN(p));

    PacketDecodeFinalize(tv, dtv, p);

    SCReturnInt(TM_ECODE_OK);
}

static TmEcode DecodePcapThreadInit(ThreadVars *tv, const void *initdata, void **data)
{
    SCEnter();

    DecodeThreadVars *dtv = DecodeThreadVarsAlloc(tv);
    if (dtv == NULL)
        SCReturnInt(TM_ECODE_FAILED);

    DecodeRegisterPerfCounters(dtv, tv);

    *data = (void *)dtv;

    SCReturnInt(TM_ECODE_OK);
}

static TmEcode DecodePcapThreadDeinit(ThreadVars *tv, void *data)
{
    if (data != NULL)
        DecodeThreadVarsFree(tv, data);
    SCReturnInt(TM_ECODE_OK);
}

void PcapTranslateIPToDevice(char *pcap_dev, size_t len)
{
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_if_t *alldevsp = NULL;

    struct addrinfo ai_hints;
    struct addrinfo *ai_list = NULL;

    memset(&ai_hints, 0, sizeof(ai_hints));
    ai_hints.ai_family = AF_UNSPEC;
    ai_hints.ai_flags = AI_NUMERICHOST;

    /* try to translate IP */
    if (getaddrinfo(pcap_dev, NULL, &ai_hints, &ai_list) != 0) {
        return;
    }

    if (pcap_findalldevs(&alldevsp, errbuf)) {
        freeaddrinfo(ai_list);
        return;
    }

    for (pcap_if_t *devsp = alldevsp; devsp ; devsp = devsp->next) {
        for (pcap_addr_t *ip = devsp->addresses; ip ; ip = ip->next) {

            if (ai_list->ai_family != ip->addr->sa_family) {
                continue;
            }

            if (ip->addr->sa_family == AF_INET) {
                if (memcmp(&((struct sockaddr_in*)ai_list->ai_addr)->sin_addr,
                            &((struct sockaddr_in*)ip->addr)->sin_addr,
                            sizeof(struct in_addr)))
                {
                    continue;
                }
            } else if (ip->addr->sa_family == AF_INET6) {
                if (memcmp(&((struct sockaddr_in6*)ai_list->ai_addr)->sin6_addr,
                            &((struct sockaddr_in6*)ip->addr)->sin6_addr,
                            sizeof(struct in6_addr)))
                {
                    continue;
                }
            } else {
                continue;
            }

            freeaddrinfo(ai_list);

            memset(pcap_dev, 0, len);
            strlcpy(pcap_dev, devsp->name, len);

            pcap_freealldevs(alldevsp);
            return;
        }
    }

    freeaddrinfo(ai_list);

    pcap_freealldevs(alldevsp);
}

/*
 *  unittests
 */

#ifdef UNITTESTS
#include "tests/source-pcap.c"
/**
 *  \brief  Register the Unit tests for pcap source
 */
static void SourcePcapRegisterTests(void)
{
    SourcePcapRegisterStatsTests();
}
#endif /* UNITTESTS */
