/*
 * Copyright (c) 2016-2023, OARC, 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 "network.h"
#include "iaddr.h"
#include "log.h"
#include "pcaps.h"
#include "dumper.h"
#include "endpoint.h"
#include "tcpstate.h"
#include "tcpreasm.h"
#include "endian_compat.h"

#include <ldns/ldns.h>

struct ip6_hdr* network_ipv6 = 0;
struct ip*      network_ip   = 0;
struct udphdr*  network_udp  = 0;

extern tcpstate_ptr _curr_tcpstate; /* from tcpstate.c */

static inline uint16_t _need16(const void* ptr)
{
    uint16_t v;
    memcpy(&v, ptr, sizeof(v));
    return be16toh(v);
}

static inline uint32_t _need32(const void* ptr)
{
    uint32_t v;
    memcpy(&v, ptr, sizeof(v));
    return be32toh(v);
}

static int skip_vlan(unsigned vlan)
{
    if (!EMPTY(vlans_excl)) {
        vlan_ptr vl;

        for (vl = HEAD(vlans_excl); vl != NULL; vl = NEXT(vl, link)) {
            if (vl->vlan == vlan || vl->vlan == MAX_VLAN)
                break;
        }

        /*
         * If there is no VLAN matching the packet, skip it
         */
        if (vl == NULL)
            return 1;
    } else if (!EMPTY(vlans_incl)) {
        vlan_ptr vl;

        for (vl = HEAD(vlans_incl); vl != NULL; vl = NEXT(vl, link)) {
            if (vl->vlan == vlan || vl->vlan == MAX_VLAN)
                break;
        }

        /*
         * If there is no VLAN matching the packet, and the packet is tagged, skip it
         */
        if (vl == NULL && vlan != MAX_VLAN)
            return 1;
    }

    return 0;
}

void layer_pkt(u_char* user, const pcap_thread_packet_t* packet, const u_char* payload, size_t length)
{
    mypcap_ptr                  mypcap = (mypcap_ptr)user;
    size_t                      len;
    unsigned                    vlan;
    const pcap_thread_packet_t *prevpkt, *firstpkt = packet;
    char                        descr[200];

    if (!mypcap)
        return;
    if (!packet)
        return;

    while (firstpkt->have_prevpkt) {
        if (firstpkt->have_pkthdr)
            break;
        firstpkt = firstpkt->prevpkt;
    }
    if (!firstpkt->have_pkthdr)
        return;

    if (only_offline_pcaps && start_time != 0 && firstpkt->pkthdr.ts.tv_sec < start_time)
        return;

    len = firstpkt->pkthdr.caplen;

    last_ts = firstpkt->pkthdr.ts;
    if (stop_time != 0 && firstpkt->pkthdr.ts.tv_sec >= stop_time) {
        breakloop_pcaps();
        main_exit = TRUE;
    }

    if (main_exit)
        return;

    /* If ever SNAPLEN wasn't big enough, we have no recourse. */
    if (firstpkt->pkthdr.len != firstpkt->pkthdr.caplen)
        return;

    vlan = MAX_VLAN;
    for (prevpkt = packet; prevpkt; prevpkt = prevpkt->prevpkt) {
        if (prevpkt->have_ieee802hdr) {
            /* TODO: Only match first found VLAN or all? */
            vlan = prevpkt->ieee802hdr.vid;
            len -= 4;
            break;
        }
        if (!prevpkt->have_prevpkt)
            break;
    }
    if (skip_vlan(vlan)) {
        return;
    }

    descr[0] = 0;
    if (preso) {
        char      when[100];
        struct tm tm;
        time_t    t;

        /*
         * Reduce `len` to report same captured length as `dl_pkt`
         */
        for (prevpkt = packet; len && prevpkt; prevpkt = prevpkt->prevpkt) {
            if (prevpkt->have_nullhdr) {
                if (len > sizeof(prevpkt->nullhdr))
                    len -= sizeof(prevpkt->nullhdr);
                else
                    len = 0;
            }
            if (prevpkt->have_loophdr) {
                if (len > sizeof(prevpkt->loophdr))
                    len -= sizeof(prevpkt->loophdr);
                else
                    len = 0;
            }
            if (prevpkt->have_ethhdr) {
                if (len > sizeof(prevpkt->ethhdr))
                    len -= sizeof(prevpkt->ethhdr);
                else
                    len = 0;
            }
            if (prevpkt->have_linux_sll) {
                if (len > sizeof(prevpkt->linux_sll))
                    len -= sizeof(prevpkt->linux_sll);
                else
                    len = 0;
            }

            if (!prevpkt->have_prevpkt)
                break;
        }

        t = (time_t)firstpkt->pkthdr.ts.tv_sec;
        gmtime_r(&t, &tm);
        strftime(when, sizeof(when), "%Y-%m-%d %T", &tm);

        if (vlan != MAX_VLAN) {
            snprintf(descr, sizeof(descr), "[%lu] %s.%06lu [#%ld %s (vlan %u) %u] \\\n",
                (u_long)len,
                when,
                (u_long)firstpkt->pkthdr.ts.tv_usec,
                (long)msgcount,
                mypcap->name ? mypcap->name : "\"some interface\"",
                vlan,
                vlan);
        } else {
            snprintf(descr, sizeof(descr), "[%lu] %s.%06lu [#%ld %s %u] \\\n",
                (u_long)len,
                when,
                (u_long)firstpkt->pkthdr.ts.tv_usec,
                (long)msgcount,
                mypcap->name ? mypcap->name : "\"some interface\"",
                vlan);
        }
    }

    if (next_interval != 0 && firstpkt->pkthdr.ts.tv_sec >= next_interval) {
        if (preso)
            goto breakloop;
        if (dumper_opened == dump_state)
            dumper_close(firstpkt->pkthdr.ts);
        if (dump_type == to_stdout)
            goto breakloop;
    }
    if (dumper_closed == dump_state && dumper_open(firstpkt->pkthdr.ts))
        goto breakloop;

    network_pkt2(descr, firstpkt->pkthdr.ts, packet, payload, length);

    if (limit_packets != 0U && msgcount == limit_packets) {
        if (preso)
            goto breakloop;
        if (dumper_opened == dump_state && dumper_close(firstpkt->pkthdr.ts))
            goto breakloop;
        msgcount = 0;
    }

    if (limit_pcapfilesize != 0U && capturedbytes >= limit_pcapfilesize) {
        if (preso) {
            goto breakloop;
        }
        if (dumper_opened == dump_state && dumper_close(firstpkt->pkthdr.ts)) {
            goto breakloop;
        }
        capturedbytes = 0;
    }

    return;
breakloop:
    breakloop_pcaps();
    main_exit = TRUE;
}

void dl_pkt(u_char* user, const struct pcap_pkthdr* hdr, const u_char* pkt, const char* name, const int dlt)
{
    mypcap_ptr mypcap = (mypcap_ptr)user;
    size_t     len    = hdr->caplen;
    unsigned   etype, vlan, pf;
    char       descr[512];

    if (only_offline_pcaps && start_time != 0 && hdr->ts.tv_sec < start_time)
        return;

    last_ts = hdr->ts;
    if (stop_time != 0 && hdr->ts.tv_sec >= stop_time) {
        breakloop_pcaps();
        main_exit = TRUE;
    }

    if (main_exit)
        return;

    /* If ever SNAPLEN wasn't big enough, we have no recourse. */
    if (hdr->len != hdr->caplen)
        return;

    /* Data link. */
    vlan = MAX_VLAN; /* MAX_VLAN (0xFFF) is reserved and shouldn't appear on the wire */
    switch (dlt) {
    case DLT_NULL: {
        uint32_t x;

        if (len < 4)
            return;
        x = _need32(pkt);
        if (x == PF_INET)
            etype = ETHERTYPE_IP;
        else if (x == PF_INET6)
            etype = ETHERTYPE_IPV6;
        else
            return;
        pkt += 4;
        len -= 4;
        break;
    }
    case DLT_LOOP: {
        uint32_t x;

        if (len < 4)
            return;
        x = _need32(pkt);
        if (x == PF_INET)
            etype = ETHERTYPE_IP;
        else if (x == PF_INET6)
            etype = ETHERTYPE_IPV6;
        else
            return;
        pkt += 4;
        len -= 4;
        break;
    }
    case DLT_RAW: {
        if (len < 1)
            return;
        switch (*(const uint8_t*)pkt >> 4) {
        case 4:
            etype = ETHERTYPE_IP;
            break;
        case 6:
            etype = ETHERTYPE_IPV6;
            break;
        default:
            return;
        }
        break;
    }
    case DLT_EN10MB: {
        const struct ether_header* ether;

        if (len < ETHER_HDR_LEN)
            return;
        ether = (const struct ether_header*)pkt;
        etype = ntohs(ether->ether_type);
        pkt += ETHER_HDR_LEN;
        len -= ETHER_HDR_LEN;
        if (etype == ETHERTYPE_VLAN) {
            if (len < 4)
                return;
            vlan = _need16(pkt) & 0xFFF;
            pkt += 2;
            len -= 2;
            etype = _need16(pkt);
            pkt += 2;
            len -= 2;
        }
        break;
    }
#ifdef DLT_LINUX_SLL
    case DLT_LINUX_SLL: {
        if (len < 16)
            return;
        etype = _need16(&pkt[14]);
        pkt += 16;
        len -= 16;
        break;
    }
#endif
    default:
        return;
    }

    if (!EMPTY(vlans_excl)) {
        vlan_ptr vl;

        for (vl = HEAD(vlans_excl);
             vl != NULL;
             vl = NEXT(vl, link))
            if (vl->vlan == vlan || vl->vlan == MAX_VLAN)
                break;
        /*
         * If there is no VLAN matching the packet, skip it
         */
        if (vl == NULL)
            return;
    } else if (!EMPTY(vlans_incl)) {
        vlan_ptr vl;

        for (vl = HEAD(vlans_incl);
             vl != NULL;
             vl = NEXT(vl, link))
            if (vl->vlan == vlan || vl->vlan == MAX_VLAN)
                break;
        /*
         * If there is no VLAN matching the packet, and the packet is tagged, skip it
         */
        if (vl == NULL && vlan != MAX_VLAN)
            return;
    }

    switch (etype) {
    case ETHERTYPE_IP:
        pf = PF_INET;
        break;
    case ETHERTYPE_IPV6:
        pf = PF_INET6;
        break;
    default:
        return;
    }

    if (preso) {
        char        when[100], via[100];
        const char* viap;
        struct tm   tm;
        time_t      t;

        t = (time_t)hdr->ts.tv_sec;
        gmtime_r(&t, &tm);
        strftime(when, sizeof when, "%Y-%m-%d %T", &tm);
        if (vlan != MAX_VLAN) {
            snprintf(via, sizeof(via), "%s (vlan %u)", mypcap->name ? mypcap->name : "\"some interface\"", vlan);
            viap = via;
        } else if (mypcap->name) {
            viap = mypcap->name;
        } else {
            viap = "\"some interface\"";
        }
        snprintf(descr, sizeof(descr), "[%lu] %s.%06lu [#%ld %s %u] \\\n",
            (u_long)len, when, (u_long)hdr->ts.tv_usec, (long)msgcount, viap, vlan);
    } else {
        descr[0] = '\0';
    }

    if (next_interval != 0 && hdr->ts.tv_sec >= next_interval) {
        if (preso)
            goto breakloop;
        if (dumper_opened == dump_state)
            dumper_close(hdr->ts);
        if (dump_type == to_stdout)
            goto breakloop;
    }
    if (dumper_closed == dump_state && dumper_open(hdr->ts))
        goto breakloop;

    network_pkt(descr, hdr->ts, pf, pkt, len);

    if (limit_packets != 0U && msgcount == limit_packets) {
        if (preso)
            goto breakloop;
        if (dumper_opened == dump_state && dumper_close(hdr->ts))
            goto breakloop;
        msgcount = 0;
    }

    if (limit_pcapfilesize != 0U && capturedbytes >= limit_pcapfilesize) {
        if (preso) {
            goto breakloop;
        }
        if (dumper_opened == dump_state && dumper_close(hdr->ts)) {
            goto breakloop;
        }
        capturedbytes = 0;
    }

    return;
breakloop:
    breakloop_pcaps();
    main_exit = TRUE;
}

/*
 * Determine if packet should be filtered based on query type
 * return values: -1 error
 *                 0 don't filter
 *                 1 filter
 * for non-zero return status, reason should be set to a
 * static string.
 */
static int
_filter_by_qtype(const ldns_pkt* lpkt, char** reason)
{
    ldns_rr_list* rrs = ldns_pkt_question(lpkt);
    if (!rrs) {
        *reason = "failed to get list of questions";
        return -1;
    }
    /* Look at each RR in the section (or each QNAME in
       the question section). */
    size_t i, n;
    for (i = 0, n = ldns_rr_list_rr_count(rrs); i < n; i++) {
        ldns_rr* rr = ldns_rr_list_rr(rrs, i);
        if (!rr) {
            *reason = "failed to get question";
            return -1;
        }
        if (match_qtype && ldns_rr_get_type(rr) == match_qtype) {
            if (dumptrace >= 2)
                fprintf(stderr, "Packet query type %u matches filter on type == %u\n", ldns_rr_get_type(rr), match_qtype);
            return 0;
        } else if (nmatch_qtype && ldns_rr_get_type(rr) == nmatch_qtype) {
            if (dumptrace >= 2)
                fprintf(stderr, "Packet query type %u matches filter on type != %u\n", ldns_rr_get_type(rr), nmatch_qtype);
            *reason = "matched unwanted qtype";
            return 1;
        }
    }
    if (match_qtype) {
        *reason = "didn't match wanted qtype";
        return 1; // didn't match any question RRs
    }
    return 0;
}

/*
 * Determine if packet should be filtered based on query name
 * return values: -1 error
 *                 0 don't filter
 *                 1 filter
 * for non-zero return status, reason should be set to a
 * static string.
 */
static int
_match_rr(const ldns_rr_list* rrs, char** reason, int* negmatch, int* match, ldns_buffer* buf)
{
    /* Look at each RR in the section (or each QNAME in
       the question section). */
    myregex_ptr myregex;
    size_t      i, n;
    for (i = 0, n = ldns_rr_list_rr_count(rrs); i < n; i++) {
        ldns_rr* rr = ldns_rr_list_rr(rrs, i);
        if (!rr) {
            *reason = "failed to get RR";
            return -1;
        }

        ldns_buffer_clear(buf);
        if (ldns_rdf2buffer_str(buf, ldns_rr_owner(rr)) != LDNS_STATUS_OK) {
            *reason = "failed to get RR";
            return -1;
        }

        for (myregex = HEAD(myregexes);
             myregex != NULL;
             myregex = NEXT(myregex, link)) {

            if (regexec(&myregex->reg, (char*)ldns_buffer_begin(buf), 0, NULL, 0) == 0) {
                if (myregex->not )
                    (*negmatch)++;
                else
                    (*match)++;

                if (dumptrace >= 2)
                    fprintf(stderr,
                        "; \"%s\" %s~ /%s/ %d %d\n",
                        (char*)ldns_buffer_begin(buf),
                        myregex->not ? "!" : "",
                        myregex->str,
                        *match,
                        *negmatch);
            }
        }
    }
    return 0;
}

static int
_filter_by_qname(const ldns_pkt* lpkt, char** reason)
{
    int          match    = -1;
    int          negmatch = -1;
    ldns_buffer* buf      = ldns_buffer_new(512);
    if (!buf) {
        fprintf(stderr, "%s: out of memory", ProgramName);
        exit(1);
    }

    /*
     * Initialize matching counters
     */
    myregex_ptr myregex;
    for (myregex = HEAD(myregexes); myregex != NULL; myregex = NEXT(myregex, link)) {
        if (myregex->not ) {
            negmatch = 0;
        } else {
            match = 0;
        }
    }

    ldns_rr_list* rrs;
    if ((rrs = ldns_pkt_question(lpkt))) {
        if (_match_rr(rrs, reason, &negmatch, &match, buf))
            return -1;
    }
    if ((rrs = ldns_pkt_answer(lpkt))) {
        if (_match_rr(rrs, reason, &negmatch, &match, buf))
            return -1;
    }
    if ((rrs = ldns_pkt_authority(lpkt))) {
        if (_match_rr(rrs, reason, &negmatch, &match, buf))
            return -1;
    }
    if ((rrs = ldns_pkt_additional(lpkt))) {
        if (_match_rr(rrs, reason, &negmatch, &match, buf))
            return -1;
    }
    ldns_buffer_free(buf);

    /*
     * Fail if any negative matches or if no positive matches
     */
    if (negmatch > 0 || match == 0) {
        *reason = "failed regex match";
        return 1;
    }
    return 0;
}

void network_pkt2(const char* descr, my_bpftimeval ts, const pcap_thread_packet_t* packet, const u_char* payload, size_t length)
{
    u_char       pkt_copy[SNAPLEN], *pkt = pkt_copy;
    u_char*      dnspkt = 0;
    unsigned     proto, sport, dport;
    iaddr        from, to, initiator, responder;
    int          response, m;
    unsigned     flags    = DNSCAP_OUTPUT_ISLAYER;
    tcpstate_ptr tcpstate = NULL;
    size_t       len, dnslen = 0;
    HEADER       dns;

    /* Make a writable copy of the packet and use that copy from now on. */
    if (length > SNAPLEN)
        return;
    memcpy(pkt, payload, len = length);

    /* Network. */
    sport = dport = 0;
    if (packet->have_iphdr) {
        if (dumptrace >= 4)
            fprintf(stderr, "processing IPv4 packet: len=%zu\n", length);

        memset(&from, 0, sizeof from);
        from.af = AF_INET;
        memcpy(&from.u.a4, &(packet->iphdr.ip_src), sizeof(struct in_addr));
        memset(&to, 0, sizeof to);
        to.af = AF_INET;
        memcpy(&to.u.a4, &(packet->iphdr.ip_dst), sizeof(struct in_addr));
    } else if (packet->have_ip6hdr) {
        if (dumptrace >= 4)
            fprintf(stderr, "processing IPv6 packet: len=%zu\n", length);

        memset(&from, 0, sizeof from);
        from.af = AF_INET6;
        memcpy(&from.u.a6, &(packet->ip6hdr.ip6_src), sizeof(struct in6_addr));
        memset(&to, 0, sizeof to);
        to.af = AF_INET6;
        memcpy(&to.u.a6, &(packet->ip6hdr.ip6_dst), sizeof(struct in6_addr));
    } else {
        if (dumptrace >= 4)
            fprintf(stderr, "processing unknown packet: len=%zu\n", length);
        from.af = AF_UNSPEC;
        to.af   = AF_UNSPEC;
    }

    /* Transport. */
    if (packet->have_icmphdr) {
        output(descr, from, to, IPPROTO_ICMP, flags, sport, dport, ts, pkt_copy, length, pkt, len);
        return;
    } else if (packet->have_icmpv6hdr) {
        output(descr, from, to, IPPROTO_ICMPV6, flags, sport, dport, ts, pkt_copy, length, pkt, len);
        return;
    } else if (packet->have_udphdr) {
        proto  = IPPROTO_UDP;
        sport  = packet->udphdr.uh_sport;
        dport  = packet->udphdr.uh_dport;
        dnspkt = pkt;
        dnslen = len;
        flags |= DNSCAP_OUTPUT_ISDNS;
    } else if (packet->have_tcphdr) {
        uint32_t seq = packet->tcphdr.th_seq;

        proto = IPPROTO_TCP;
        sport = packet->tcphdr.th_sport;
        dport = packet->tcphdr.th_dport;

        /*
         * TCP processing.
         *
         * We need to capture enough to allow a later analysis to
         * reassemble the TCP stream, but we don't want to keep all
         * the state required to do reassembly here.
         * When we get a SYN, we don't yet know if the DNS message
         * will pass the filters, so we always output it, and also
         * generate a tcpstate to keep track of the stream.  (An
         * alternative would be to store the SYN packet on the
         * tcpstate and not output it until a later packet passes the
         * filter, but that would require more memory and would
         * reorder packets in the pcap output.)
         * When we get the _first_ DNS header on the stream, then we
         * can apply the DNS header filters; if the packet passes, we
         * output the packet and keep the tcpstate; if it fails, we
         * discard the packet and the tcpstate.
         * When we get any other packet with DNS payload, we output it
         * only if there is a corresponding tcpstate indicating that
         * the header passed the filters.
         * Packets with no TCP payload (e.g., packets containing only
         * an ACK) are discarded, since they carry no DNS information
         * and are not needed for stream reassembly.
         * FIN packets are always output to match the SYN, even if the
         * DNS header failed the filter, to be friendly to later
         * analysis programs that allocate state for each SYN.
         * -- kkeys@caida.org
         */

        tcpstate = tcpstate_find(from, to, sport, dport, ts.tv_sec);
        if (dumptrace >= 3) {
            fprintf(stderr, "%s: tcp pkt: %lu.%06lu [%4lu] %15s -> ",
                ProgramName,
                (u_long)ts.tv_sec,
                (u_long)ts.tv_usec,
                (u_long)len,
                ia_str(from));
            fprintf(stderr, "%15s; ", ia_str(to));

            if (tcpstate)
                fprintf(stderr, "want=%08x; ", tcpstate->start);
            else
                fprintf(stderr, "no state; ");

            fprintf(stderr, "seq=%08x; ", seq);
        }
        if (packet->tcphdr.th_flags & (TH_FIN | TH_RST)) {
            if (dumptrace >= 3)
                fprintf(stderr, "FIN|RST\n");

            /* Always output FIN and RST segments. */
            _curr_tcpstate = tcpstate;
            output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0);
            _curr_tcpstate = 0;

            /* End of stream; deallocate the tcpstate. */
            tcpstate_free(tcpstate);
            return;
        }
        if (packet->tcphdr.th_flags & TH_SYN) {
            if (dumptrace >= 3)
                fprintf(stderr, "SYN\n");

            if (tcpstate) {
                if (tcpstate->start == seq + 1) {
                    /* repeated SYN */
                } else {
                    /* Assume existing state is stale and recycle it. */

                    /*
                     * Disabled because warning may scare user, and
                     * there's nothing else we can do anyway.
                     */

                    /*
                    if (ts.tv_sec - tcpstate->last_use < MAX_TCP_IDLE_TIME)
                    fprintf(stderr, "warning: recycling state for "
                        "duplicate tcp stream after only %ld "
                        "seconds idle\n",
                        (u_long)(ts.tv_sec - tcpstate->last_use));
                    */
                }
            } else {
                /* create new tcpstate */
                tcpstate = tcpstate_new(from, to, sport, dport);
            }
            tcpstate->last_use = ts.tv_sec;
            tcpstate->start    = seq + 1; /* add 1 for the SYN */
            tcpstate->maxdiff  = 1;
            tcpstate->dnslen   = 0;
            tcpstate->lastdns  = 0;

            /* Always output SYN segments. */
            _curr_tcpstate = tcpstate;
            output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0);
            _curr_tcpstate = 0;

            return;
        }
        if (options.parse_ongoing_tcp && !tcpstate && len) {
            tcpstate           = tcpstate_new(from, to, sport, dport);
            tcpstate->last_use = ts.tv_sec;
            tcpstate->start    = seq;
            tcpstate->maxdiff  = 0;
            tcpstate->dnslen   = 0;
            tcpstate->lastdns  = seq;
        }
        if (tcpstate && options.reassemble_tcp) {
            if (!tcpstate->reasm) {
                if (!(tcpstate->reasm = calloc(1, sizeof(tcpreasm_t)))) {
                    logerr("out of memory, TCP reassembly failed");
                    return;
                }
                tcpstate->reasm->seq_start = tcpstate->start;
                tcpstate->reasm->seq_bfb   = tcpstate->start;
            }
            if (options.allow_reset_tcpstate) {
                if (tcpstate->reasm_faults > options.reassemble_tcp_faultreset) {
                    if (dumptrace >= 3)
                        fprintf(stderr, "fault reset ");
                    tcpstate_reset(tcpstate, "too many reassembly faults");
                    tcpstate->reasm->seq_start = seq;
                    tcpstate->reasm->seq_bfb   = seq;
                    tcpstate->reasm_faults     = 0;
                }
                if (dumptrace >= 3)
                    fprintf(stderr, "reassemble\n");
                if (pcap_handle_tcp_segment(pkt, len, seq, tcpstate)) {
                    tcpstate->reasm_faults++;
                }
            } else {
                if (dumptrace >= 3)
                    fprintf(stderr, "reassemble\n");
                (void)pcap_handle_tcp_segment(pkt, len, seq, tcpstate);
            }
        } else if (tcpstate) {
            uint32_t seqdiff = seq - tcpstate->start;

            tcpstate->currseq = seq;
            tcpstate->currlen = len;

            if (options.allow_reset_tcpstate && tcpstate->lastdns && seq > tcpstate->lastdns + 2) {
                /*
                 * seq received is beyond where we expect next DNS message
                 * to be, reset tcpstate and continue
                 */
                tcpstate->maxdiff = 0;
                tcpstate->dnslen  = 0;
                tcpstate->lastdns = seq;
            }

            if (dumptrace >= 3)
                fprintf(stderr, "diff=%08x; lastdns=%08x; ", seqdiff, tcpstate->lastdns);

            if (tcpstate->lastdns && seq == tcpstate->lastdns && len > 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "+len+hdr\n");
                dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0);
                dnspkt                    = pkt + 2;
                if (dnslen > len - 2)
                    dnslen = len - 2;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->maxdiff = (uint32_t)len;
                tcpstate->lastdns = seq + 2 + tcpstate->dnslen;
            } else if (tcpstate->lastdns && seq == tcpstate->lastdns && len == 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "+len\n");
                tcpstate->dnslen  = (pkt[0] << 8) | (pkt[1] << 0);
                tcpstate->maxdiff = (uint32_t)len;

                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0);
                _curr_tcpstate = 0;
                return;
            } else if (tcpstate->lastdns && ((seq == tcpstate->lastdns && len == 1) || seqdiff == 1)) {
                tcpstate_discard(tcpstate, NULL);
                return;
            } else if (tcpstate->lastdns && seq == tcpstate->lastdns + 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "+hdr\n");
                tcpstate->maxdiff = seqdiff + (uint32_t)len;
                dnslen            = tcpstate->dnslen;
                dnspkt            = pkt;
                if (dnslen == 0) /* we never received it */
                    dnslen = len;
                if (dnslen > len)
                    dnslen = len;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->lastdns = seq + tcpstate->dnslen;
            } else if (seqdiff == 0 && len > 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "len+hdr\n");

                /*
                 * This is the first segment of the stream, and
                 * contains the dnslen and dns header, so we can
                 * filter on it.
                 */
                dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0);
                dnspkt                    = pkt + 2;
                if (dnslen > len - 2)
                    dnslen = len - 2;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->maxdiff = (uint32_t)len;
                tcpstate->lastdns = seq + 2 + tcpstate->dnslen;
            } else if (seqdiff == 0 && len == 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "len\n");

                /*
                 * This is the first segment of the stream, but only
                 * contains the dnslen.
                 */
                tcpstate->dnslen  = (pkt[0] << 8) | (pkt[1] << 0);
                tcpstate->maxdiff = (uint32_t)len;

                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0);
                _curr_tcpstate = 0;
                return;
            } else if ((seqdiff == 0 && len == 1) || seqdiff == 1) {
                /* shouldn't happen */
                tcpstate_discard(tcpstate, NULL);
                return;
            } else if (seqdiff == 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "hdr\n");

                /*
                 * This is not the first segment, but it does contain
                 * the first dns header, so we can filter on it.
                 */
                tcpstate->maxdiff = seqdiff + (uint32_t)len;
                dnslen            = tcpstate->dnslen;
                dnspkt            = pkt;
                if (dnslen == 0) /* we never received it */
                    dnslen = len;
                if (dnslen > len)
                    dnslen = len;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->lastdns = seq + tcpstate->dnslen;
            } else if (seqdiff > tcpstate->maxdiff + MAX_TCP_WINDOW) {
                if (dumptrace >= 3)
                    fprintf(stderr, "out of window\n");

                /* This segment is outside the window. */
                return;
            } else if (len == 0) {
                if (dumptrace >= 3)
                    fprintf(stderr, "empty\n");

                /* No payload (e.g., an ACK) */
                return;
            } else {
                if (dumptrace >= 3)
                    fprintf(stderr, "keep\n");

                /* non-first */
                if (tcpstate->maxdiff < seqdiff + (uint32_t)len)
                    tcpstate->maxdiff = seqdiff + (uint32_t)len;

                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0);
                _curr_tcpstate = 0;
                return;
            }
        } else {
            if (dumptrace >= 3)
                fprintf(stderr, "no state\n");

            /*
             * There is no state for this stream.  Either we never saw
             * a SYN for this stream, or we have already decided to
             * discard this stream.
             */
            return;
        }
    } else {
        return;
    }

    for (m = 0; m < MAX_TCP_DNS_MSG; m++) {
        if (tcpstate && tcpstate->reasm) {
            if (!tcpstate->reasm->dnsmsg[m])
                continue;
            dnslen = tcpstate->reasm->dnsmsg[m]->dnslen;
            dnspkt = tcpstate->reasm->dnsmsg[m]->dnspkt;
            flags |= DNSCAP_OUTPUT_ISDNS;
            if (tcpstate->reasm->dnsmsg[m]->segments_seen > 1) {
                /* emulate dnslen in own packet */
                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0);
                _curr_tcpstate = 0;
            }
        }

        /* Application. */
        if (!dnspkt) {
            tcpstate_discard(tcpstate, "no dns");
            return;
        }
        if (dnslen < sizeof dns) {
            tcpstate_discard(tcpstate, "too small");
            return;
        }
        memcpy(&dns, dnspkt, sizeof dns);

        /* Policy filtering. */
        if (dns.qr == 0 && dport == dns_port) {
            if ((dir_wanted & DIR_INITIATE) == 0) {
                tcpstate_discard(tcpstate, "unwanted dir=i");
                return;
            }
            initiator = from;
            responder = to;
            response  = FALSE;
        } else if (dns.qr != 0 && sport == dns_port) {
            if ((dir_wanted & DIR_RESPONSE) == 0) {
                tcpstate_discard(tcpstate, "unwanted dir=r");
                return;
            }
            initiator = to;
            responder = from;
            response  = TRUE;
        } else {
            tcpstate_discard(tcpstate, "unwanted direction/port");
            return;
        }
        if ((!EMPTY(initiators) && !ep_present(&initiators, initiator)) || (!EMPTY(responders) && !ep_present(&responders, responder))) {
            tcpstate_discard(tcpstate, "unwanted host");
            return;
        }
        if ((!EMPTY(not_initiators) && ep_present(&not_initiators, initiator)) || (!EMPTY(not_responders) && ep_present(&not_responders, responder))) {
            tcpstate_discard(tcpstate, "missing required host");
            return;
        }
        if (!(((msg_wanted & MSG_QUERY) != 0 && dns.opcode == LDNS_PACKET_QUERY) || ((msg_wanted & MSG_UPDATE) != 0 && dns.opcode == LDNS_PACKET_UPDATE) || ((msg_wanted & MSG_NOTIFY) != 0 && dns.opcode == LDNS_PACKET_NOTIFY))) {
            tcpstate_discard(tcpstate, "unwanted opcode");
            return;
        }
        if (response) {
            int match_tc    = (dns.tc != 0 && err_wanted & ERR_TRUNC);
            int match_rcode = err_wanted & (ERR_RCODE_BASE << dns.rcode);

            if (!match_tc && !match_rcode) {
                tcpstate_discard(tcpstate, "unwanted error code");
                return;
            }
            if (!EMPTY(drop_responders) && ep_present(&drop_responders, responder)) {
                tcpstate_discard(tcpstate, "dropped response due to -Y");
                return;
            }
        }
        if (!EMPTY(myregexes) || match_qtype || nmatch_qtype) {
            ldns_pkt* lpkt = 0;
            if (ldns_wire2pkt(&lpkt, dnspkt, dnslen) != LDNS_STATUS_OK) {
                /* DNS message may have padding, try get actual size */
                size_t dnslen2 = calcdnslen(dnspkt, dnslen);
                if (dnslen2 > 0 && dnslen2 < dnslen) {
                    if (ldns_wire2pkt(&lpkt, dnspkt, dnslen2) != LDNS_STATUS_OK) {
                        tcpstate_discard(tcpstate, "failed parse");
                        return;
                    }
                } else {
                    tcpstate_discard(tcpstate, "failed parse");
                    return;
                }
            }
            char* reason = 0;
            if ((match_qtype || nmatch_qtype) && _filter_by_qtype(lpkt, &reason)) {
                ldns_pkt_free(lpkt);
                tcpstate_discard(tcpstate, reason);
                return;
            }
            if (!EMPTY(myregexes) && _filter_by_qname(lpkt, &reason)) {
                ldns_pkt_free(lpkt);
                tcpstate_discard(tcpstate, reason);
                return;
            }
            ldns_pkt_free(lpkt);
        }

        /*
         * TODO: Policy hiding.
         */

        _curr_tcpstate = tcpstate;
        output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, dnspkt, dnslen);
        _curr_tcpstate = 0;

        if (tcpstate && tcpstate->reasm) {
            free(tcpstate->reasm->dnsmsg[m]);
            tcpstate->reasm->dnsmsg[m] = 0;
            tcpstate->reasm->dnsmsgs--;
        } else
            break;
    }
}

void network_pkt(const char* descr, my_bpftimeval ts, unsigned pf,
    const u_char* opkt, size_t olen)
{
    u_char          pkt_copy[SNAPLEN], *pkt = pkt_copy;
    u_char*         dnspkt = 0;
    unsigned        proto, sport, dport;
    iaddr           from, to, initiator, responder;
    struct ip6_hdr* ipv6;
    int             response, m;
    unsigned        flags    = 0;
    struct udphdr*  udp      = NULL;
    struct tcphdr*  tcp      = NULL;
    tcpstate_ptr    tcpstate = NULL;
    struct ip*      ip;
    size_t          len, dnslen = 0;
    HEADER          dns;

    if (dumptrace >= 4)
        fprintf(stderr, "processing %s packet: len=%zu\n", (pf == PF_INET ? "IPv4" : (pf == PF_INET6 ? "IPv6" : "unknown")), olen);

    /* Make a writable copy of the packet and use that copy from now on. */
    memcpy(pkt, opkt, len = olen);

    /* Network. */
    ip    = NULL;
    ipv6  = NULL;
    sport = dport = 0;
    switch (pf) {
    case PF_INET: {
        unsigned offset;

        if (len < sizeof *ip)
            return;
        network_ip = ip = (void*)pkt;
        network_ipv6    = 0;
        if (ip->ip_v != IPVERSION)
            goto network_pkt_end;
        proto = ip->ip_p;
        memset(&from, 0, sizeof from);
        from.af = AF_INET;
        memcpy(&from.u.a4, &ip->ip_src, sizeof(struct in_addr));
        memset(&to, 0, sizeof to);
        to.af = AF_INET;
        memcpy(&to.u.a4, &ip->ip_dst, sizeof(struct in_addr));
        offset = ip->ip_hl << 2;
        if (len > ntohs(ip->ip_len)) /* small IP packets have L2 padding */
            len = ntohs(ip->ip_len);
        if (len <= (size_t)offset)
            goto network_pkt_end;
        pkt += offset;
        len -= offset;
        offset = ntohs(ip->ip_off);
        if ((offset & IP_MF) != 0 || (offset & IP_OFFMASK) != 0) {
            if (wantfrags) {
                flags |= DNSCAP_OUTPUT_ISFRAG;
                output(descr, from, to, ip->ip_p, flags, sport, dport, ts, pkt_copy, olen, NULL, 0);
                goto network_pkt_end;
            }
            goto network_pkt_end;
        }
        break;
    }
    case PF_INET6: {
        uint16_t payload_len;
        uint8_t  nexthdr;
        unsigned offset;

        if (len < sizeof *ipv6)
            return;
        network_ipv6 = ipv6 = (void*)pkt;
        network_ip          = 0;
        if ((ipv6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION)
            goto network_pkt_end;

        nexthdr     = ipv6->ip6_nxt;
        offset      = sizeof(struct ip6_hdr);
        payload_len = ntohs(ipv6->ip6_plen);

        memset(&from, 0, sizeof from);
        from.af = AF_INET6;
        memcpy(&from.u.a6, &ipv6->ip6_src, sizeof(struct in6_addr));
        memset(&to, 0, sizeof to);
        to.af = AF_INET6;
        memcpy(&to.u.a6, &ipv6->ip6_dst, sizeof(struct in6_addr));

        while (nexthdr == IPPROTO_ROUTING || /* routing header */
               nexthdr == IPPROTO_HOPOPTS || /* Hop-by-Hop opts */
               nexthdr == IPPROTO_FRAGMENT || /* fragmentation hdr */
               nexthdr == IPPROTO_DSTOPTS || /* destination opts */
               nexthdr == IPPROTO_AH || /* destination opts */
               nexthdr == IPPROTO_ESP) /* encap sec payload */
        {
            struct {
                uint8_t nexthdr;
                uint8_t length;
            } ext_hdr;
            uint16_t ext_hdr_len;

            /* Catch broken packets */
            if ((offset + sizeof ext_hdr) > len)
                goto network_pkt_end;

            /* Cannot handle fragments. */
            if (nexthdr == IPPROTO_FRAGMENT) {
                if (wantfrags) {
                    flags |= DNSCAP_OUTPUT_ISFRAG;
                    output(descr, from, to, IPPROTO_FRAGMENT, flags, sport, dport, ts, pkt_copy, olen, NULL, 0);
                    goto network_pkt_end;
                }
                goto network_pkt_end;
            }

            memcpy(&ext_hdr, (u_char*)ipv6 + offset,
                sizeof ext_hdr);
            nexthdr     = ext_hdr.nexthdr;
            ext_hdr_len = (8 * (ntohs(ext_hdr.length) + 1));

            if (ext_hdr_len > payload_len)
                goto network_pkt_end;

            offset += ext_hdr_len;
            payload_len -= ext_hdr_len;
        }

        if ((offset + payload_len) > len || payload_len == 0)
            goto network_pkt_end;

        proto = nexthdr;
        pkt += offset;
        len -= offset;
        break;
    }
    default:
        goto network_pkt_end;
    }

    /* Transport. */
    switch (proto) {
    case IPPROTO_ICMP:
    case IPPROTO_ICMPV6:
        network_udp = 0;
        output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, olen, pkt, len);
        goto network_pkt_end;
    case IPPROTO_UDP: {
        if (len < sizeof *udp)
            goto network_pkt_end;
        network_udp = udp = (void*)pkt;
        switch (from.af) {
        case AF_INET:
        case AF_INET6:
            sport = ntohs(udp->uh_sport);
            dport = ntohs(udp->uh_dport);
            break;
        default:
            abort();
        }
        pkt += sizeof *udp;
        len -= sizeof *udp;
        dnspkt = pkt;
        dnslen = len;
        flags |= DNSCAP_OUTPUT_ISDNS;
        break;
    }
    case IPPROTO_TCP: {
        network_udp = 0;

        /* TCP processing.
         * We need to capture enough to allow a later analysis to
         * reassemble the TCP stream, but we don't want to keep all
         * the state required to do reassembly here.
         * When we get a SYN, we don't yet know if the DNS message
         * will pass the filters, so we always output it, and also
         * generate a tcpstate to keep track of the stream.  (An
         * alternative would be to store the SYN packet on the
         * tcpstate and not output it until a later packet passes the
         * filter, but that would require more memory and would
         * reorder packets in the pcap output.)
         * When we get the _first_ DNS header on the stream, then we
         * can apply the DNS header filters; if the packet passes, we
         * output the packet and keep the tcpstate; if it fails, we
         * discard the packet and the tcpstate.
         * When we get any other packet with DNS payload, we output it
         * only if there is a corresponding tcpstate indicating that
         * the header passed the filters.
         * Packets with no TCP payload (e.g., packets containing only
         * an ACK) are discarded, since they carry no DNS information
         * and are not needed for stream reassembly.
         * FIN packets are always output to match the SYN, even if the
         * DNS header failed the filter, to be friendly to later
         * analysis programs that allocate state for each SYN.
         * -- kkeys@caida.org
         */
        unsigned offset;
        uint32_t seq;
        if (!wanttcp)
            goto network_pkt_end;
        if (len < sizeof *tcp)
            goto network_pkt_end;
        tcp = (void*)pkt;
        switch (from.af) {
        case AF_INET:
        case AF_INET6:
            sport = ntohs(tcp->th_sport);
            dport = ntohs(tcp->th_dport);
            seq   = ntohl(tcp->th_seq);
            break;
        default:
            abort();
        }
        offset = tcp->th_off * 4;
        pkt += offset;
        len -= offset;

        tcpstate = tcpstate_find(from, to, sport, dport, ts.tv_sec);
        if (dumptrace >= 3) {
            fprintf(stderr, "%s: tcp pkt: %lu.%06lu [%4lu] ", ProgramName,
                (u_long)ts.tv_sec, (u_long)ts.tv_usec, (u_long)len);
            fprintf(stderr, "%15s -> ", ia_str(from));
            fprintf(stderr, "%15s; ", ia_str(to));
            if (tcpstate)
                fprintf(stderr, "want=%08x; ", tcpstate->start);
            else
                fprintf(stderr, "no state; ");
            fprintf(stderr, "seq=%08x; ", seq);
        }
        if (tcp->th_flags & (TH_FIN | TH_RST)) {
            /* Always output FIN and RST segments. */
            if (dumptrace >= 3)
                fprintf(stderr, "FIN|RST\n");
            _curr_tcpstate = tcpstate;
            output(descr, from, to, proto, flags, sport, dport, ts,
                pkt_copy, olen, NULL, 0);
            _curr_tcpstate = 0;
            /* End of stream; deallocate the tcpstate. */
            tcpstate_free(tcpstate);
            goto network_pkt_end;
        }
        if (tcp->th_flags & TH_SYN) {
            if (dumptrace >= 3)
                fprintf(stderr, "SYN\n");
            if (tcpstate) {
#if 0
            /* Disabled because warning may scare user, and
             * there's nothing else we can do anyway. */
            if (tcpstate->start == seq + 1) {
                /* repeated SYN */
            } else {
                /* Assume existing state is stale and recycle it. */
                if (ts.tv_sec - tcpstate->last_use < MAX_TCP_IDLE_TIME)
                fprintf(stderr, "warning: recycling state for "
                    "duplicate tcp stream after only %ld "
                    "seconds idle\n",
                    (u_long)(ts.tv_sec - tcpstate->last_use));
            }
#endif
            } else {
                /* create new tcpstate */
                tcpstate = tcpstate_new(from, to, sport, dport);
            }
            tcpstate->last_use = ts.tv_sec;
            tcpstate->start    = seq + 1; /* add 1 for the SYN */
            tcpstate->maxdiff  = 1;
            tcpstate->dnslen   = 0;
            tcpstate->lastdns  = 0;

            /* Always output SYN segments. */
            _curr_tcpstate = tcpstate;
            output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, olen, NULL, 0);
            _curr_tcpstate = 0;

            goto network_pkt_end;
        }
        if (options.parse_ongoing_tcp && !tcpstate && len) {
            tcpstate           = tcpstate_new(from, to, sport, dport);
            tcpstate->last_use = ts.tv_sec;
            tcpstate->start    = seq;
            tcpstate->maxdiff  = 0;
            tcpstate->dnslen   = 0;
            tcpstate->lastdns  = seq;
        }
        if (tcpstate && options.reassemble_tcp) {
            if (!tcpstate->reasm) {
                if (!(tcpstate->reasm = calloc(1, sizeof(tcpreasm_t)))) {
                    logerr("out of memory, TCP reassembly failed");
                    goto network_pkt_end;
                }
                tcpstate->reasm->seq_start = tcpstate->start;
                tcpstate->reasm->seq_bfb   = tcpstate->start;
            }
            if (options.allow_reset_tcpstate) {
                if (tcpstate->reasm_faults > options.reassemble_tcp_faultreset) {
                    if (dumptrace >= 3)
                        fprintf(stderr, "fault reset ");
                    tcpstate_reset(tcpstate, "too many reassembly faults");
                    tcpstate->reasm->seq_start = seq;
                    tcpstate->reasm->seq_bfb   = seq;
                    tcpstate->reasm_faults     = 0;
                }
                if (dumptrace >= 3)
                    fprintf(stderr, "reassemble\n");
                if (pcap_handle_tcp_segment(pkt, len, seq, tcpstate)) {
                    tcpstate->reasm_faults++;
                }
            } else {
                if (dumptrace >= 3)
                    fprintf(stderr, "reassemble\n");
                (void)pcap_handle_tcp_segment(pkt, len, seq, tcpstate);
            }
        } else if (tcpstate) {
            uint32_t seqdiff  = seq - tcpstate->start;
            tcpstate->currseq = seq;
            tcpstate->currlen = len;
            if (options.allow_reset_tcpstate && tcpstate->lastdns && seq > tcpstate->lastdns + 2) {
                /*
                 * seq received is beyond where we expect next DNS message
                 * to be, reset tcpstate and continue
                 */
                tcpstate->maxdiff = 0;
                tcpstate->dnslen  = 0;
                tcpstate->lastdns = seq;
            }
            if (dumptrace >= 3)
                fprintf(stderr, "diff=%08x; lastdns=%08x; ", seqdiff, tcpstate->lastdns);
            if (tcpstate->lastdns && seq == tcpstate->lastdns && len > 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "+len+hdr\n");
                dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0);
                dnspkt                    = pkt + 2;
                if (dnslen > len - 2)
                    dnslen = len - 2;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->maxdiff = (uint32_t)len;
                tcpstate->lastdns = seq + 2 + tcpstate->dnslen;
            } else if (tcpstate->lastdns && seq == tcpstate->lastdns && len == 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "+len\n");
                tcpstate->dnslen  = (pkt[0] << 8) | (pkt[1] << 0);
                tcpstate->maxdiff = (uint32_t)len;

                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts,
                    pkt_copy, olen, NULL, 0);
                _curr_tcpstate = 0;
                goto network_pkt_end;
            } else if (tcpstate->lastdns && ((seq == tcpstate->lastdns && len == 1) || seqdiff == 1)) {
                tcpstate_discard(tcpstate, NULL);
                goto network_pkt_end;
            } else if (tcpstate->lastdns && seq == tcpstate->lastdns + 2) {
                if (dumptrace >= 3)
                    fprintf(stderr, "+hdr\n");
                tcpstate->maxdiff = seqdiff + (uint32_t)len;
                dnslen            = tcpstate->dnslen;
                dnspkt            = pkt;
                if (dnslen == 0) /* we never received it */
                    dnslen = len;
                if (dnslen > len)
                    dnslen = len;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->lastdns = seq + tcpstate->dnslen;
            } else if (seqdiff == 0 && len > 2) {
                /* This is the first segment of the stream, and
                 * contains the dnslen and dns header, so we can
                 * filter on it. */
                if (dumptrace >= 3)
                    fprintf(stderr, "len+hdr\n");
                dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0);
                dnspkt                    = pkt + 2;
                if (dnslen > len - 2)
                    dnslen = len - 2;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->maxdiff = (uint32_t)len;
                tcpstate->lastdns = seq + 2 + tcpstate->dnslen;
            } else if (seqdiff == 0 && len == 2) {
                /* This is the first segment of the stream, but only
                 * contains the dnslen. */
                if (dumptrace >= 3)
                    fprintf(stderr, "len\n");
                tcpstate->dnslen  = (pkt[0] << 8) | (pkt[1] << 0);
                tcpstate->maxdiff = (uint32_t)len;

                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts,
                    pkt_copy, olen, NULL, 0);
                _curr_tcpstate = 0;
                goto network_pkt_end;
            } else if ((seqdiff == 0 && len == 1) || seqdiff == 1) {
                /* shouldn't happen */
                tcpstate_discard(tcpstate, NULL);
                goto network_pkt_end;
            } else if (seqdiff == 2) {
                /* This is not the first segment, but it does contain
                 * the first dns header, so we can filter on it. */
                if (dumptrace >= 3)
                    fprintf(stderr, "hdr\n");
                tcpstate->maxdiff = seqdiff + (uint32_t)len;
                dnslen            = tcpstate->dnslen;
                dnspkt            = pkt;
                if (dnslen == 0) /* we never received it */
                    dnslen = len;
                if (dnslen > len)
                    dnslen = len;
                flags |= DNSCAP_OUTPUT_ISDNS;
                tcpstate->lastdns = seq + tcpstate->dnslen;
            } else if (seqdiff > tcpstate->maxdiff + MAX_TCP_WINDOW) {
                /* This segment is outside the window. */
                if (dumptrace >= 3)
                    fprintf(stderr, "out of window\n");
                goto network_pkt_end;
            } else if (len == 0) {
                /* No payload (e.g., an ACK) */
                if (dumptrace >= 3)
                    fprintf(stderr, "empty\n");
                goto network_pkt_end;
            } else {
                /* non-first */
                if (dumptrace >= 3)
                    fprintf(stderr, "keep\n");
                if (tcpstate->maxdiff < seqdiff + (uint32_t)len)
                    tcpstate->maxdiff = seqdiff + (uint32_t)len;

                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts,
                    pkt_copy, olen, NULL, 0);
                _curr_tcpstate = 0;
                goto network_pkt_end;
            }
        } else {
            if (dumptrace >= 3)
                fprintf(stderr, "no state\n");
            /* There is no state for this stream.  Either we never saw
             * a SYN for this stream, or we have already decided to
             * discard this stream. */
            goto network_pkt_end;
        }
        break;
    }
    default:
        goto network_pkt_end;
    }

    for (m = 0; m < MAX_TCP_DNS_MSG; m++) {
        if (tcpstate && tcpstate->reasm) {
            if (!tcpstate->reasm->dnsmsg[m])
                continue;
            dnslen = tcpstate->reasm->dnsmsg[m]->dnslen;
            dnspkt = tcpstate->reasm->dnsmsg[m]->dnspkt;
            flags |= DNSCAP_OUTPUT_ISDNS;
            if (tcpstate->reasm->dnsmsg[m]->segments_seen > 1) {
                /* emulate dnslen in own packet */
                _curr_tcpstate = tcpstate;
                output(descr, from, to, proto, flags, sport, dport, ts,
                    pkt_copy, olen, NULL, 0);
                _curr_tcpstate = 0;
            }
        }

        /* Application. */
        if (!dnspkt) {
            tcpstate_discard(tcpstate, "no dns");
            goto network_pkt_end;
        }
        if (dnslen < sizeof dns) {
            tcpstate_discard(tcpstate, "too small");
            goto network_pkt_end;
        }
        memcpy(&dns, dnspkt, sizeof dns);

        /* Policy filtering. */
        if (dns.qr == 0 && dport == dns_port) {
            if ((dir_wanted & DIR_INITIATE) == 0) {
                tcpstate_discard(tcpstate, "unwanted dir=i");
                goto network_pkt_end;
            }
            initiator = from;
            responder = to;
            response  = FALSE;
        } else if (dns.qr != 0 && sport == dns_port) {
            if ((dir_wanted & DIR_RESPONSE) == 0) {
                tcpstate_discard(tcpstate, "unwanted dir=r");
                goto network_pkt_end;
            }
            initiator = to;
            responder = from;
            response  = TRUE;
        } else {
            tcpstate_discard(tcpstate, "unwanted direction/port");
            goto network_pkt_end;
        }
        if ((!EMPTY(initiators) && !ep_present(&initiators, initiator)) || (!EMPTY(responders) && !ep_present(&responders, responder))) {
            tcpstate_discard(tcpstate, "unwanted host");
            goto network_pkt_end;
        }
        if ((!EMPTY(not_initiators) && ep_present(&not_initiators, initiator)) || (!EMPTY(not_responders) && ep_present(&not_responders, responder))) {
            tcpstate_discard(tcpstate, "missing required host");
            goto network_pkt_end;
        }
        if (!(((msg_wanted & MSG_QUERY) != 0 && dns.opcode == LDNS_PACKET_QUERY) || ((msg_wanted & MSG_UPDATE) != 0 && dns.opcode == LDNS_PACKET_UPDATE) || ((msg_wanted & MSG_NOTIFY) != 0 && dns.opcode == LDNS_PACKET_NOTIFY))) {
            tcpstate_discard(tcpstate, "unwanted opcode");
            goto network_pkt_end;
        }
        if (response) {
            int match_tc    = (dns.tc != 0 && err_wanted & ERR_TRUNC);
            int match_rcode = err_wanted & (ERR_RCODE_BASE << dns.rcode);

            if (!match_tc && !match_rcode) {
                tcpstate_discard(tcpstate, "unwanted error code");
                goto network_pkt_end;
            }
            if (!EMPTY(drop_responders) && ep_present(&drop_responders, responder)) {
                tcpstate_discard(tcpstate, "dropped response due to -Y");
                goto network_pkt_end;
            }
        }
        if (!EMPTY(myregexes) || match_qtype || nmatch_qtype) {
            ldns_pkt* lpkt = 0;
            if (ldns_wire2pkt(&lpkt, dnspkt, dnslen) != LDNS_STATUS_OK) {
                /* DNS message may have padding, try get actual size */
                size_t dnslen2 = calcdnslen(dnspkt, dnslen);
                if (dnslen2 > 0 && dnslen2 < dnslen) {
                    if (ldns_wire2pkt(&lpkt, dnspkt, dnslen2) != LDNS_STATUS_OK) {
                        tcpstate_discard(tcpstate, "failed parse");
                        goto network_pkt_end;
                    }
                } else {
                    tcpstate_discard(tcpstate, "failed parse");
                    goto network_pkt_end;
                }
            }
            char* reason = 0;
            if ((match_qtype || nmatch_qtype) && _filter_by_qtype(lpkt, &reason)) {
                ldns_pkt_free(lpkt);
                tcpstate_discard(tcpstate, reason);
                goto network_pkt_end;
            }
            if (!EMPTY(myregexes) && _filter_by_qname(lpkt, &reason)) {
                ldns_pkt_free(lpkt);
                tcpstate_discard(tcpstate, reason);
                goto network_pkt_end;
            }
            ldns_pkt_free(lpkt);
        }

        /* Policy hiding. */
        if (end_hide != 0) {
            switch (from.af) {
            case AF_INET: {
                void *    init_addr, *resp_addr;
                uint16_t* init_port;

                if (dns.qr == 0) {
                    init_addr = (void*)&ip->ip_src;
                    resp_addr = (void*)&ip->ip_dst;
                    init_port = tcp ? &tcp->th_sport : &udp->uh_sport;
                } else {
                    init_addr = (void*)&ip->ip_dst;
                    resp_addr = (void*)&ip->ip_src;
                    init_port = tcp ? &tcp->th_dport : &udp->uh_dport;
                }

                if ((end_hide & END_INITIATOR) != 0) {
                    memcpy(init_addr, HIDE_INET, sizeof(struct in_addr));
                    *init_port = htons(HIDE_PORT);
                }
                if ((end_hide & END_RESPONDER) != 0)
                    memcpy(resp_addr, HIDE_INET, sizeof(struct in_addr));

                ip->ip_sum = 0;
                ip->ip_sum = ~in_checksum((u_char*)ip, sizeof *ip);
                if (udp)
                    udp->uh_sum = 0U;
                break;
            }
            case AF_INET6: {
                void *    init_addr, *resp_addr;
                uint16_t* init_port;

                if (dns.qr == 0) {
                    init_addr = (void*)&ipv6->ip6_src;
                    resp_addr = (void*)&ipv6->ip6_dst;
                    init_port = tcp ? &tcp->th_sport : &udp->uh_sport;
                } else {
                    init_addr = (void*)&ipv6->ip6_dst;
                    resp_addr = (void*)&ipv6->ip6_src;
                    init_port = tcp ? &tcp->th_dport : &udp->uh_dport;
                }

                if ((end_hide & END_INITIATOR) != 0) {
                    memcpy(init_addr, HIDE_INET6, sizeof(struct in6_addr));
                    *init_port = htons(HIDE_PORT);
                }
                if ((end_hide & END_RESPONDER) != 0)
                    memcpy(resp_addr, HIDE_INET6, sizeof(struct in6_addr));

                if (udp)
                    udp->uh_sum = 0U;
                break;
            }
            default:
                abort();
            }
        }
        _curr_tcpstate = tcpstate;
        output(descr, from, to, proto, flags, sport, dport, ts,
            pkt_copy, olen, dnspkt, dnslen);
        _curr_tcpstate = 0;

        if (tcpstate && tcpstate->reasm) {
            free(tcpstate->reasm->dnsmsg[m]);
            tcpstate->reasm->dnsmsg[m] = 0;
            tcpstate->reasm->dnsmsgs--;
        } else
            break;
    }

network_pkt_end:
    network_ip   = 0;
    network_ipv6 = 0;
}

uint16_t in_checksum(const u_char* ptr, size_t len)
{
    unsigned sum = 0, top;

    /* Main body. */
    while (len >= 2) {
        sum += *(const uint16_t*)ptr;
        ptr += 2;
        len -= 2;
    }

    /* Leftover octet? */
    if (len != 0)
        sum += *ptr;

    /* Leftover carries? */
    while ((top = (sum >> 16)) != 0)
        sum = ((uint16_t)sum) + top;

    /* Caller should ~ this result. */
    return ((uint16_t)sum);
}

static size_t calcrr(int q, const u_char* p, size_t l, size_t t)
{
    while (l < t) {
        if ((p[l] & 0xc0) == 0xc0) {
            l += 2;
        } else if (p[l] & 0xc0) {
            l += 1;
        } else if (p[l]) {
            l += p[l];
        } else {
            break;
        }
    }
    l += 4; /* type + class */
    if (q)
        return l;
    l += 6; /* ttl + rdlength */
    if (l < t) {
        l += (p[l - 2] << 8) + p[l - 1]; /* rdata */
    }
    return l;
}

size_t calcdnslen(const u_char* dnspkt, size_t dnslen)
{
    HEADER dns;
    size_t n, len;

    if (dnslen > 65535 || dnslen < sizeof(dns)) {
        return 0;
    }
    memcpy(&dns, dnspkt, sizeof dns);
    len = sizeof(dns);

    for (n = 0; len < dnslen && n < dns.qdcount; n++) {
        len = calcrr(1, dnspkt, len, dnslen);
    }
    for (n = 0; len < dnslen && n < dns.ancount; n++) {
        len = calcrr(0, dnspkt, len, dnslen);
    }
    for (n = 0; len < dnslen && n < dns.nscount; n++) {
        len = calcrr(0, dnspkt, len, dnslen);
    }
    for (n = 0; len < dnslen && n < dns.arcount; n++) {
        len = calcrr(0, dnspkt, len, dnslen);
    }
    if (len < dnslen)
        return len;
    return dnslen;
}
