/*
 * Copyright (c) 2008-2026 OARC, Inc.
 * Copyright (c) 2007-2008, Internet Systems Consortium, Inc.
 * Copyright (c) 2003-2007, The Measurement Factory, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include "pcap.h"
#include "xmalloc.h"
#include "syslog_debug.h"
#include "hashtbl.h"
#include "pcap_layers/byteorder.h"
#include "pcap_layers/pcap_layers.h"
#include "dns_protocol.h"
#include "pcap-thread/pcap_thread.h"
#include "compat.h"

#include <sys/stat.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>

#define PCAP_SNAPLEN 65536
#ifndef ETHER_HDR_LEN
#define ETHER_ADDR_LEN 6
#define ETHER_TYPE_LEN 2
#define ETHER_HDR_LEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN)
#endif
#ifndef ETHERTYPE_8021Q
#define ETHERTYPE_8021Q 0x8100
#endif

#ifdef __OpenBSD__
#define assign_timeval(A, B) \
    A.tv_sec  = B.tv_sec;    \
    A.tv_usec = B.tv_usec
#else
#define assign_timeval(A, B) A = B
#endif

/* We might need to define ETHERTYPE_IPV6 */
#ifndef ETHERTYPE_IPV6
#define ETHERTYPE_IPV6 0x86dd
#endif

#ifdef __GLIBC__
#define uh_dport dest
#define uh_sport source
#define th_off doff
#define th_dport dest
#define th_sport source
#define th_seq seq
#define TCPFLAGFIN(a) (a)->fin
#define TCPFLAGSYN(a) (a)->syn
#define TCPFLAGRST(a) (a)->rst
#else
#define TCPFLAGSYN(a) ((a)->th_flags & TH_SYN)
#define TCPFLAGFIN(a) ((a)->th_flags & TH_FIN)
#define TCPFLAGRST(a) ((a)->th_flags & TH_RST)
#endif

#ifndef IP_OFFMASK
#define IP_OFFMASK 0x1fff
#endif

struct _interface {
    char*            device;
    struct pcap_stat ps0, ps1;
    unsigned int     pkts_captured;
};

#define MAX_N_INTERFACES 10
static int                n_interfaces = 0;
static struct _interface* interfaces   = NULL;
unsigned short            port53       = 53;
pcap_thread_t             pcap_thread  = PCAP_THREAD_T_INIT;

int   n_pcap_offline                 = 0; /* global so daemon.c can use it */
char* bpf_program_str                = NULL;
int   vlan_tag_needs_byte_conversion = 1;

#if 0
static int debug_count = 20;
#endif
struct timeval        last_ts;
static struct timeval start_ts;
static struct timeval finish_ts;
#define MAX_VLAN_IDS 100
static int      n_vlan_ids = 0;
static int      vlan_ids[MAX_VLAN_IDS];
static hashtbl* tcpHash;

static int
pcap_udp_handler(const struct udphdr* udp, int len, void* udata)
{
    transport_message* tm = udata;
    tm->src_port          = nptohs(&udp->uh_sport);
    tm->dst_port          = nptohs(&udp->uh_dport);
    tm->proto             = IPPROTO_UDP;
    if (port53 != tm->dst_port && port53 != tm->src_port)
        return 1;
    return 0;
}

#define MAX_DNS_LENGTH 0xFFFF

#define MAX_TCP_WINDOW_SIZE (0xFFFF << 14)
#define MAX_TCP_STATE 65535
#define MAX_TCP_IDLE 60 /* tcpstate is tossed if idle for this many seconds */
#define MAX_FRAG_IDLE 60 /* keep fragments in pcap_layers for this many seconds */

/* These numbers define the sizes of small arrays which are simpler to work
 * with than dynamically allocated lists. */
#define MAX_TCP_MSGS 8 /* messages being reassembled (per connection) */
#define MAX_TCP_SEGS 8 /* segments not assigned to a message (per connection) */
#define MAX_TCP_HOLES 8 /* holes in a msg buf (per message) */

typedef struct
{
    inX_addr src_ip_addr;
    inX_addr dst_ip_addr;
    uint16_t dport;
    uint16_t sport;
} tcpHashkey_t;

/* Description of hole in tcp reassembly buffer. */
typedef struct
{
    uint16_t start; /* start of hole, measured from beginning of msgbuf->buf */
    uint16_t len; /* length of hole (0 == unused) */
} tcphole_t;

/* TCP message reassembly buffer */
typedef struct
{
    uint32_t  seq; /* seq# of first byte of header of this DNS msg */
    uint16_t  dnslen; /* length of dns message, and size of buf */
    tcphole_t hole[MAX_TCP_HOLES];
    int       holes; /* number of holes remaining in message */
    u_char    buf[]; /* reassembled message (C99 flexible array member) */
} tcp_msgbuf_t;

/* held TCP segment */
typedef struct
{
    uint32_t seq; /* sequence number of first byte of segment */
    uint16_t len; /* length of segment, and size of buf */
    u_char   buf[]; /* segment payload (C99 flexible array member) */
} tcp_segbuf_t;

/* TCP reassembly state */
typedef struct tcpstate {
    tcpHashkey_t     key;
    struct tcpstate *newer, *older;
    long             last_use;
    uint32_t         seq_start; /* seq# of length field of next DNS msg */
    short            msgbufs; /* number of msgbufs in use */
    u_char           dnslen_buf[2]; /* full dnslen field might not arrive in first segment */
    u_char           dnslen_bytes_seen_mask; /* bitmask, when == 3 we have full dnslen */
    int8_t           fin; /* have we seen a FIN? */
    tcp_msgbuf_t*    msgbuf[MAX_TCP_MSGS];
    tcp_segbuf_t*    segbuf[MAX_TCP_SEGS];
} tcpstate_t;

/* List of tcpstates ordered by time of last use, so we can quickly identify
 * and discard stale entries. */
struct
{
    tcpstate_t* oldest;
    tcpstate_t* newest;
} tcpList;

static void
tcpstate_reset(tcpstate_t* tcpstate, uint32_t seq)
{
    int i;
    tcpstate->seq_start = seq;
    tcpstate->fin       = 0;
    if (tcpstate->msgbufs > 0) {
        tcpstate->msgbufs = 0;
        for (i = 0; i < MAX_TCP_MSGS; i++) {
            if (tcpstate->msgbuf[i]) {
                xfree(tcpstate->msgbuf[i]);
                tcpstate->msgbuf[i] = NULL;
            }
        }
    }
    for (i = 0; i < MAX_TCP_SEGS; i++) {
        if (tcpstate->segbuf[i]) {
            xfree(tcpstate->segbuf[i]);
            tcpstate->segbuf[i] = NULL;
        }
    }
}

static void
tcpstate_free(void* p)
{
    tcpstate_reset((tcpstate_t*)p, 0);
    xfree(p);
}

inline static void tcpkey_set(tcpHashkey_t* key, inX_addr src, uint16_t sport, inX_addr dst, uint16_t dport)
{
    memset(key, 0, sizeof(*key));
    key->src_ip_addr.family = src.family;
    if (src.family == AF_INET6) {
        key->src_ip_addr.in6 = src.in6;
    } else {
        key->src_ip_addr.in4 = src.in4;
    }
    key->sport = sport;

    key->dst_ip_addr.family = dst.family;
    if (dst.family == AF_INET6) {
        key->dst_ip_addr.in6 = dst.in6;
    } else {
        key->dst_ip_addr.in4 = dst.in4;
    }
    key->dport = dport;
}

static unsigned int
tcp_hashfunc(const void* key)
{
    if (!(sizeof(tcpHashkey_t) % 4)) {
        return hashword(key, sizeof(tcpHashkey_t) / 4, 0);
    }
    return hashendian(key, sizeof(tcpHashkey_t), 0);
}

static int
tcp_cmpfunc(const void* a, const void* b)
{
    return memcmp(a, b, sizeof(tcpHashkey_t));
}

/* TCP Reassembly.
 *
 * When we see a SYN, we allocate a new tcpstate for the connection, and
 * establish the initial sequence number of the first dns message (seq_start)
 * on the connection.  We assume that no other segment can arrive before the
 * SYN (if one does, it is discarded, and if is not repeated the message it
 * belongs to can never be completely reassembled).
 *
 * Then, for each segment that arrives on the connection:
 * - If it's the first segment of a message (containing the 2-byte message
 *   length), we allocate a msgbuf, and check for any held segments that might
 *   belong to it.
 * - If the first byte of the segment belongs to any msgbuf, we fill
 *   in the holes of that message.  If the message has no more holes, we
 *   handle the complete dns message.  If the tail of the segment was longer
 *   than the hole, we recurse on the tail.
 * - Otherwise, if the segment could be within the tcp window, we hold onto it
 *   pending the creation of a matching msgbuf.
 *
 * This algorithm handles segments that arrive out of order, duplicated or
 * overlapping (including segments from different dns messages arriving out of
 * order), and dns messages that do not necessarily start on segment
 * boundaries.
 *
 */
static void
pcap_handle_tcp_segment(u_char* segment, int len, uint32_t seq, tcpstate_t* tcpstate, transport_message* tm)
{
    int      i, m, s;
    uint16_t dnslen;
    int      segoff, seglen;

    dfprintf(1, "pcap_handle_tcp_segment: seq=%u, len=%d", seq, len);

    if (len <= 0) /* there is no more payload */
        return;

    if (seq - tcpstate->seq_start < 2) {
        /* this segment contains all or part of the 2-byte DNS length field */
        uint32_t o = seq - tcpstate->seq_start;
        int      l = (len > 1 && o == 0) ? 2 : 1;
        dfprintf(1, "pcap_handle_tcp_segment: copying %d bytes to dnslen_buf[%d]", l, o);
        memcpy(&tcpstate->dnslen_buf[o], segment, l);
        if (l == 2)
            tcpstate->dnslen_bytes_seen_mask = 3;
        else
            tcpstate->dnslen_bytes_seen_mask |= (1 << o);
        len -= l;
        segment += l;
        seq += l;
    }

    if (3 == tcpstate->dnslen_bytes_seen_mask) {
        /* We have the dnslen stored now */
        dnslen = nptohs(tcpstate->dnslen_buf) & 0xffff;
        /*
         * Next we poison the mask to indicate we are in to the message body.
         * If one doesn't remember we're past the then,
         * one loops forever getting more msgbufs rather than filling
         * in the contents of THIS message.
         *
         * We need to later reset that mask when we process the message
         * (method: tcpstate->dnslen_bytes_seen_mask = 0).
         */
        tcpstate->dnslen_bytes_seen_mask = 7;
        tcpstate->seq_start += sizeof(uint16_t) + dnslen;
        dfprintf(1, "pcap_handle_tcp_segment: first segment; dnslen = %d", dnslen);
        if (len >= dnslen) {
            /* this segment contains a complete message - avoid the reassembly
             * buffer and just handle the message immediately */
            dns_protocol_handler(segment, dnslen, tm);
            tcpstate->dnslen_bytes_seen_mask = 0; /* go back for another message in this tcp connection */
            /* handle the trailing part of the segment? */
            if (len > dnslen) {
                dfprintf(1, "pcap_handle_tcp_segment: %s", "segment tail");
                pcap_handle_tcp_segment(segment + dnslen, len - dnslen, seq + dnslen, tcpstate, tm);
            }
            return;
        }
        /*
         * At this point we KNOW we have an incomplete message and need to do reassembly.
         * i.e.:  assert(len < dnslen);
         */
        dfprintf(2, "pcap_handle_tcp_segment: %s", "buffering segment");
        /* allocate a msgbuf for reassembly */
        for (m = 0; tcpstate->msgbuf[m];) {
            if (++m >= MAX_TCP_MSGS) {
                dfprintf(1, "pcap_handle_tcp_segment: %s", "out of msgbufs");
                return;
            }
        }
        tcpstate->msgbuf[m] = xcalloc(1, sizeof(tcp_msgbuf_t) + dnslen);
        if (NULL == tcpstate->msgbuf[m]) {
            dsyslogf(LOG_ERR, "out of memory for tcp_msgbuf (%d)", dnslen);
            return;
        }
        tcpstate->msgbufs++;
        tcpstate->msgbuf[m]->seq           = seq;
        tcpstate->msgbuf[m]->dnslen        = dnslen;
        tcpstate->msgbuf[m]->holes         = 1;
        tcpstate->msgbuf[m]->hole[0].start = len;
        tcpstate->msgbuf[m]->hole[0].len   = dnslen - len;
        dfprintf(1,
            "pcap_handle_tcp_segment: new msgbuf %d: seq = %u, dnslen = %d, hole start = %d, hole len = %d", m,
            tcpstate->msgbuf[m]->seq, tcpstate->msgbuf[m]->dnslen, tcpstate->msgbuf[m]->hole[0].start,
            tcpstate->msgbuf[m]->hole[0].len);
        /* copy segment to appropriate location in reassembly buffer */
        memcpy(tcpstate->msgbuf[m]->buf, segment, len);

        /* Now that we know the length of this message, we must check any held
         * segments to see if they belong to it. */
        for (s = 0; s < MAX_TCP_SEGS; s++) {
            if (!tcpstate->segbuf[s])
                continue;
            if ((int64_t)tcpstate->segbuf[s]->seq - seq > 0 && (int64_t)tcpstate->segbuf[s]->seq - seq < dnslen) {
                tcp_segbuf_t* segbuf = tcpstate->segbuf[s];
                tcpstate->segbuf[s]  = NULL;
                dfprintf(1, "pcap_handle_tcp_segment: %s", "message reassembled");
                pcap_handle_tcp_segment(segbuf->buf, segbuf->len, segbuf->seq, tcpstate, tm);
                /*
                 * Note that our recursion will also cover any tail messages (I hope).
                 * Thus we do not need to do so here and can return.
                 */
                xfree(segbuf);
            }
        }
        return;
    }

    /*
     * Welcome to reassembly-land.
     */
    /* find the message to which the first byte of this segment belongs */
    for (m = 0; m < MAX_TCP_MSGS; m++) {
        if (!tcpstate->msgbuf[m])
            continue;
        segoff = seq - tcpstate->msgbuf[m]->seq;
        if (segoff >= 0 && segoff < tcpstate->msgbuf[m]->dnslen) {
            /* segment starts in this msgbuf */
            dfprintf(1, "pcap_handle_tcp_segment: seg matches msg %d: seq = %u, dnslen = %d",
                m, tcpstate->msgbuf[m]->seq, tcpstate->msgbuf[m]->dnslen);
            if (segoff + len > tcpstate->msgbuf[m]->dnslen) {
                /* segment would overflow msgbuf */
                seglen = tcpstate->msgbuf[m]->dnslen - segoff;
                dfprintf(1, "pcap_handle_tcp_segment: using partial segment %d", seglen);
            } else {
                seglen = len;
            }
            break;
        }
    }
    if (m >= MAX_TCP_MSGS) {
        /* seg does not match any msgbuf; just hold on to it. */
        dfprintf(1, "pcap_handle_tcp_segment: %s", "seg does not match any msgbuf");

        if (seq - tcpstate->seq_start > MAX_TCP_WINDOW_SIZE) {
            dfprintf(1, "pcap_handle_tcp_segment: %s", "seg is outside window; discarding");
            return;
        }
        for (s = 0; s < MAX_TCP_SEGS; s++) {
            if (tcpstate->segbuf[s])
                continue;
            tcpstate->segbuf[s]      = xcalloc(1, sizeof(tcp_segbuf_t) + len);
            tcpstate->segbuf[s]->seq = seq;
            tcpstate->segbuf[s]->len = len;
            memcpy(tcpstate->segbuf[s]->buf, segment, len);
            dfprintf(1, "pcap_handle_tcp_segment: new segbuf %d: seq = %u, len = %d",
                s, tcpstate->segbuf[s]->seq, tcpstate->segbuf[s]->len);
            return;
        }
        dfprintf(1, "pcap_handle_tcp_segment: %s", "out of segbufs");
        return;
    }

    /* Reassembly algorithm adapted from RFC 815. */
    for (i = 0; i < MAX_TCP_HOLES; i++) {
        tcphole_t* newhole;
        uint16_t   hole_start, hole_len;
        if (tcpstate->msgbuf[m]->hole[i].len == 0)
            continue; /* hole descriptor is not in use */
        hole_start = tcpstate->msgbuf[m]->hole[i].start;
        hole_len   = tcpstate->msgbuf[m]->hole[i].len;
        if (segoff >= hole_start + hole_len)
            continue; /* segment is totally after hole */
        if (segoff + seglen <= hole_start)
            continue; /* segment is totally before hole */
        /* The segment overlaps this hole.  Delete the hole. */
        dfprintf(1, "pcap_handle_tcp_segment: overlaping hole %d: %d %d", i, hole_start, hole_len);
        tcpstate->msgbuf[m]->hole[i].len = 0;
        tcpstate->msgbuf[m]->holes--;
        if (segoff + seglen < hole_start + hole_len) {
            /* create a new hole after the segment (common case) */
            newhole        = &tcpstate->msgbuf[m]->hole[i]; /* hole[i] is guaranteed free */
            newhole->start = segoff + seglen;
            newhole->len   = (hole_start + hole_len) - newhole->start;
            tcpstate->msgbuf[m]->holes++;
            dfprintf(1, "pcap_handle_tcp_segment: new post-hole %d: %d %d", i, newhole->start, newhole->len);
        }
        if (segoff > hole_start) {
            /* create a new hole before the segment */
            int j;
            for (j = 0; j < MAX_TCP_HOLES; j++) {
                if (tcpstate->msgbuf[m]->hole[j].len == 0) {
                    newhole = &tcpstate->msgbuf[m]->hole[j];
                    break;
                }
            }
            if (j >= MAX_TCP_HOLES) {
                dfprintf(1, "pcap_handle_tcp_segment: %s", "out of hole descriptors");
                return;
            }
            tcpstate->msgbuf[m]->holes++;
            newhole->start = hole_start;
            newhole->len   = segoff - hole_start;
            dfprintf(1, "pcap_handle_tcp_segment: new pre-hole %d: %d %d", j, newhole->start, newhole->len);
        }
        if (segoff >= hole_start && (hole_len == 0 || segoff + seglen < hole_start + hole_len)) {
            /* The segment does not extend past hole boundaries; there is
             * no need to look for other matching holes. */
            break;
        }
    }

    /* copy payload to appropriate location in reassembly buffer */
    memcpy(&tcpstate->msgbuf[m]->buf[segoff], segment, seglen);

    dfprintf(1, "pcap_handle_tcp_segment: holes remaining: %d", tcpstate->msgbuf[m]->holes);

    if (tcpstate->msgbuf[m]->holes == 0) {
        /* We now have a completely reassembled dns message */
        dfprintf(2, "pcap_handle_tcp_segment: %s", "reassembly to dns_protocol_handler");
        dns_protocol_handler(tcpstate->msgbuf[m]->buf, tcpstate->msgbuf[m]->dnslen, tm);
        tcpstate->dnslen_bytes_seen_mask = 0; /* go back for another message in this tcp connection */
        xfree(tcpstate->msgbuf[m]);
        tcpstate->msgbuf[m] = NULL;
        tcpstate->msgbufs--;
    }

    if (seglen < len) {
        dfprintf(1, "pcap_handle_tcp_segment: %s", "segment tail after reassembly");
        pcap_handle_tcp_segment(segment + seglen, len - seglen, seq + seglen, tcpstate, tm);
    } else {
        dfprintf(1, "pcap_handle_tcp_segment: %s", "nothing more after reassembly");
    };
}

static void
tcpList_add_newest(tcpstate_t* tcpstate)
{
    tcpstate->older                                              = tcpList.newest;
    tcpstate->newer                                              = NULL;
    *(tcpList.newest ? &tcpList.newest->newer : &tcpList.oldest) = tcpstate;
    tcpList.newest                                               = tcpstate;
}

static void
tcpList_remove(tcpstate_t* tcpstate)
{
    *(tcpstate->older ? &tcpstate->older->newer : &tcpList.oldest) = tcpstate->newer;
    *(tcpstate->newer ? &tcpstate->newer->older : &tcpList.newest) = tcpstate->older;
}

static void
tcpList_remove_older_than(long t)
{
    int         n = 0;
    tcpstate_t* tcpstate;
    while (tcpList.oldest && tcpList.oldest->last_use < t) {
        tcpstate = tcpList.oldest;
        tcpList_remove(tcpstate);
        hash_remove(&tcpstate->key, tcpHash);
        n++;
    }
    dfprintf(1, "discarded %d old tcpstates", n);
}

/*
 * This function always returns 1 because we do our own assembly and
 * we don't want pcap_layers to do any further processing of this
 * packet.
 */
static int
pcap_tcp_handler(const struct tcphdr* tcp, int len, void* udata)
{
    transport_message* tm     = udata;
    int                offset = tcp->th_off << 2;
    uint32_t           seq;
    tcpstate_t*        tcpstate = NULL;
    tcpHashkey_t       key;

    tm->src_port = nptohs(&tcp->th_sport);
    tm->dst_port = nptohs(&tcp->th_dport);
    tm->proto    = IPPROTO_TCP;

    tcpkey_set(&key, tm->src_ip_addr, tm->src_port, tm->dst_ip_addr, tm->dst_port);

    if (debug_flag > 1) {
        char src[128], dst[128];
        inXaddr_ntop(&key.src_ip_addr, src, sizeof(src));
        inXaddr_ntop(&key.dst_ip_addr, dst, sizeof(dst));
        dfprintf(1, "handle_tcp: %s:%d %s:%d", src, key.sport, dst, key.dport);
    }

    if (port53 != key.dport && port53 != key.sport)
        return 1;

    if (NULL == tcpHash) {
        dfprintf(2, "pcap_tcp_handler: %s", "hash_create");
        tcpHash = hash_create(MAX_TCP_STATE, tcp_hashfunc, tcp_cmpfunc, 0, NULL, tcpstate_free);
        if (NULL == tcpHash)
            return 1;
    }

    seq = nptohl(&tcp->th_seq);
    len -= offset; /* len = length of TCP payload */
    dfprintf(1, "handle_tcp: seq = %u, len = %d", seq, len);

    tcpstate = hash_find(&key, tcpHash);
    if (tcpstate)
        dfprintf(1, "handle_tcp: tcpstate->seq_start = %u, ->msgs = %d", tcpstate->seq_start, tcpstate->msgbufs);

    if (!tcpstate && !(TCPFLAGSYN(tcp))) {
        /* There's no existing state, and this is not the start of a stream.
         * We have no way to synchronize with the stream, so we give up.
         * (This commonly happens for the final ACK in response to a FIN.) */
        dfprintf(1, "handle_tcp: %s", "no state");
        return 1;
    }

    if (tcpstate)
        tcpList_remove(tcpstate); /* remove from its current position */

    if (TCPFLAGRST(tcp)) {
        dfprintf(1, "handle_tcp: RST at %u", seq);

        /* remove the state for this direction */
        if (tcpstate)
            hash_remove(&key, tcpHash); /* this also frees tcpstate */

        /* remove the state for the opposite direction */
        tcpkey_set(&key, tm->dst_ip_addr, tm->dst_port, tm->src_ip_addr, tm->src_port);
        tcpstate = hash_find(&key, tcpHash);
        if (tcpstate) {
            tcpList_remove(tcpstate);
            hash_remove(&key, tcpHash); /* this also frees tcpstate */
        }
        return 1;
    }

    if (TCPFLAGSYN(tcp)) {
        dfprintf(1, "handle_tcp: SYN at %u", seq);
        seq++; /* skip the syn */
        if (tcpstate) {
            dfprintf(2, "handle_tcp: %s", "...resetting existing tcpstate");
            tcpstate_reset(tcpstate, seq);
        } else {
            dfprintf(2, "handle_tcp: %s", "...creating new tcpstate");
            tcpstate = xcalloc(1, sizeof(*tcpstate));
            if (!tcpstate)
                return 1;
            tcpstate_reset(tcpstate, seq);
            tcpstate->key = key;
            if (0 != hash_add(&tcpstate->key, tcpstate, tcpHash)) {
                tcpstate_free(tcpstate);
                return 1;
            }
        }
    }

    pcap_handle_tcp_segment((uint8_t*)tcp + offset, len, seq, tcpstate, tm);

    if (TCPFLAGFIN(tcp) && !tcpstate->fin) {
        /* End of tcp stream */
        dfprintf(1, "handle_tcp: FIN at %u", seq);
        tcpstate->fin = 1;
    }

    if (tcpstate->fin && tcpstate->msgbufs == 0) {
        /* FIN was seen, and there are no incomplete msgbufs left */
        dfprintf(1, "handle_tcp: %s", "connection done");
        hash_remove(&key, tcpHash); /* this also frees tcpstate */

    } else {
        /* We're keeping this tcpstate.  Store it in tcpList by age. */
        tcpstate->last_use = tm->ts.tv_sec;
        tcpList_add_newest(tcpstate);
    }
    return 1;
}

static int
pcap_ipv4_handler(const struct ip* ip4, int len, void* udata)
{
    transport_message* tm = udata;
#ifdef __FreeBSD__ /* FreeBSD uses packed struct ip */
    struct in_addr a;
    memcpy(&a, &ip4->ip_src, sizeof(a));
    inXaddr_assign_v4(&tm->src_ip_addr, &a);
    memcpy(&a, &ip4->ip_dst, sizeof(a));
    inXaddr_assign_v4(&tm->dst_ip_addr, &a);
#else
    inXaddr_assign_v4(&tm->src_ip_addr, &ip4->ip_src);
    inXaddr_assign_v4(&tm->dst_ip_addr, &ip4->ip_dst);
#endif
    tm->ip_version = 4;
    return 0;
}

static int
pcap_ipv6_handler(const struct ip6_hdr* ip6, int len, void* udata)
{
    transport_message* tm = udata;
#ifdef __FreeBSD__ /* FreeBSD uses packed struct ip6_hdr */
    struct in6_addr a;
    memcpy(&a, &ip6->ip6_src, sizeof(a));
    inXaddr_assign_v6(&tm->src_ip_addr, &a);
    memcpy(&a, &ip6->ip6_dst, sizeof(a));
    inXaddr_assign_v6(&tm->dst_ip_addr, &a);
#else
    inXaddr_assign_v6(&tm->src_ip_addr, &ip6->ip6_src);
    inXaddr_assign_v6(&tm->dst_ip_addr, &ip6->ip6_dst);
#endif
    tm->ip_version = 6;
    return 0;
}

static int
pcap_match_vlan(unsigned short vlan, void* udata)
{
    int i;
    if (vlan_tag_needs_byte_conversion)
        vlan = ntohs(vlan);
    dfprintf(1, "vlan is %d", vlan);
    for (i = 0; i < n_vlan_ids; i++)
        if (vlan_ids[i] == vlan)
            return 0;
    return 1;
}

/*
 * Forward declares for pcap_layers since we need to call datalink
 * handlers directly.
 */
#if USE_PPP
void handle_ppp(const u_char* pkt, int len, void* userdata);
#endif
void handle_null(const u_char* pkt, int len, void* userdata);
#ifdef DLT_LOOP
void handle_loop(const u_char* pkt, int len, void* userdata);
#endif
#ifdef DLT_RAW
void handle_raw(const u_char* pkt, int len, void* userdata);
#endif
void handle_ether(const u_char* pkt, int len, void* userdata);
#ifdef DLT_LINUX_SLL
void handle_linux_sll(const u_char* pkt, int len, void* userdata);
#endif

static void
pcap_handle_packet(u_char* udata, const struct pcap_pkthdr* hdr, const u_char* pkt, const char* name, int dlt)
{
    void (*handle_datalink)(const u_char* pkt, int len, void* userdata);
    transport_message tm;

#if 0 /* enable this to test code with unaligned headers */
    char buf[PCAP_SNAPLEN + 1];
    memcpy(buf + 1, pkt, hdr->caplen);
    pkt = buf + 1;
#endif

    assign_timeval(last_ts, hdr->ts);
    if (hdr->caplen < ETHER_HDR_LEN)
        return;
    memset(&tm, 0, sizeof(tm));
    assign_timeval(tm.ts, hdr->ts);

    switch (dlt) {
    case DLT_EN10MB:
        handle_datalink = handle_ether;
        break;
#if USE_PPP
    case DLT_PPP:
        handle_datalink = handle_ppp;
        break;
#endif
#ifdef DLT_LOOP
    case DLT_LOOP:
        handle_datalink = handle_loop;
        break;
#endif
#ifdef DLT_RAW
    case DLT_RAW:
        handle_datalink = handle_raw;
        break;
#endif
#ifdef DLT_LINUX_SLL
    case DLT_LINUX_SLL:
        handle_datalink = handle_linux_sll;
        break;
#endif
    case DLT_NULL:
        handle_datalink = handle_null;
        break;
    default:
        fprintf(stderr, "unsupported data link type %d", dlt);
        exit(1);
    }

    handle_datalink(pkt, hdr->caplen, (u_char*)&tm);
}

/* ========================================================================= */

extern int sig_while_processing;

void _callback(u_char* user, const struct pcap_pkthdr* pkthdr, const u_char* pkt, const char* name, int dlt)
{
    struct _interface* i;
    if (!user) {
        dsyslog(LOG_ERR, "internal error");
        exit(2);
    }
    i = (struct _interface*)user;

    i->pkts_captured++;

    pcap_handle_packet(user, pkthdr, pkt, name, dlt);
}

void Pcap_init(const char* device, int promisc, int monitor, int immediate, int threads, int buffer_size)
{
    char               errbuf[512];
    struct stat        sb;
    struct _interface* i;
    int                err;
    extern int         pt_timeout;

    if (interfaces == NULL) {
        interfaces = xcalloc(MAX_N_INTERFACES, sizeof(*interfaces));
        if ((err = pcap_thread_set_promiscuous(&pcap_thread, promisc))) {
            dsyslogf(LOG_ERR, "unable to set promiscuous mode: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if ((err = pcap_thread_set_monitor(&pcap_thread, monitor))) {
            dsyslogf(LOG_ERR, "unable to set monitor mode: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if ((err = pcap_thread_set_immediate_mode(&pcap_thread, immediate))) {
            dsyslogf(LOG_ERR, "unable to set immediate mode: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if ((err = pcap_thread_set_use_threads(&pcap_thread, threads))) {
            dsyslogf(LOG_ERR, "unable to set use threads: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if ((err = pcap_thread_set_snaplen(&pcap_thread, PCAP_SNAPLEN))) {
            dsyslogf(LOG_ERR, "unable to set snap length: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if (bpf_program_str && (err = pcap_thread_set_filter(&pcap_thread, bpf_program_str, strlen(bpf_program_str)))) {
            dsyslogf(LOG_ERR, "unable to set pcap filter: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if ((err = pcap_thread_set_callback(&pcap_thread, _callback))) {
            dsyslogf(LOG_ERR, "unable to set pcap callback: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if (buffer_size > 0 && (err = pcap_thread_set_buffer_size(&pcap_thread, buffer_size))) {
            dsyslogf(LOG_ERR, "unable to set pcap buffer size: %s", pcap_thread_strerr(err));
            exit(1);
        }
        if (pt_timeout > 0 && (err = pcap_thread_set_timeout(&pcap_thread, pt_timeout))) {
            dsyslogf(LOG_ERR, "unable to set pcap-thread timeout: %s", pcap_thread_strerr(err));
            exit(1);
        }
    }
    assert(interfaces);
    assert(n_interfaces < MAX_N_INTERFACES);
    i         = &interfaces[n_interfaces];
    i->device = strdup(device);

    last_ts.tv_sec = last_ts.tv_usec = 0;
    finish_ts.tv_sec = finish_ts.tv_usec = 0;

    if (!stat(device, &sb)) {
        if ((err = pcap_thread_open_offline(&pcap_thread, device, i))) {
            dsyslogf(LOG_ERR, "unable to open offline file %s: %s", device, pcap_thread_strerr(err));
            if (err == PCAP_THREAD_EPCAP) {
                dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)",
                    pcap_thread_status(&pcap_thread),
                    pcap_statustostr(pcap_thread_status(&pcap_thread)),
                    pcap_thread_errbuf(&pcap_thread));
            } else if (err == PCAP_THREAD_ERRNO) {
                dsyslogf(LOG_ERR, "system error [%d]: %s (%s)\n",
                    errno,
                    dsc_strerror(errno, errbuf, sizeof(errbuf)),
                    pcap_thread_errbuf(&pcap_thread));
            }
            exit(1);
        }

        n_pcap_offline++;
    } else {
        if ((err = pcap_thread_open(&pcap_thread, device, i))) {
            dsyslogf(LOG_ERR, "unable to open interface %s: %s", device, pcap_thread_strerr(err));
            if (err == PCAP_THREAD_EPCAP) {
                dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)",
                    pcap_thread_status(&pcap_thread),
                    pcap_statustostr(pcap_thread_status(&pcap_thread)),
                    pcap_thread_errbuf(&pcap_thread));
            } else if (err == PCAP_THREAD_ERRNO) {
                dsyslogf(LOG_ERR, "system error [%d]: %s (%s)\n",
                    errno,
                    dsc_strerror(errno, errbuf, sizeof(errbuf)),
                    pcap_thread_errbuf(&pcap_thread));
            }
            exit(1);
        }
    }

    if (0 == n_interfaces) {
        extern int drop_ip_fragments;
        /*
         * Initialize pcap_layers library and specifiy IP fragment reassembly
         * Datalink type is handled in callback
         */
        pcap_layers_init(DLT_EN10MB, drop_ip_fragments ? 0 : 1);
        if (n_vlan_ids)
            callback_vlan = pcap_match_vlan;
        callback_ipv4 = pcap_ipv4_handler;
        callback_ipv6 = pcap_ipv6_handler;
        callback_udp  = pcap_udp_handler;
        callback_tcp  = pcap_tcp_handler;
        callback_l7   = dns_protocol_handler;
    }
    n_interfaces++;
    if (n_pcap_offline > 1 || (n_pcap_offline > 0 && n_interfaces > n_pcap_offline)) {
        dsyslog(LOG_ERR, "offline interface must be only interface");
        exit(1);
    }
}

void _stats(u_char* user, const struct pcap_stat* stats, const char* name, int dlt)
{
    int                i;
    struct _interface* I = 0;

    for (i = 0; i < n_interfaces; i++) {
        if (!strcmp(name, interfaces[i].device)) {
            I = &interfaces[i];
            break;
        }
    }

    if (I) {
        I->ps0 = I->ps1;
        I->ps1 = *stats;
    }
}

int Pcap_run(void)
{
    int             i, err;
    extern uint64_t statistics_interval;

    for (i = 0; i < n_interfaces; i++)
        interfaces[i].pkts_captured = 0;

    if (n_pcap_offline > 0) {
        if (finish_ts.tv_sec > 0) {
            start_ts.tv_sec = finish_ts.tv_sec;
            finish_ts.tv_sec += statistics_interval;
        } else {
            /*
             * First run, need to walk each pcap savefile and find
             * the first start time
             */

            if ((err = pcap_thread_next_reset(&pcap_thread))) {
                dsyslogf(LOG_ERR, "unable to reset pcap thread next: %s", pcap_thread_strerr(err));
                return 0;
            }
            for (i = 0; i < n_pcap_offline; i++) {
                if ((err = pcap_thread_next(&pcap_thread))) {
                    if (err != PCAP_THREAD_EPCAP) {
                        dsyslogf(LOG_ERR, "unable to do pcap thread next: %s", pcap_thread_strerr(err));
                        return 0;
                    }
                    continue;
                }

                if (!start_ts.tv_sec
                    || last_ts.tv_sec < start_ts.tv_sec
                    || (last_ts.tv_sec == start_ts.tv_sec && last_ts.tv_usec < start_ts.tv_usec)) {
                    start_ts = last_ts;
                }
            }

            if (!start_ts.tv_sec) {
                return 0;
            }

            finish_ts.tv_sec  = ((start_ts.tv_sec / statistics_interval) + 1) * statistics_interval;
            finish_ts.tv_usec = 0;
        }

        i = 0;
        do {
            err = pcap_thread_next(&pcap_thread);
            if (err == PCAP_THREAD_EPCAP) {
                /*
                 * Potential EOF, count number of times
                 */
                i++;
            } else if (err) {
                dsyslogf(LOG_ERR, "unable to do pcap thread next: %s", pcap_thread_strerr(err));
                return 0;
            } else {
                i = 0;
            }

            if (i == n_pcap_offline || sig_while_processing) {
                /*
                 * All pcaps reports EOF or we got a signal, nothing more to do
                 */
                finish_ts = last_ts;
                return 0;
            }
        } while (last_ts.tv_sec < finish_ts.tv_sec);
    } else {
        gettimeofday(&start_ts, NULL);
        gettimeofday(&last_ts, NULL);
        finish_ts.tv_sec  = ((start_ts.tv_sec / statistics_interval) + 1) * statistics_interval;
        finish_ts.tv_usec = 0;
        if ((err = pcap_thread_set_timedrun_to(&pcap_thread, finish_ts))) {
            dsyslogf(LOG_ERR, "unable to set pcap thread timed run: %s", pcap_thread_strerr(err));
            return 0;
        }

        if ((err = pcap_thread_run(&pcap_thread))) {
            if (err == PCAP_THREAD_ERRNO && errno == EINTR && sig_while_processing) {
                dsyslog(LOG_INFO, "pcap thread run interruped by signal");
            } else {
                dsyslogf(LOG_ERR, "unable to pcap thread run: %s", pcap_thread_strerr(err));
                if (err == PCAP_THREAD_EPCAP) {
                    dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)",
                        pcap_thread_status(&pcap_thread),
                        pcap_statustostr(pcap_thread_status(&pcap_thread)),
                        pcap_thread_errbuf(&pcap_thread));
                } else if (err == PCAP_THREAD_ERRNO) {
                    char errbuf[512];
                    dsyslogf(LOG_ERR, "system error [%d]: %s (%s)\n",
                        errno,
                        dsc_strerror(errno, errbuf, sizeof(errbuf)),
                        pcap_thread_errbuf(&pcap_thread));
                }
                return 0;
            }
        }

        if (sig_while_processing)
            finish_ts = last_ts;

        if ((err = pcap_thread_stats(&pcap_thread, _stats, 0))) {
            dsyslogf(LOG_ERR, "unable to get pcap thread stats: %s", pcap_thread_strerr(err));
            if (err == PCAP_THREAD_EPCAP) {
                dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)",
                    pcap_thread_status(&pcap_thread),
                    pcap_statustostr(pcap_thread_status(&pcap_thread)),
                    pcap_thread_errbuf(&pcap_thread));
            }
            return 0;
        }
    }
    tcpList_remove_older_than(last_ts.tv_sec - MAX_TCP_IDLE);
    pcap_layers_clear_fragments(time(NULL) - MAX_FRAG_IDLE);
    return 1;
}

void Pcap_stop(void)
{
    pcap_thread_stop(&pcap_thread);
}

void Pcap_close(void)
{
    int i;

    pcap_thread_close(&pcap_thread);
    for (i = 0; i < n_interfaces; i++)
        if (interfaces[i].device)
            free(interfaces[i].device);

    xfree(interfaces);
    interfaces = NULL;
}

int Pcap_start_time(void)
{
    return (int)start_ts.tv_sec;
}

int Pcap_finish_time(void)
{
    return (int)finish_ts.tv_sec;
}

void pcap_set_match_vlan(int vlan)
{
    assert(n_vlan_ids < MAX_VLAN_IDS);
    vlan_ids[n_vlan_ids++] = vlan;
}

/* ========== PCAP_STAT INDEXER ========== */

int pcap_ifname_iterator(const char**);
int pcap_stat_iterator(const char**);

static indexer indexers[] = {
    { "ifname", 0, 0, pcap_ifname_iterator },
    { "pcap_stat", 0, 0, pcap_stat_iterator },
    { 0 },
};

int pcap_ifname_iterator(const char** label)
{
    static int next_iter = 0;
    if (NULL == label) {
        next_iter = 0;
        return n_interfaces;
    }
    if (next_iter >= 0 && next_iter < n_interfaces) {
        *label = interfaces[next_iter].device;
        return next_iter++;
    }
    return -1;
}

int pcap_stat_iterator(const char** label)
{
    static int next_iter = 0;
    if (NULL == label) {
        next_iter = 0;
        return 3;
    }
    if (0 == next_iter)
        *label = "pkts_captured";
    else if (1 == next_iter)
        *label = "filter_received";
    else if (2 == next_iter)
        *label = "kernel_dropped";
    else
        return -1;
    return next_iter++;
}

void pcap_report(FILE* fp, md_array_printer* printer)
{
    int       i;
    md_array* theArray = acalloc(1, sizeof(*theArray));
    if (!theArray) {
        dsyslog(LOG_ERR, "unable to write report, out of memory");
        return;
    }
    theArray->name        = "pcap_stats";
    theArray->d1.indexer  = &indexers[0];
    theArray->d1.type     = "ifname";
    theArray->d1.alloc_sz = n_interfaces;
    theArray->d2.indexer  = &indexers[1];
    theArray->d2.type     = "pcap_stat";
    theArray->d2.alloc_sz = 3;
    theArray->array       = acalloc(n_interfaces, sizeof(*theArray->array));
    if (!theArray->array) {
        dsyslog(LOG_ERR, "unable to write report, out of memory");
        return;
    }
    for (i = 0; i < n_interfaces; i++) {
        struct _interface* I        = &interfaces[i];
        theArray->array[i].alloc_sz = 3;
        theArray->array[i].array    = acalloc(3, sizeof(int));
        theArray->array[i].array[0] = I->pkts_captured;
        theArray->array[i].array[1] = I->ps1.ps_recv - I->ps0.ps_recv;
        theArray->array[i].array[2] = I->ps1.ps_drop - I->ps0.ps_drop;
    }
    md_array_print(theArray, printer, fp);
}
