// See the file "COPYING" in the main distribution directory for copyright.

#include "zeek/analyzer/protocol/dns/DNS.h"

#include "zeek/zeek-config.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <cctype>

#include "zeek/Event.h"
#include "zeek/NetVar.h"
#include "zeek/RunState.h"
#include "zeek/ZeekString.h"
#include "zeek/analyzer/protocol/dns/events.bif.h"
#include "zeek/session/Manager.h"

namespace zeek::analyzer::dns {

namespace detail {

// Used for checking whether the connection being parsed comes from NetBIOS,
// since it's similar to DNS but does some things differently.
constexpr int NETBIOS_PORT = 137;

DNS_Interpreter::DNS_Interpreter(analyzer::Analyzer* arg_analyzer) {
    analyzer = arg_analyzer;
    first_message = true;
    is_netbios =
        ntohs(analyzer->Conn()->OrigPort()) == NETBIOS_PORT || ntohs(analyzer->Conn()->RespPort()) == NETBIOS_PORT;
}

void DNS_Interpreter::ParseMessage(const u_char* data, int len, int is_query) {
    // Every packet for every opcode starts with same size header.
    int hdr_len = sizeof(detail::DNS_RawMsgHdr);

    if ( len < hdr_len ) {
        analyzer->Weird("DNS_truncated_len_lt_hdr_len");
        return;
    }

    // The flags section may be different between the different opcodes, but the
    // opcode is always in the same location. Parse out just that part of it here
    // even though it will probably be reparsed later.
    auto* hdr = (detail::DNS_RawMsgHdr*)data;
    unsigned short flags = ntohs(hdr->flags);
    int opcode = (flags & 0x7800) >> 11;

    // NetBIOS registration and release messages look like regular DNS requests, so parse them as such
    if ( opcode != DNS_OP_QUERY && ! is_netbios ) {
        analyzer->Weird("DNS_unknown_opcode", util::fmt("%d", opcode));
        analyzer->Conn()->CheckHistory(zeek::session::detail::HIST_UNKNOWN_PKT, 'X');
        return;
    }

    detail::DNS_MsgInfo msg(hdr, is_query);

    if ( first_message && msg.QR && is_query == 1 ) {
        is_query = msg.is_query = 0;

        if ( ! analyzer->Conn()->RespAddr().IsMulticast() )
            analyzer->Conn()->FlipRoles();
    }

    first_message = false;

    if ( dns_message )
        analyzer->EnqueueConnEvent(dns_message, analyzer->ConnVal(), val_mgr->Bool(is_query), msg.BuildHdrVal(),
                                   val_mgr->Count(len));

    // There is a great deal of non-DNS traffic that runs on port 53.
    // This should weed out most of it.
    if ( zeek::detail::dns_max_queries > 0 && msg.qdcount > zeek::detail::dns_max_queries ) {
        analyzer->AnalyzerViolation("DNS_Conn_count_too_large");
        analyzer->Weird("DNS_Conn_count_too_large");
        EndMessage(&msg);
        return;
    }

    const u_char* msg_start = data; // needed for interpreting compression

    data += hdr_len;
    len -= hdr_len;

    if ( ! ParseQuestions(&msg, data, len, msg_start) ) {
        EndMessage(&msg);
        return;
    }

    if ( ! ParseAnswers(&msg, msg.ancount, detail::DNS_ANSWER, data, len, msg_start) ) {
        EndMessage(&msg);
        return;
    }

    analyzer->AnalyzerConfirmation();

    int skip_auth = zeek::detail::dns_skip_all_auth;
    int skip_addl = zeek::detail::dns_skip_all_addl;
    if ( msg.ancount > 0 ) { // We did an answer, so can potentially skip auth/addl.
        static auto dns_skip_auth = id::find_val<TableVal>("dns_skip_auth");
        static auto dns_skip_addl = id::find_val<TableVal>("dns_skip_addl");
        auto server = make_intrusive<AddrVal>(analyzer->Conn()->RespAddr());

        skip_auth = skip_auth || msg.nscount == 0 || dns_skip_auth->FindOrDefault(server);
        skip_addl = skip_addl || msg.arcount == 0 || dns_skip_addl->FindOrDefault(server);
    }

    if ( skip_auth && skip_addl ) {
        // No point doing further work parsing the message.
        EndMessage(&msg);
        return;
    }

    msg.skip_event = skip_auth;
    if ( ! ParseAnswers(&msg, msg.nscount, detail::DNS_AUTHORITY, data, len, msg_start) ) {
        EndMessage(&msg);
        return;
    }

    if ( skip_addl ) {
        // No point doing further work parsing the message.
        EndMessage(&msg);
        return;
    }

    msg.skip_event = skip_addl;
    if ( ! ParseAnswers(&msg, msg.arcount, detail::DNS_ADDITIONAL, data, len, msg_start) ) {
        EndMessage(&msg);
        return;
    }

    EndMessage(&msg);
}

void DNS_Interpreter::EndMessage(detail::DNS_MsgInfo* msg) {
    if ( dns_end )
        analyzer->EnqueueConnEvent(dns_end, analyzer->ConnVal(), msg->BuildHdrVal());
}

bool DNS_Interpreter::ParseQuestions(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, const u_char* msg_start) {
    int n = msg->qdcount;

    while ( n > 0 && ParseQuestion(msg, data, len, msg_start) )
        --n;
    return n == 0;
}

bool DNS_Interpreter::ParseAnswers(detail::DNS_MsgInfo* msg, int n, detail::DNS_AnswerType atype, const u_char*& data,
                                   int& len, const u_char* msg_start) {
    msg->answer_type = atype;

    while ( n > 0 && ParseAnswer(msg, data, len, msg_start) )
        --n;

    return n == 0;
}

bool DNS_Interpreter::ParseQuestion(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, const u_char* msg_start) {
    u_char name[513];
    int name_len = sizeof(name) - 1;

    u_char* name_end = ExtractName(data, len, name, name_len, msg_start, false);
    if ( ! name_end )
        return false;

    if ( len < int(sizeof(short)) * 2 ) {
        analyzer->Weird("DNS_truncated_quest_too_short");
        return false;
    }

    EventHandlerPtr dns_event = nullptr;

    if ( msg->QR == 0 )
        dns_event = dns_request;

    else if ( msg->QR == 1 && msg->ancount == 0 && msg->nscount == 0 && msg->arcount == 0 )
        // Service rejected in some fashion, and it won't be reported
        // via a returned RR because there aren't any.
        dns_event = dns_rejected;
    else
        dns_event = dns_query_reply;

    if ( dns_event && ! msg->skip_event ) {
        String* original_name = new String(name, name_end - name, true);

        // Downcase the Name to normalize it
        for ( u_char* np = name; np < name_end; ++np )
            if ( isupper(*np) )
                *np = tolower(*np);

        String* question_name = new String(name, name_end - name, true);

        SendReplyOrRejectEvent(msg, dns_event, data, len, question_name, original_name);
    }
    else {
        // Consume the unused type/class.
        (void)ExtractShort(data, len);
        (void)ExtractShort(data, len);
    }

    return true;
}

bool DNS_Interpreter::ParseAnswer(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, const u_char* msg_start) {
    u_char name[513];
    int name_len = sizeof(name) - 1;

    u_char* name_end = ExtractName(data, len, name, name_len, msg_start);

    if ( ! name_end )
        return false;

    if ( len < int(sizeof(short)) * 2 ) {
        analyzer->Weird("DNS_truncated_ans_too_short");
        return false;
    }

    // Note that the exact meaning of some of these fields will be
    // re-interpreted by other, more adventurous RR types.

    msg->query_name = make_intrusive<StringVal>(new String(name, name_end - name, true));
    msg->atype = detail::RR_Type(ExtractShort(data, len));
    msg->aclass = ExtractShort(data, len);
    msg->ttl = ExtractLong(data, len);

    int rdlength = ExtractShort(data, len);
    if ( rdlength > len ) {
        analyzer->Weird("DNS_truncated_RR_rdlength_lt_len");
        return false;
    }
    else if ( rdlength == 0 && len > 0 ) {
        analyzer->Weird("DNS_zero_rdlength");
        return false;
    }

    bool status;
    switch ( msg->atype ) {
        case detail::TYPE_A: status = ParseRR_A(msg, data, len, rdlength); break;

        case detail::TYPE_A6:
        case detail::TYPE_AAAA: status = ParseRR_AAAA(msg, data, len, rdlength); break;

        case detail::TYPE_NS:
        case detail::TYPE_CNAME:
        case detail::TYPE_PTR: status = ParseRR_Name(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_SOA: status = ParseRR_SOA(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_WKS: status = ParseRR_WKS(msg, data, len, rdlength); break;

        case detail::TYPE_HINFO: status = ParseRR_HINFO(msg, data, len, rdlength); break;

        case detail::TYPE_MX: status = ParseRR_MX(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_TXT: status = ParseRR_TXT(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_SPF: status = ParseRR_SPF(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_CAA: status = ParseRR_CAA(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_NBS: status = ParseRR_NBS(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_SRV:
            if ( ntohs(analyzer->Conn()->RespPort()) == NETBIOS_PORT ) {
                // This is an NBSTAT (NetBIOS NODE STATUS) record.
                // The SRV RFC reused the value that was already being
                // used for this.
                // We aren't parsing this yet.
                status = true;
            }
            else
                status = ParseRR_SRV(msg, data, len, rdlength, msg_start);

            break;

        case detail::TYPE_EDNS: status = ParseRR_EDNS(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_TKEY: status = ParseRR_TKEY(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_TSIG: status = ParseRR_TSIG(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_RRSIG: status = ParseRR_RRSIG(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_DNSKEY: status = ParseRR_DNSKEY(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_NSEC: status = ParseRR_NSEC(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_NSEC3: status = ParseRR_NSEC3(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_NSEC3PARAM: status = ParseRR_NSEC3PARAM(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_DS: status = ParseRR_DS(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_BINDS: status = ParseRR_BINDS(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_SSHFP: status = ParseRR_SSHFP(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_LOC: status = ParseRR_LOC(msg, data, len, rdlength, msg_start); break;

        case detail::TYPE_SVCB: status = ParseRR_SVCB(msg, data, len, rdlength, msg_start, TYPE_SVCB); break;

        case detail::TYPE_HTTPS: status = ParseRR_SVCB(msg, data, len, rdlength, msg_start, TYPE_HTTPS); break;

        default:

            if ( dns_unknown_reply && ! msg->skip_event )
                analyzer->EnqueueConnEvent(dns_unknown_reply, analyzer->ConnVal(), msg->BuildHdrVal(),
                                           msg->BuildAnswerVal());

            analyzer->Weird("DNS_RR_unknown_type", util::fmt("%d", msg->atype));
            data += rdlength;
            len -= rdlength;
            status = true;
            break;
    }

    return status;
}

u_char* DNS_Interpreter::ExtractName(const u_char*& data, int& len, u_char* name, int name_len, const u_char* msg_start,
                                     bool downcase) {
    u_char* name_start = name;

    while ( ExtractLabel(data, len, name, name_len, msg_start) )
        ;

    int n = name - name_start;

    if ( n >= 255 )
        analyzer->Weird("DNS_NAME_too_long");

    if ( n >= 2 && name[-1] == '.' ) {
        // Remove trailing dot.
        --name;
        name[0] = 0;
    }

    // Convert labels to lower case for consistency.
    if ( downcase )
        for ( u_char* np = name_start; np < name; ++np )
            if ( isupper(*np) )
                *np = tolower(*np);

    return name;
}

bool DNS_Interpreter::ExtractLabel(const u_char*& data, int& len, u_char*& name, int& name_len,
                                   const u_char* msg_start) {
    if ( len <= 0 )
        return false;

    const u_char* orig_data = data;
    int label_len = data[0];

    ++data;
    --len;

    if ( len <= 0 )
        return false;

    if ( label_len == 0 )
        // Found terminating label.
        return false;

    if ( (label_len & 0xc0) == 0xc0 ) {
        unsigned short offset = (label_len & ~0xc0) << 8;

        offset |= *data;

        ++data;
        --len;

        if ( offset >= orig_data - msg_start ) {
            // (You'd think that actually the offset should be
            //  at least 6 bytes below our current position:
            //  2 bytes for a non-trivial label, plus 4 bytes for
            //  its class and type, which presumably are between
            //  our current location and the instance of the label.
            //  But actually this turns out not to be the case -
            //  sometimes compression points to compression.)

            analyzer->Weird("DNS_label_forward_compress_offset");
            return false;
        }

        // Recursively resolve name.
        const u_char* recurse_data = msg_start + offset;
        int recurse_max_len = orig_data - recurse_data;

        u_char* name_end = ExtractName(recurse_data, recurse_max_len, name, name_len, msg_start);

        name_len -= name_end - name;
        name = name_end;

        return false;
    }

    if ( label_len > len ) {
        analyzer->Weird("DNS_label_len_gt_pkt");
        data += len; // consume the rest of the packet
        len = 0;
        return false;
    }

    if ( label_len > 63 &&
         // NetBIOS name service look ups can use longer labels.
         ntohs(analyzer->Conn()->RespPort()) != NETBIOS_PORT ) {
        analyzer->Weird("DNS_label_too_long");
        return false;
    }

    if ( label_len >= name_len ) {
        analyzer->Weird("DNS_label_len_gt_name_len");
        return false;
    }

    memcpy(name, data, label_len);
    name[label_len] = '.';

    name += label_len + 1;
    name_len -= label_len + 1;

    data += label_len;
    len -= label_len;

    return true;
}

uint16_t DNS_Interpreter::ExtractShort(const u_char*& data, int& len) {
    if ( len < 2 )
        return 0;

    uint16_t val;

    val = data[0] << 8;

    ++data;
    --len;

    val |= data[0];

    ++data;
    --len;

    return val;
}

uint32_t DNS_Interpreter::ExtractLong(const u_char*& data, int& len) {
    if ( len < 4 )
        return 0;

    uint32_t val;

    val = data[0] << 24;
    val |= data[1] << 16;
    val |= data[2] << 8;
    val |= data[3];

    data += sizeof(val);
    len -= sizeof(val);

    return val;
}

bool DNS_Interpreter::ParseRR_Name(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                   const u_char* msg_start) {
    const u_char* data_start = data;

    u_char name[513];
    int name_len = sizeof(name) - 1;

    u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
    if ( ! name_end )
        return false;

    if ( data - data_start != rdlength ) {
        analyzer->Weird("DNS_RR_length_mismatch");
    }

    EventHandlerPtr reply_event;
    switch ( msg->atype ) {
        case detail::TYPE_NS: reply_event = dns_NS_reply; break;

        case detail::TYPE_CNAME:
        case detail::TYPE_AAAA:
        case detail::TYPE_A6: reply_event = dns_CNAME_reply; break;

        case detail::TYPE_PTR: reply_event = dns_PTR_reply; break;

        default: analyzer->Conn()->Internal("DNS_RR_bad_name"); reply_event = nullptr;
    }

    if ( reply_event && ! msg->skip_event )
        analyzer->EnqueueConnEvent(reply_event, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   make_intrusive<StringVal>(new String(name, name_end - name, true)));

    return true;
}

bool DNS_Interpreter::ParseRR_SOA(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                  const u_char* msg_start) {
    const u_char* data_start = data;

    u_char mname[513];
    int mname_len = sizeof(mname) - 1;

    u_char* mname_end = ExtractName(data, len, mname, mname_len, msg_start);
    if ( ! mname_end )
        return false;

    u_char rname[513];
    int rname_len = sizeof(rname) - 1;

    u_char* rname_end = ExtractName(data, len, rname, rname_len, msg_start);
    if ( ! rname_end )
        return false;

    if ( len < 20 )
        return false;

    uint32_t serial = ExtractLong(data, len);
    uint32_t refresh = ExtractLong(data, len);
    uint32_t retry = ExtractLong(data, len);
    uint32_t expire = ExtractLong(data, len);
    uint32_t minimum = ExtractLong(data, len);

    if ( data - data_start != rdlength )
        analyzer->Weird("DNS_RR_length_mismatch");

    if ( dns_SOA_reply && ! msg->skip_event ) {
        static auto dns_soa = id::find_type<RecordType>("dns_soa");
        auto r = make_intrusive<RecordVal>(dns_soa);
        r->Assign(0, new String(mname, mname_end - mname, true));
        r->Assign(1, new String(rname, rname_end - rname, true));
        r->Assign(2, serial);
        r->AssignInterval(3, double(refresh));
        r->AssignInterval(4, double(retry));
        r->AssignInterval(5, double(expire));
        r->AssignInterval(6, double(minimum));

        analyzer->EnqueueConnEvent(dns_SOA_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   std::move(r));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_MX(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                 const u_char* msg_start) {
    const u_char* data_start = data;

    int preference = ExtractShort(data, len);

    u_char name[513];
    int name_len = sizeof(name) - 1;

    u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
    if ( ! name_end )
        return false;

    if ( data - data_start != rdlength )
        analyzer->Weird("DNS_RR_length_mismatch");

    if ( dns_MX_reply && ! msg->skip_event )
        analyzer->EnqueueConnEvent(dns_MX_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   make_intrusive<StringVal>(new String(name, name_end - name, true)),
                                   val_mgr->Count(preference));

    return true;
}

bool DNS_Interpreter::ParseRR_NBS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                  const u_char* msg_start) {
    data += rdlength;
    len -= rdlength;
    return true;
}

bool DNS_Interpreter::ParseRR_SRV(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                  const u_char* msg_start) {
    const u_char* data_start = data;

    unsigned int priority = ExtractShort(data, len);
    unsigned int weight = ExtractShort(data, len);
    unsigned int port = ExtractShort(data, len);

    u_char name[513];
    int name_len = sizeof(name) - 1;

    u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
    if ( ! name_end )
        return false;

    if ( data - data_start != rdlength )
        analyzer->Weird("DNS_RR_length_mismatch");

    if ( dns_SRV_reply && ! msg->skip_event )
        analyzer->EnqueueConnEvent(dns_SRV_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   make_intrusive<StringVal>(new String(name, name_end - name, true)),
                                   val_mgr->Count(priority), val_mgr->Count(weight), val_mgr->Count(port));

    return true;
}

bool DNS_Interpreter::ParseRR_EDNS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                   const u_char* msg_start) {
    if ( dns_EDNS_addl && ! msg->skip_event )
        analyzer->EnqueueConnEvent(dns_EDNS_addl, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildEDNS_Val());

    // parse EDNS options. length has to be at least 4 to parse out the option
    // code and length.
    while ( len >= 4 ) {
        uint16_t option_code = ExtractShort(data, len);
        int option_len = ExtractShort(data, len);
        // check for invalid option length
        if ( (option_len > len) ) {
            break;
        }
        len -= option_len;

        // TODO: Implement additional option codes
        switch ( option_code ) {
            case detail::TYPE_ECS: {
                // must be 4 bytes + variable number of octets for address
                if ( option_len <= 4 ) {
                    analyzer->Weird("EDNS_ECS_invalid_option_len");
                    data += option_len;
                    break;
                }

                detail::EDNS_ECS opt{};
                uint16_t ecs_family = ExtractShort(data, option_len);
                uint16_t source_scope = ExtractShort(data, option_len);
                opt.ecs_src_pfx_len = (source_scope >> 8) & 0xff;
                opt.ecs_scp_pfx_len = source_scope & 0xff;

                // ADDRESS, variable number of octets, contains either an IPv4 or
                // IPv6 address, depending on FAMILY, which MUST be truncated to the
                // number of bits indicated by the SOURCE PREFIX-LENGTH field,
                // padding with 0 bits to pad to the end of the last octet needed.
                if ( ecs_family == L3_IPV4 ) {
                    if ( opt.ecs_src_pfx_len > 32 ) {
                        analyzer->Weird("EDNS_ECS_invalid_addr_v4_prefix",
                                        util::fmt("%" PRIu16 " bits", opt.ecs_src_pfx_len));
                        data += option_len;
                        break;
                    }

                    if ( opt.ecs_src_pfx_len > option_len * 8 ) {
                        analyzer->Weird("EDNS_ECS_invalid_addr_v4", util::fmt("need %" PRIu16 " bits, have %d bits",
                                                                              opt.ecs_src_pfx_len, option_len * 8));
                        data += option_len;
                        break;
                    }

                    opt.ecs_family = make_intrusive<StringVal>("v4");
                    uint32_t addr = 0;
                    uint16_t shift_factor = 3;
                    int bits_left = opt.ecs_src_pfx_len;

                    while ( bits_left > 0 ) {
                        addr |= data[0] << (shift_factor * 8);
                        data++;
                        shift_factor--;
                        option_len--;
                        bits_left -= 8;
                    }

                    addr = htonl(addr);
                    opt.ecs_addr = make_intrusive<AddrVal>(addr);
                }
                else if ( ecs_family == L3_IPV6 ) {
                    if ( opt.ecs_src_pfx_len > 128 ) {
                        analyzer->Weird("EDNS_ECS_invalid_addr_v6_prefix",
                                        util::fmt("%" PRIu16 " bits", opt.ecs_src_pfx_len));
                        data += option_len;
                        break;
                    }

                    if ( opt.ecs_src_pfx_len > option_len * 8 ) {
                        analyzer->Weird("EDNS_ECS_invalid_addr_v6", util::fmt("need %" PRIu16 " bits, have %d bits",
                                                                              opt.ecs_src_pfx_len, option_len * 8));
                        data += option_len;
                        break;
                    }

                    opt.ecs_family = make_intrusive<StringVal>("v6");
                    uint32_t addr[4] = {0};
                    uint16_t shift_factor = 15;
                    int bits_left = opt.ecs_src_pfx_len;
                    int i = 0;

                    while ( bits_left > 0 ) {
                        addr[i / 4] |= data[0] << ((shift_factor % 4) * 8);
                        data++;
                        i++;
                        shift_factor--;
                        option_len--;
                        bits_left -= 8;
                    }

                    for ( uint8_t i = 0; i < 4; i++ ) {
                        addr[i] = htonl(addr[i]);
                    }
                    opt.ecs_addr = make_intrusive<AddrVal>(addr);
                }
                else {
                    // non ipv4/ipv6 family address
                    data += option_len;
                    break;
                }

                analyzer->EnqueueConnEvent(dns_EDNS_ecs, analyzer->ConnVal(), msg->BuildHdrVal(),
                                           msg->BuildEDNS_ECS_Val(&opt));
                data += option_len;
                break;
            } // END EDNS ECS

            case TYPE_TCP_KA: {
                EDNS_TCP_KEEPALIVE edns_tcp_keepalive{true, 0};
                if ( option_len == 0 || option_len == 2 ) {
                    // 0 bytes is permitted by RFC 7828, showing that the timeout value is
                    // omitted.
                    if ( option_len == 2 ) {
                        edns_tcp_keepalive.keepalive_timeout = ExtractShort(data, option_len);
                        edns_tcp_keepalive.keepalive_timeout_omitted = false;
                    }

                    if ( analyzer->Conn()->ConnTransport() == TRANSPORT_UDP ) {
                        /*
                         * Based on RFC 7828 (3.2.1/3.2.2), clients and servers MUST NOT
                         * negotiate TCP Keepalive timeout in DNS-over-UDP.
                         */
                        analyzer->Weird("EDNS_TCP_Keepalive_In_UDP");
                    }

                    analyzer->EnqueueConnEvent(dns_EDNS_tcp_keepalive, analyzer->ConnVal(), msg->BuildHdrVal(),
                                               msg->BuildEDNS_TCP_KA_Val(&edns_tcp_keepalive));
                }
                else {
                    // error. MUST BE 0 or 2 bytes. skip
                    data += option_len;
                }
                break;
            } // END EDNS TCP KEEPALIVE

            case TYPE_COOKIE: {
                EDNS_COOKIE cookie{};
                if ( option_len != 8 && ! (option_len >= 16 && option_len <= 40) ) {
                    /*
                     * option length for DNS Cookie must be 8 bytes (with client cookie only)
                     * OR
                     * between 16 bytes to 40 bytes (with an 8 bytes client and an 8 to 32 bytes
                     * server cookie)
                     */
                    data += option_len;
                    break;
                }

                int client_cookie_len = 8;
                int server_cookie_len = option_len - client_cookie_len;

                cookie.client_cookie = ExtractStream(data, client_cookie_len, client_cookie_len);
                cookie.server_cookie = nullptr;

                if ( server_cookie_len >= 8 ) {
                    cookie.server_cookie = ExtractStream(data, server_cookie_len, server_cookie_len);
                }

                analyzer->EnqueueConnEvent(dns_EDNS_cookie, analyzer->ConnVal(), msg->BuildHdrVal(),
                                           msg->BuildEDNS_COOKIE_Val(&cookie));

                break;
            } // END EDNS COOKIE

            default: {
                data += option_len;
                break;
            }
        }
    }

    if ( len > 0 ) {
        analyzer->Weird("EDNS_truncated_option");
        return false;
    }

    return true;
}

void DNS_Interpreter::ExtractOctets(const u_char*& data, int& len, String** p) {
    uint16_t dlen = ExtractShort(data, len);
    dlen = min(len, static_cast<int>(dlen));

    if ( p )
        *p = new String(data, dlen, false);

    data += dlen;
    len -= dlen;
}

String* DNS_Interpreter::ExtractStream(const u_char*& data, int& len, int l) {
    l = max(l, 0);
    int dlen = min(len, l); // Len in bytes of the algorithm use
    auto rval = new String(data, dlen, false);

    data += dlen;
    len -= dlen;
    return rval;
}

bool DNS_Interpreter::ParseRR_TSIG(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                   const u_char* msg_start) {
    const u_char* data_start = data;
    u_char alg_name[1024];
    int alg_name_len = sizeof(alg_name) - 1;

    u_char* alg_name_end = ExtractName(data, len, alg_name, alg_name_len, msg_start);

    if ( ! alg_name_end )
        return false;

    uint32_t sign_time_sec = ExtractLong(data, len);
    unsigned int sign_time_msec = ExtractShort(data, len);
    unsigned int fudge = ExtractShort(data, len);
    String* request_MAC;
    ExtractOctets(data, len, dns_TSIG_addl ? &request_MAC : nullptr);
    unsigned int orig_id = ExtractShort(data, len);
    unsigned int rr_error = ExtractShort(data, len);
    ExtractOctets(data, len, nullptr); // Other Data

    if ( dns_TSIG_addl ) {
        detail::TSIG_DATA tsig;
        tsig.alg_name = new String(alg_name, alg_name_end - alg_name, true);
        tsig.sig = request_MAC;
        tsig.time_s = sign_time_sec;
        tsig.time_ms = sign_time_msec;
        tsig.fudge = fudge;
        tsig.orig_id = orig_id;
        tsig.rr_error = rr_error;

        analyzer->EnqueueConnEvent(dns_TSIG_addl, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildTSIG_Val(&tsig));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_TKEY(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                   const u_char* msg_start) {
    if ( ! dns_TKEY || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 16 )
        return false;

    const u_char* data_start = data;
    u_char alg_name[513];
    int alg_name_len = sizeof(alg_name) - 1;

    u_char* alg_name_end = ExtractName(data, len, alg_name, alg_name_len, msg_start);

    if ( ! alg_name_end )
        return false;

    uint32_t inception = ExtractLong(data, len);
    uint32_t expiration = ExtractLong(data, len);
    uint16_t mode = ExtractShort(data, len);
    uint16_t error = ExtractShort(data, len);
    String* key_data;
    ExtractOctets(data, len, dns_TKEY ? &key_data : nullptr);
    ExtractOctets(data, len, nullptr); // Other data

    if ( dns_TKEY ) {
        detail::TKEY_DATA tkey;
        tkey.alg_name = new String(alg_name, int(alg_name_end - alg_name), true);
        tkey.inception = inception;
        tkey.expiration = expiration;
        tkey.mode = mode;
        tkey.error = error;
        tkey.key = key_data;
        analyzer->EnqueueConnEvent(dns_TKEY, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildTKEY_Val(&tkey));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_RRSIG(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                    const u_char* msg_start) {
    if ( ! dns_RRSIG || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 18 )
        return false;

    unsigned int type_covered = ExtractShort(data, len);
    // split the two bytes for algo and labels extraction
    uint32_t algo_lab = ExtractShort(data, len);
    unsigned int algo = (algo_lab >> 8) & 0xff;
    unsigned int lab = algo_lab & 0xff;

    uint32_t orig_ttl = ExtractLong(data, len);
    uint32_t sign_exp = ExtractLong(data, len);
    uint32_t sign_incp = ExtractLong(data, len);
    unsigned int key_tag = ExtractShort(data, len);

    // implement signer's name with the msg_start offset
    const u_char* data_start = data;
    u_char name[513];
    int name_len = sizeof(name) - 1;

    u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
    if ( ! name_end )
        return false;

    int sig_len = rdlength - ((data - data_start) + 18);
    detail::DNSSEC_Algo dsa = detail::DNSSEC_Algo(algo);
    String* sign = ExtractStream(data, len, sig_len);

    switch ( dsa ) {
        case detail::RSA_MD5: analyzer->Weird("DNSSEC_RRSIG_NotRecommended_ZoneSignAlgo", util::fmt("%d", algo)); break;
        case detail::Diffie_Hellman: break;
        case detail::DSA_SHA1: break;
        case detail::Elliptic_Curve: break;
        case detail::RSA_SHA1: break;
        case detail::DSA_NSEC3_SHA1: break;
        case detail::RSA_SHA1_NSEC3_SHA1: break;
        case detail::RSA_SHA256: break;
        case detail::RSA_SHA512: break;
        case detail::GOST_R_34_10_2001: break;
        case detail::ECDSA_curveP256withSHA256: break;
        case detail::ECDSA_curveP384withSHA384: break;
        case detail::Ed25519: break;
        case detail::Ed448: break;
        case detail::Indirect: analyzer->Weird("DNSSEC_RRSIG_Indirect_ZoneSignAlgo", util::fmt("%d", algo)); break;
        case detail::PrivateDNS: analyzer->Weird("DNSSEC_RRSIG_PrivateDNS_ZoneSignAlgo", util::fmt("%d", algo)); break;
        case detail::PrivateOID: analyzer->Weird("DNSSEC_RRSIG_PrivateOID_ZoneSignAlgo", util::fmt("%d", algo)); break;
        default: analyzer->Weird("DNSSEC_RRSIG_unknown_ZoneSignAlgo", util::fmt("%d", algo)); break;
    }

    if ( dns_RRSIG ) {
        detail::RRSIG_DATA rrsig;
        rrsig.type_covered = type_covered;
        rrsig.algorithm = algo;
        rrsig.labels = lab;
        rrsig.orig_ttl = orig_ttl;
        rrsig.sig_exp = sign_exp;
        rrsig.sig_incep = sign_incp;
        rrsig.key_tag = key_tag;
        rrsig.signer_name = new String(name, name_end - name, true);
        rrsig.signature = sign;

        analyzer->EnqueueConnEvent(dns_RRSIG, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   msg->BuildRRSIG_Val(&rrsig));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_DNSKEY(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                     const u_char* msg_start) {
    if ( ! dns_DNSKEY || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 4 )
        return false;

    auto dflags = ExtractShort(data, len);
    // split the two bytes for protocol and algorithm extraction
    auto proto_algo = ExtractShort(data, len);
    unsigned int dprotocol = (proto_algo >> 8) & 0xff;
    unsigned int dalgorithm = proto_algo & 0xff;
    detail::DNSSEC_Algo dsa = detail::DNSSEC_Algo(dalgorithm);
    // Evaluating the size of remaining bytes for Public Key
    String* key = ExtractStream(data, len, rdlength - 4);

    // flags bit  7: zone key
    // flags bit  8: revoked
    // flags bit 15: Secure Entry Point, key signing key
    if ( (dflags & 0xfe7e) != 0 )
        analyzer->Weird("DNSSEC_DNSKEY_Invalid_Flag", util::fmt("%d", dflags));

    // flags bit 7, 8, and 15 all set
    if ( (dflags & 0x0181) == 0x0181 )
        analyzer->Weird("DNSSEC_DNSKEY_Revoked_KSK", util::fmt("%d", dflags));

    if ( dprotocol != 3 )
        analyzer->Weird("DNSSEC_DNSKEY_Invalid_Protocol", util::fmt("%d", dprotocol));

    switch ( dsa ) {
        case detail::RSA_MD5:
            analyzer->Weird("DNSSEC_DNSKEY_NotRecommended_ZoneSignAlgo", util::fmt("%d", dalgorithm));
            break;
        case detail::Diffie_Hellman: break;
        case detail::DSA_SHA1: break;
        case detail::Elliptic_Curve: break;
        case detail::RSA_SHA1: break;
        case detail::DSA_NSEC3_SHA1: break;
        case detail::RSA_SHA1_NSEC3_SHA1: break;
        case detail::RSA_SHA256: break;
        case detail::RSA_SHA512: break;
        case detail::GOST_R_34_10_2001: break;
        case detail::ECDSA_curveP256withSHA256: break;
        case detail::ECDSA_curveP384withSHA384: break;
        case detail::Ed25519: break;
        case detail::Ed448: break;
        case detail::Indirect:
            analyzer->Weird("DNSSEC_DNSKEY_Indirect_ZoneSignAlgo", util::fmt("%d", dalgorithm));
            break;
        case detail::PrivateDNS:
            analyzer->Weird("DNSSEC_DNSKEY_PrivateDNS_ZoneSignAlgo", util::fmt("%d", dalgorithm));
            break;
        case detail::PrivateOID:
            analyzer->Weird("DNSSEC_DNSKEY_PrivateOID_ZoneSignAlgo", util::fmt("%d", dalgorithm));
            break;
        default: analyzer->Weird("DNSSEC_DNSKEY_unknown_ZoneSignAlgo", util::fmt("%d", dalgorithm)); break;
    }

    if ( dns_DNSKEY ) {
        detail::DNSKEY_DATA dnskey;
        dnskey.dflags = dflags;
        dnskey.dalgorithm = dalgorithm;
        dnskey.dprotocol = dprotocol;
        dnskey.public_key = key;

        analyzer->EnqueueConnEvent(dns_DNSKEY, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   msg->BuildDNSKEY_Val(&dnskey));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_NSEC(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                   const u_char* msg_start) {
    if ( ! dns_NSEC || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    const u_char* data_start = data;
    u_char name[513];
    int name_len = sizeof(name) - 1;

    u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
    if ( ! name_end )
        return false;

    int typebitmaps_len = rdlength - (data - data_start);

    auto char_strings = make_intrusive<VectorVal>(id::string_vec);

    while ( typebitmaps_len > 0 && len > 0 ) {
        uint32_t block_bmlen = ExtractShort(data, len);
        unsigned int win_blck = (block_bmlen >> 8) & 0xff;
        unsigned int bmlen = block_bmlen & 0xff;

        if ( bmlen == 0 ) {
            analyzer->Weird("DNSSEC_NSEC_bitmapLen0", util::fmt("%d", win_blck));
            break;
        }

        String* bitmap = ExtractStream(data, len, bmlen);
        char_strings->Assign(char_strings->Size(), make_intrusive<StringVal>(bitmap));
        typebitmaps_len = typebitmaps_len - (2 + bmlen);
    }

    if ( dns_NSEC )
        analyzer->EnqueueConnEvent(dns_NSEC, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   make_intrusive<StringVal>(new String(name, name_end - name, true)),
                                   std::move(char_strings));

    return true;
}

bool DNS_Interpreter::ParseRR_NSEC3(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                    const u_char* msg_start) {
    if ( ! dns_NSEC3 || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 6 )
        return false;

    const u_char* data_start = data;
    uint32_t halgo_flags = ExtractShort(data, len);
    unsigned int hash_algo = (halgo_flags >> 8) & 0xff;
    unsigned int nsec_flags = halgo_flags & 0xff;
    unsigned int iter = ExtractShort(data, len);

    uint8_t salt_len = 0;

    if ( len > 0 ) {
        salt_len = data[0];
        ++data;
        --len;
    }

    auto salt_val = ExtractStream(data, len, static_cast<int>(salt_len));

    uint8_t hash_len = 0;

    if ( len > 0 ) {
        hash_len = data[0];
        ++data;
        --len;
    }

    auto hash_val = ExtractStream(data, len, static_cast<int>(hash_len));

    int typebitmaps_len = rdlength - (data - data_start);

    auto char_strings = make_intrusive<VectorVal>(id::string_vec);

    while ( typebitmaps_len > 0 && len > 0 ) {
        uint32_t block_bmlen = ExtractShort(data, len);
        unsigned int win_blck = (block_bmlen >> 8) & 0xff;
        unsigned int bmlen = block_bmlen & 0xff;

        if ( bmlen == 0 ) {
            analyzer->Weird("DNSSEC_NSEC3_bitmapLen0", util::fmt("%d", win_blck));
            break;
        }

        String* bitmap = ExtractStream(data, len, bmlen);
        char_strings->Assign(char_strings->Size(), make_intrusive<StringVal>(bitmap));
        typebitmaps_len = typebitmaps_len - (2 + bmlen);
    }

    if ( dns_NSEC3 ) {
        detail::NSEC3_DATA nsec3;
        nsec3.nsec_flags = nsec_flags;
        nsec3.nsec_hash_algo = hash_algo;
        nsec3.nsec_iter = iter;
        nsec3.nsec_salt_len = salt_len;
        nsec3.nsec_salt = salt_val;
        nsec3.nsec_hlen = hash_len;
        nsec3.nsec_hash = hash_val;
        nsec3.bitmaps = std::move(char_strings);

        analyzer->EnqueueConnEvent(dns_NSEC3, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   msg->BuildNSEC3_Val(&nsec3));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_NSEC3PARAM(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                         const u_char* msg_start) {
    if ( ! dns_NSEC3PARAM || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 5 )
        return false;

    uint32_t halgo_flags = ExtractShort(data, len);
    unsigned int hash_algo = (halgo_flags >> 8) & 0xff;
    unsigned int nsec_flags = halgo_flags & 0xff;
    unsigned int iter = ExtractShort(data, len);

    uint8_t salt_len = 0;

    if ( len > 0 ) {
        salt_len = data[0];
        ++data;
        --len;
    }

    auto salt_value = ExtractStream(data, len, static_cast<int>(salt_len));

    if ( dns_NSEC3PARAM ) {
        detail::NSEC3PARAM_DATA nsec3param;
        nsec3param.nsec_flags = nsec_flags;
        nsec3param.nsec_hash_algo = hash_algo;
        nsec3param.nsec_iter = iter;
        nsec3param.nsec_salt_len = salt_len;
        nsec3param.nsec_salt = salt_value;

        analyzer->EnqueueConnEvent(dns_NSEC3PARAM, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   msg->BuildNSEC3PARAM_Val(&nsec3param));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_DS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                 const u_char* msg_start) {
    if ( ! dns_DS || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 4 )
        return false;

    unsigned int ds_key_tag = ExtractShort(data, len);
    // split the two bytes for algorithm and digest type extraction
    uint32_t ds_algo_dtype = ExtractShort(data, len);
    unsigned int ds_algo = (ds_algo_dtype >> 8) & 0xff;
    unsigned int ds_dtype = ds_algo_dtype & 0xff;
    detail::DNSSEC_Digest ds_digest_type = detail::DNSSEC_Digest(ds_dtype);
    String* ds_digest = ExtractStream(data, len, rdlength - 4);

    switch ( ds_digest_type ) {
        case detail::SHA1: break;
        case detail::SHA256: break;
        case detail::GOST_R_34_11_94: break;
        case detail::SHA384: break;
        case detail::reserved: analyzer->Weird("DNSSEC_DS_ReservedDigestType", util::fmt("%d", ds_dtype)); break;
        default: analyzer->Weird("DNSSEC_DS_unknown_DigestType", util::fmt("%d", ds_dtype)); break;
    }

    if ( dns_DS ) {
        detail::DS_DATA ds;
        ds.key_tag = ds_key_tag;
        ds.algorithm = ds_algo;
        ds.digest_type = ds_dtype;
        ds.digest_val = ds_digest;

        analyzer->EnqueueConnEvent(dns_DS, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   msg->BuildDS_Val(&ds));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_BINDS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                    const u_char* msg_start) {
    if ( ! dns_BINDS || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 5 )
        return false;

    uint32_t algo_keyid_rflag = ExtractLong(data, len);

    unsigned int algo = (algo_keyid_rflag >> 24) & 0xff;
    unsigned int keyid1 = (algo_keyid_rflag >> 16) & 0xff;
    unsigned int keyid2 = (algo_keyid_rflag >> 8) & 0xff;
    unsigned int rmflag = algo_keyid_rflag & 0xff;

    unsigned int keyid = (keyid1 << 8) | keyid2;

    String* completeflag = ExtractStream(data, len, rdlength - 4);

    if ( dns_BINDS ) {
        detail::BINDS_DATA binds;
        binds.algorithm = algo;
        binds.key_id = keyid;
        binds.removal_flag = rmflag;
        binds.complete_flag = completeflag;

        analyzer->EnqueueConnEvent(dns_BINDS, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   msg->BuildBINDS_Val(&binds));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_SSHFP(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                    const u_char* msg_start) {
    if ( ! dns_SSHFP || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 2 )
        return false;

    uint32_t algo_fptype = ExtractShort(data, len);
    unsigned int algo = (algo_fptype >> 8) & 0xff;
    unsigned int fptype = algo_fptype & 0xff;

    String* fingerprint = ExtractStream(data, len, rdlength - 2);

    if ( dns_SSHFP ) {
        analyzer->EnqueueConnEvent(dns_SSHFP, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   val_mgr->Count(algo), val_mgr->Count(fptype),
                                   make_intrusive<StringVal>(fingerprint));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_LOC(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                  const u_char* msg_start) {
    if ( ! dns_LOC || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    if ( len < 15 )
        return false;

    // split the two bytes for version and size extraction
    uint32_t ver_size = ExtractShort(data, len);
    unsigned int version = (ver_size >> 8) & 0xff;
    unsigned int size = ver_size & 0xff;

    // split the two bytes for horizontal and vertical precision extraction
    uint32_t horiz_vert = ExtractShort(data, len);
    unsigned int horiz_pre = (horiz_vert >> 8) & 0xff;
    unsigned int vert_pre = horiz_vert & 0xff;

    uint32_t latitude = ExtractLong(data, len);
    uint32_t longitude = ExtractLong(data, len);
    uint32_t altitude = ExtractLong(data, len);

    if ( version != 0 ) {
        analyzer->Weird("DNS_LOC_version_unrecognized", util::fmt("%d", version));
    }

    if ( dns_LOC ) {
        detail::LOC_DATA loc;
        loc.version = version;
        loc.size = size;
        loc.horiz_pre = horiz_pre;
        loc.vert_pre = vert_pre;
        loc.latitude = latitude;
        loc.longitude = longitude;
        loc.altitude = altitude;

        analyzer->EnqueueConnEvent(dns_LOC, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   msg->BuildLOC_Val(&loc));
    }

    return true;
}

bool DNS_Interpreter::ParseRR_A(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
    if ( rdlength != 4 ) {
        analyzer->Weird("DNS_RR_bad_length");
        return false;
    }

    uint32_t addr = ExtractLong(data, len);

    if ( dns_A_reply && ! msg->skip_event )
        analyzer->EnqueueConnEvent(dns_A_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   make_intrusive<AddrVal>(htonl(addr)));

    return true;
}

bool DNS_Interpreter::ParseRR_AAAA(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
    uint32_t addr[4];

    for ( int i = 0; i < 4; ++i ) {
        addr[i] = htonl(ExtractLong(data, len));

        if ( len < 0 ) {
            if ( msg->atype == detail::TYPE_AAAA )
                analyzer->Weird("DNS_AAAA_neg_length");
            else
                analyzer->Weird("DNS_A6_neg_length");
            return false;
        }
    }

    EventHandlerPtr event;
    if ( msg->atype == detail::TYPE_AAAA )
        event = dns_AAAA_reply;
    else
        event = dns_A6_reply;

    if ( event && ! msg->skip_event )
        analyzer->EnqueueConnEvent(event, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   make_intrusive<AddrVal>(addr));

    return true;
}

bool DNS_Interpreter::ParseRR_WKS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
    if ( ! dns_WKS_reply || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    // TODO: Pass the ports as parameters to the event
    analyzer->EnqueueConnEvent(dns_WKS_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal());

    // TODO: Return a status which reflects if the port parameters were successfully parsed
    return true;
}

static StringValPtr extract_char_string(analyzer::Analyzer* analyzer, const u_char*& data, int& len, int& rdlen) {
    if ( rdlen <= 0 )
        return nullptr;

    uint8_t str_size = data[0];

    --rdlen;
    --len;
    ++data;

    if ( str_size > rdlen ) {
        analyzer->Weird("DNS_TXT_char_str_past_rdlen");
        return nullptr;
    }

    auto rval = make_intrusive<StringVal>(str_size, reinterpret_cast<const char*>(data));

    rdlen -= str_size;
    len -= str_size;
    data += str_size;

    return rval;
}

bool DNS_Interpreter::ParseRR_HINFO(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
    if ( ! dns_HINFO_reply || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    auto cpu = extract_char_string(analyzer, data, len, rdlength);
    auto os = extract_char_string(analyzer, data, len, rdlength);

    analyzer->EnqueueConnEvent(dns_HINFO_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(), cpu,
                               os);

    return rdlength == 0;
}

bool DNS_Interpreter::ParseRR_TXT(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                  const u_char* msg_start) {
    if ( ! dns_TXT_reply || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    auto char_strings = make_intrusive<VectorVal>(id::string_vec);
    StringValPtr char_string;

    while ( (char_string = extract_char_string(analyzer, data, len, rdlength)) )
        char_strings->Assign(char_strings->Size(), std::move(char_string));

    if ( dns_TXT_reply )
        analyzer->EnqueueConnEvent(dns_TXT_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   std::move(char_strings));

    return rdlength == 0;
}

bool DNS_Interpreter::ParseRR_SPF(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                  const u_char* msg_start) {
    if ( ! dns_SPF_reply || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    auto char_strings = make_intrusive<VectorVal>(id::string_vec);
    StringValPtr char_string;

    while ( (char_string = extract_char_string(analyzer, data, len, rdlength)) )
        char_strings->Assign(char_strings->Size(), std::move(char_string));

    if ( dns_SPF_reply )
        analyzer->EnqueueConnEvent(dns_SPF_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   std::move(char_strings));

    return rdlength == 0;
}

bool DNS_Interpreter::ParseRR_CAA(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                  const u_char* msg_start) {
    if ( ! dns_CAA_reply || msg->skip_event ) {
        data += rdlength;
        len -= rdlength;
        return true;
    }

    unsigned int flags = ExtractShort(data, len);
    unsigned int tagLen = flags & 0xff;
    flags = flags >> 8;
    rdlength -= 2;
    if ( (int)tagLen >= rdlength ) {
        analyzer->Weird("DNS_CAA_char_str_past_rdlen");
        return false;
    }
    String* tag = new String(data, tagLen, true);
    len -= tagLen;
    data += tagLen;
    rdlength -= tagLen;
    String* value = new String(data, rdlength, false);

    len -= value->Len();
    data += value->Len();
    rdlength -= value->Len();

    if ( dns_CAA_reply )
        analyzer->EnqueueConnEvent(dns_CAA_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                   val_mgr->Count(flags), make_intrusive<StringVal>(tag),
                                   make_intrusive<StringVal>(value));
    else {
        delete tag;
        delete value;
    }

    return rdlength == 0;
}

bool DNS_Interpreter::ParseRR_SVCB(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
                                   const u_char* msg_start, const RR_Type& svcb_type) {
    const u_char* data_start = data;
    // the smallest SVCB/HTTPS rr is 3 bytes:
    // the first 2 bytes are for the svc priority, and the third byte is root (0x0)
    if ( len < 3 ) {
        analyzer->Weird("DNS_SVBC_wrong_length");
        return false;
    }

    uint16_t svc_priority = ExtractShort(data, len);

    u_char target_name[513];
    int name_len = sizeof(target_name) - 1;
    u_char* name_end = ExtractName(data, len, target_name, name_len, msg_start, false);
    if ( ! name_end )
        return false;

    // target name can be root - in this case the alternative endpoint is
    // qname itself. make sure that we print "." instead of an empty string
    if ( name_end - target_name == 0 ) {
        target_name[0] = '.';
        target_name[1] = '\0';
        name_end = target_name + 1;
    }

    SVCB_DATA svcb_data = {svc_priority,
                           make_intrusive<StringVal>(new String(target_name, name_end - target_name, true))};

    // TODO: parse svcparams
    // we consume all the remaining raw data (svc params) but do nothing.
    // this should be removed if the svc param parser is ready
    std::ptrdiff_t parsed_bytes = data - data_start;
    if ( parsed_bytes < rdlength ) {
        len -= (rdlength - parsed_bytes);
        data += (rdlength - parsed_bytes);
    }

    switch ( svcb_type ) {
        case detail::TYPE_SVCB:
            analyzer->EnqueueConnEvent(dns_SVCB, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                       msg->BuildSVCB_Val(svcb_data));
            break;
        case detail::TYPE_HTTPS:
            analyzer->EnqueueConnEvent(dns_HTTPS, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
                                       msg->BuildSVCB_Val(svcb_data));
            break;
        default: break; // unreachable. for suppressing compiler warnings.
    }
    return true;
}

void DNS_Interpreter::SendReplyOrRejectEvent(detail::DNS_MsgInfo* msg, EventHandlerPtr event, const u_char*& data,
                                             int& len, String* question_name, String* original_name) {
    detail::RR_Type qtype = detail::RR_Type(ExtractShort(data, len));
    int qclass = ExtractShort(data, len);

    assert(event);

    analyzer->EnqueueConnEvent(event, analyzer->ConnVal(), msg->BuildHdrVal(), make_intrusive<StringVal>(question_name),
                               val_mgr->Count(qtype), val_mgr->Count(qclass), make_intrusive<StringVal>(original_name));
}

DNS_MsgInfo::DNS_MsgInfo(DNS_RawMsgHdr* hdr, int arg_is_query) {
    // ### Need to fix alignment if hdr is misaligned (not on a short
    //  boundary).
    unsigned short flags = ntohs(hdr->flags);

    QR = (flags & 0x8000) != 0;
    opcode = (flags & 0x7800) >> 11;
    AA = (flags & 0x0400) != 0;
    TC = (flags & 0x0200) != 0;
    RD = (flags & 0x0100) != 0;
    RA = (flags & 0x0080) != 0;
    Z = (flags & 0x0070) >> 4;
    AD = (flags & 0x0020) >> 5;
    CD = (flags & 0x0010) >> 4;
    rcode = (flags & 0x000f);

    qdcount = ntohs(hdr->qdcount);
    ancount = ntohs(hdr->ancount);
    nscount = ntohs(hdr->nscount);
    arcount = ntohs(hdr->arcount);

    id = ntohs(hdr->id);
    is_query = arg_is_query;

    atype = detail::TYPE_ALL;
    aclass = 0;
    ttl = 0;

    answer_type = DNS_QUESTION;
    skip_event = 0;
}

RecordValPtr DNS_MsgInfo::BuildHdrVal() {
    static auto dns_msg = id::find_type<RecordType>("dns_msg");
    auto r = make_intrusive<RecordVal>(dns_msg);

    r->Assign(0, id);
    r->Assign(1, opcode);
    r->Assign(2, rcode);
    r->Assign(3, static_cast<bool>(QR));
    r->Assign(4, static_cast<bool>(AA));
    r->Assign(5, static_cast<bool>(TC));
    r->Assign(6, static_cast<bool>(RD));
    r->Assign(7, static_cast<bool>(RA));
    r->Assign(8, Z);
    r->Assign(9, static_cast<bool>(AD));
    r->Assign(10, static_cast<bool>(CD));
    r->Assign(11, qdcount);
    r->Assign(12, ancount);
    r->Assign(13, nscount);
    r->Assign(14, arcount);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildAnswerVal() {
    static auto dns_answer = id::find_type<RecordType>("dns_answer");
    auto r = make_intrusive<RecordVal>(dns_answer);

    r->Assign(0, answer_type);
    r->Assign(1, query_name);
    r->Assign(2, atype);
    r->Assign(3, aclass);
    r->AssignInterval(4, double(ttl));

    return r;
}

RecordValPtr DNS_MsgInfo::BuildEDNS_Val() {
    // We have to treat the additional record type in EDNS differently
    // than a regular resource record.
    static auto dns_edns_additional = id::find_type<RecordType>("dns_edns_additional");
    auto r = make_intrusive<RecordVal>(dns_edns_additional);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);

    // type = 0x29 or 41 = EDNS
    r->Assign(2, atype);

    // sender's UDP payload size, per RFC 2671 4.3
    r->Assign(3, aclass);

    // Need to break the TTL field into three components:
    // initial: [------------- ttl (32) ---------------------]
    // after:   [ ext rcode (8)][ver # (8)][   Z field (16)  ]

    unsigned int ercode = (ttl >> 24) & 0xff;
    unsigned int version = (ttl >> 16) & 0xff;
    // unsigned int DO = ttl & 0x8000;	// "DNSSEC OK" - RFC 3225
    unsigned int z = ttl & 0xffff;

    unsigned int return_error = (ercode << 8) | rcode;

    r->Assign(4, return_error);
    r->Assign(5, version);
    r->Assign(6, z);
    r->AssignInterval(7, double(ttl));
    r->Assign(8, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildEDNS_ECS_Val(struct EDNS_ECS* opt) {
    static auto dns_edns_ecs = id::find_type<RecordType>("dns_edns_ecs");
    auto r = make_intrusive<RecordVal>(dns_edns_ecs);

    r->Assign(0, opt->ecs_family);
    r->Assign(1, opt->ecs_src_pfx_len);
    r->Assign(2, opt->ecs_scp_pfx_len);
    r->Assign(3, opt->ecs_addr);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildEDNS_TCP_KA_Val(struct EDNS_TCP_KEEPALIVE* opt) {
    static auto dns_edns_tcp_keepalive = id::find_type<RecordType>("dns_edns_tcp_keepalive");
    auto r = make_intrusive<RecordVal>(dns_edns_tcp_keepalive);

    r->Assign(0, opt->keepalive_timeout_omitted);
    r->Assign(1, opt->keepalive_timeout);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildEDNS_COOKIE_Val(struct EDNS_COOKIE* opt) {
    static auto dns_edns_cookie = id::find_type<RecordType>("dns_edns_cookie");
    auto r = make_intrusive<RecordVal>(dns_edns_cookie);

    r->Assign(0, opt->client_cookie);
    if ( opt->server_cookie != nullptr ) {
        r->Assign(1, opt->server_cookie);
    }

    return r;
}

RecordValPtr DNS_MsgInfo::BuildTKEY_Val(struct TKEY_DATA* tkey) {
    static auto dns_tkey = id::find_type<RecordType>("dns_tkey");
    auto r = make_intrusive<RecordVal>(dns_tkey);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, tkey->alg_name);
    r->AssignTime(3, static_cast<double>(tkey->inception));
    r->AssignTime(4, static_cast<double>(tkey->expiration));
    r->Assign(5, static_cast<uint16_t>(tkey->mode));
    r->Assign(6, static_cast<uint16_t>(tkey->error));
    r->Assign(7, tkey->key);
    r->Assign(8, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildTSIG_Val(struct TSIG_DATA* tsig) {
    static auto dns_tsig_additional = id::find_type<RecordType>("dns_tsig_additional");
    auto r = make_intrusive<RecordVal>(dns_tsig_additional);
    double rtime = tsig->time_s + tsig->time_ms / 1000.0;

    // r->Assign(0, answer_type);
    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, tsig->alg_name);
    r->Assign(3, tsig->sig);
    r->AssignTime(4, rtime);
    r->AssignTime(5, double(tsig->fudge));
    r->Assign(6, tsig->orig_id);
    r->Assign(7, tsig->rr_error);
    r->Assign(8, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildRRSIG_Val(RRSIG_DATA* rrsig) {
    static auto dns_rrsig_rr = id::find_type<RecordType>("dns_rrsig_rr");
    auto r = make_intrusive<RecordVal>(dns_rrsig_rr);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, rrsig->type_covered);
    r->Assign(3, rrsig->algorithm);
    r->Assign(4, rrsig->labels);
    r->AssignInterval(5, double(rrsig->orig_ttl));
    r->AssignTime(6, double(rrsig->sig_exp));
    r->AssignTime(7, double(rrsig->sig_incep));
    r->Assign(8, rrsig->key_tag);
    r->Assign(9, rrsig->signer_name);
    r->Assign(10, rrsig->signature);
    r->Assign(11, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildDNSKEY_Val(DNSKEY_DATA* dnskey) {
    static auto dns_dnskey_rr = id::find_type<RecordType>("dns_dnskey_rr");
    auto r = make_intrusive<RecordVal>(dns_dnskey_rr);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, dnskey->dflags);
    r->Assign(3, dnskey->dprotocol);
    r->Assign(4, dnskey->dalgorithm);
    r->Assign(5, dnskey->public_key);
    r->Assign(6, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildNSEC3_Val(NSEC3_DATA* nsec3) {
    static auto dns_nsec3_rr = id::find_type<RecordType>("dns_nsec3_rr");
    auto r = make_intrusive<RecordVal>(dns_nsec3_rr);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, nsec3->nsec_flags);
    r->Assign(3, nsec3->nsec_hash_algo);
    r->Assign(4, nsec3->nsec_iter);
    r->Assign(5, nsec3->nsec_salt_len);
    r->Assign(6, nsec3->nsec_salt);
    r->Assign(7, nsec3->nsec_hlen);
    r->Assign(8, nsec3->nsec_hash);
    r->Assign(9, std::move(nsec3->bitmaps));
    r->Assign(10, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildNSEC3PARAM_Val(NSEC3PARAM_DATA* nsec3param) {
    static auto dns_nsec3param_rr = id::find_type<RecordType>("dns_nsec3param_rr");
    auto r = make_intrusive<RecordVal>(dns_nsec3param_rr);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, nsec3param->nsec_flags);
    r->Assign(3, nsec3param->nsec_hash_algo);
    r->Assign(4, nsec3param->nsec_iter);
    r->Assign(5, nsec3param->nsec_salt_len);
    r->Assign(6, nsec3param->nsec_salt);
    r->Assign(7, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildDS_Val(DS_DATA* ds) {
    static auto dns_ds_rr = id::find_type<RecordType>("dns_ds_rr");
    auto r = make_intrusive<RecordVal>(dns_ds_rr);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, ds->key_tag);
    r->Assign(3, ds->algorithm);
    r->Assign(4, ds->digest_type);
    r->Assign(5, ds->digest_val);
    r->Assign(6, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildBINDS_Val(BINDS_DATA* binds) {
    static auto dns_binds_rr = id::find_type<RecordType>("dns_binds_rr");
    auto r = make_intrusive<RecordVal>(dns_binds_rr);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, binds->algorithm);
    r->Assign(3, binds->key_id);
    r->Assign(4, binds->removal_flag);
    r->Assign(5, binds->complete_flag);
    r->Assign(6, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildLOC_Val(LOC_DATA* loc) {
    static auto dns_loc_rr = id::find_type<RecordType>("dns_loc_rr");
    auto r = make_intrusive<RecordVal>(dns_loc_rr);

    r->Assign(0, query_name);
    r->Assign(1, answer_type);
    r->Assign(2, loc->version);
    r->Assign(3, loc->size);
    r->Assign(4, loc->horiz_pre);
    r->Assign(5, loc->vert_pre);
    r->Assign(6, static_cast<uint64_t>(loc->latitude));
    r->Assign(7, static_cast<uint64_t>(loc->longitude));
    r->Assign(8, static_cast<uint64_t>(loc->altitude));
    r->Assign(9, is_query);

    return r;
}

RecordValPtr DNS_MsgInfo::BuildSVCB_Val(const SVCB_DATA& svcb) {
    static auto dns_svcb_rr = id::find_type<RecordType>("dns_svcb_rr");
    auto r = make_intrusive<RecordVal>(dns_svcb_rr);

    r->Assign(0, svcb.svc_priority);
    r->Assign(1, svcb.target_name);

    // TODO: assign values to svcparams
    return r;
}

} // namespace detail

Contents_DNS::Contents_DNS(Connection* conn, bool orig, detail::DNS_Interpreter* arg_interp)
    : analyzer::tcp::TCP_SupportAnalyzer("CONTENTS_DNS", conn, orig) {
    interp = arg_interp;

    msg_buf = nullptr;
    buf_n = buf_len = msg_size = 0;
    state = detail::DNS_LEN_HI;
}

Contents_DNS::~Contents_DNS() { free(msg_buf); }

void Contents_DNS::Flush() {
    if ( buf_n > 0 ) { // Deliver partial message.
        // '2' here means whether it's a query is unknown.
        interp->ParseMessage(msg_buf, buf_n, 2);
        msg_size = 0;
    }
}

void Contents_DNS::DeliverStream(int len, const u_char* data, bool orig) {
    while ( len > 0 )
        ProcessChunk(len, data, orig);
}

void Contents_DNS::ProcessChunk(int& len, const u_char*& data, bool orig) {
    if ( state == detail::DNS_LEN_HI ) {
        msg_size = (*data) << 8;
        state = detail::DNS_LEN_LO;

        ++data;
        --len;

        if ( len == 0 )
            return;
    }

    if ( state == detail::DNS_LEN_LO ) {
        msg_size += *data;
        state = detail::DNS_MESSAGE_BUFFER;

        buf_n = 0;

        if ( msg_buf ) {
            if ( buf_len < msg_size ) {
                buf_len = msg_size;
                msg_buf = (u_char*)util::safe_realloc((void*)msg_buf, buf_len);
            }
        }
        else {
            buf_len = msg_size;
            msg_buf = (u_char*)util::safe_malloc(buf_len);
        }

        ++data;
        --len;

        if ( len == 0 )
            return;
    }

    if ( state != detail::DNS_MESSAGE_BUFFER )
        Conn()->Internal("state inconsistency in Contents_DNS::DeliverStream");

    int n;
    for ( n = 0; buf_n < msg_size && n < len; ++n )
        msg_buf[buf_n++] = data[n];

    data += n;
    len -= n;

    if ( buf_n < msg_size )
        // Haven't filled up the message buffer yet, no more to do.
        return;

    ForwardPacket(msg_size, msg_buf, orig, -1, nullptr, 0);

    buf_n = 0;
    state = detail::DNS_LEN_HI;
}

DNS_Analyzer::DNS_Analyzer(Connection* conn) : analyzer::tcp::TCP_ApplicationAnalyzer("DNS", conn) {
    interp = new detail::DNS_Interpreter(this);
    contents_dns_orig = contents_dns_resp = nullptr;

    if ( Conn()->ConnTransport() == TRANSPORT_TCP ) {
        contents_dns_orig = new Contents_DNS(conn, true, interp);
        contents_dns_resp = new Contents_DNS(conn, false, interp);
        AddSupportAnalyzer(contents_dns_orig);
        AddSupportAnalyzer(contents_dns_resp);
    }
    else {
        ADD_ANALYZER_TIMER(&DNS_Analyzer::ExpireTimer, run_state::network_time + zeek::detail::dns_session_timeout,
                           true, zeek::detail::TIMER_DNS_EXPIRE);
    }
}

DNS_Analyzer::~DNS_Analyzer() { delete interp; }

void DNS_Analyzer::Init() {}

void DNS_Analyzer::Done() {
    analyzer::tcp::TCP_ApplicationAnalyzer::Done();

    if ( Conn()->ConnTransport() == TRANSPORT_UDP )
        Event(udp_session_done);
    else
        interp->Timeout();
}

void DNS_Analyzer::DeliverPacket(int len, const u_char* data, bool orig, uint64_t seq, const IP_Hdr* ip, int caplen) {
    analyzer::tcp::TCP_ApplicationAnalyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
    interp->ParseMessage(data, len, orig ? 1 : 0);
}

void DNS_Analyzer::ConnectionClosed(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer,
                                    bool gen_event) {
    analyzer::tcp::TCP_ApplicationAnalyzer::ConnectionClosed(endpoint, peer, gen_event);

    assert(contents_dns_orig && contents_dns_resp);
    contents_dns_orig->Flush();
    contents_dns_resp->Flush();
}

void DNS_Analyzer::ExpireTimer(double t) {
    // The - 1.0 in the following is to allow 1 second for the
    // common case of a single request followed by a single reply,
    // so we don't needlessly set the timer twice in that case.
    if ( t - Conn()->LastTime() >= zeek::detail::dns_session_timeout - 1.0 || run_state::terminating ) {
        Event(connection_timeout);
        session_mgr->Remove(Conn());
    }
    else
        ADD_ANALYZER_TIMER(&DNS_Analyzer::ExpireTimer, t + zeek::detail::dns_session_timeout, true,
                           zeek::detail::TIMER_DNS_EXPIRE);
}

} // namespace zeek::analyzer::dns
