]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4705: extractor: extend dns support
authorAdrian Mamolea (admamole) <admamole@cisco.com>
Mon, 28 Apr 2025 18:39:39 +0000 (18:39 +0000)
committerMaya Dagon (mdagon) <mdagon@cisco.com>
Mon, 28 Apr 2025 18:39:39 +0000 (18:39 +0000)
Merge in SNORT/snort3 from ~ADMAMOLE/snort3:dns3 to master

Squashed commit of the following:

commit a66400442cc0567df4607d23f5a070e670b6d76a
Author: Adrian Mamolea <admamole@cisco.com>
Date:   Tue Apr 15 13:55:28 2025 -0400

    extractor: extend dns support

doc/user/extractor.txt
src/pub_sub/dns_events.cc
src/service_inspectors/dns/dns.cc
src/service_inspectors/dns/dns.h
src/service_inspectors/dns/dns_rr_decoder.cc

index 08f6326a2f6897ea58afec72054a1359ad6b302f..8bb10c2b53e31a9001b207d04d113a197cdde01c 100644 (file)
@@ -130,8 +130,13 @@ Fields supported for DNS:
 * `auth` - The list of authoritative responses
 * `addl` - The list of additional responses
 
-In the answers, auth, and addl lists the decoding of the following RR types is supported:
-A, AAAA, CNAME, DS, MX, NS, NSEC, PTR, RRSIG, SOA, TXT
+The answers, auth, and addl lists contain all the RRs found in the corresponding message sections. Each RR is
+represented by a summary of its decoding. For these RR types the decoding contains type specific information
+(ip addresses, domain names, etc.): A, AAAA, BIND9 signing, CNAME, DNSKEY, DS, LOC, MX, NS, NSEC, OPT, PTR, RRSIG,
+SOA, SPF, SRV, SSHFP, TXT. For these RR types: CAA, HINFO, HTTPS, NSEC3, NSEC3PARAM, SVCB, TKEY, TSIG, the decoding
+contains only the name of the RR type. This is also the default decoding applied to all RR types that don't have
+a type specific decoder. When the name of the type is not known it is decoded as UNKNOWN-N, where N is RR type
+numeric value.
 
 Fields supported for connection:
 
index 85990b59c847a5e0a30dc7383e3e019b1d7c5e05..47e8c4f5b8d0c7585616f8f844214cd341a64df8 100644 (file)
@@ -50,112 +50,9 @@ static const std::string& class_name(uint16_t query_class)
     return it != class_names.end() ? it->second : unknown;
 }
 
-static const std::string& qtype_name(uint16_t query_type)
-{
-    static const std::map<uint16_t, std::string> qtype_names =
-    {
-        {1,     "A"},          // RFC 1035
-        {2,     "NS"},         // RFC 1035
-        {3,     "MD"},         // RFC 1035
-        {4,     "MF"},         // RFC 1035
-        {5,     "CNAME"},      // RFC 1035
-        {6,     "SOA"},        // RFC 1035
-        {7,     "MB"},         // RFC 1035
-        {8,     "MG"},         // RFC 1035
-        {9,     "MR"},         // RFC 1035
-        {10,    "NULL"},       // RFC 1035
-        {11,    "WKS"},        // RFC 1035
-        {12,    "PTR"},        // RFC 1035
-        {13,    "HINFO"},      // RFC 1035
-        {14,    "MINFO"},      // RFC 1035
-        {15,    "MX"},         // RFC 1035
-        {16,    "TXT"},        // RFC 1035
-        {17,    "RP"},         // RFC 1183
-        {18,    "AFSDB"},      // RFC 1183
-        {19,    "X25"},        // RFC 1183
-        {20,    "ISDN"},       // RFC 1183
-        {21,    "RT"},         // RFC 1183
-        {22,    "NSAP"},       // RFC 1706
-        {23,    "NSAP_PTR"},   // RFC 1348
-        {24,    "SIG"},        // RFC 2536
-        {25,    "KEY"},        // RFC 2536
-        {26,    "PX"},         // RFC 2163
-        {27,    "GPOS"},       // RFC 1712
-        {28,    "AAAA"},       // RFC 3596
-        {29,    "LOC"},        // RFC 1876
-        {30,    "NXT"},        // RFC 2535
-        {31,    "EID"},
-        {32,    "NIMLOC"},
-        {33,    "SRV"},        // RFC 2782
-        {34,    "ATMA"},
-        {35,    "NAPTR"},      // RFC 3403
-        {36,    "KX"},         // RFC 2230
-        {37,    "CERT"},       // RFC 4398
-        {38,    "A6"},         // RFC 2874
-        {39,    "DNAME"},      // RFC 6672
-        {40,    "SINK"},
-        {41,    "OPT"},        // RFC 6891
-        {42,    "APL"},        // RFC 3123
-        {43,    "DS"},         // RFC 4034
-        {44,    "SSHFP"},      // RFC 4255
-        {45,    "IPSECKEY"},   // RFC 4025
-        {46,    "RRSIG"},      // RFC 4034
-        {47,    "NSEC"},       // RFC 4034
-        {48,    "DNSKEY"},     // RFC 4034
-        {49,    "DHCID"},      // RFC 4701
-        {50,    "NSEC3"},      // RFC 5155
-        {51,    "NSEC3PARAM"}, // RFC 5155
-        {52,    "TLSA"},       // RFC 6698
-        {53,    "SMIMEA"},     // RFC 8162
-        {55,    "HIP"},        // RFC 8005
-        {56,    "NINFO"},
-        {57,    "RKEY"},
-        {58,    "TALINK"},
-        {59,    "CDS"},        // RFC 7344
-        {60,    "CDNSKEY"},    // RFC 7344
-        {61,    "OPENPGPKEY"}, // RFC 7929
-        {62,    "CSYNC"},      // RFC 7477
-        {63,    "ZONEMD"},     // RFC 8976
-        {64,    "SVCB"},       // RFC 9460
-        {65,    "HTTPS"},      // RFC 9460
-        {66,    "DSYNC"},
-        {99,    "SPF"},        // RFC 7208
-        {100,   "UINFO"},
-        {101,   "UID"},
-        {102,   "GID"},
-        {103,   "UNSPEC"},
-        {104,   "NID"},        // RFC 6742
-        {105,   "L32"},        // RFC 6742
-        {106,   "L64"},        // RFC 6742
-        {107,   "LP"},         // RFC 6742
-        {108,   "EUI48"},      // RFC 7043
-        {109,   "EUI64"},      // RFC 7043
-        {249,   "TKEY"},       // RFC 2930
-        {250,   "TSIG"},       // RFC 8945
-        {251,   "IXFR"},       // RFC 1995
-        {252,   "AXFR"},       // RFC 1035
-        {253,   "MAILB"},      // RFC 1035
-        {254,   "MAILA"},      // RFC 1035
-        {255,   "*"},          // RFC 1035, also known as ANY
-        {256,   "URI"},        // RFC 7553
-        {257,   "CAA"},        // RFC 8659
-        {32768, "TA"},
-        {32769, "DLV"},        // RFC 4431
-        {65281, "WINS"},       // Microsoft
-        {65282, "WINS-R"},     // Microsoft
-        {65521, "INTEGRITY"}   // Chromium Design Doc: Querying HTTPSSVC
-        // Add more QTYPEs as needed
-    };
-
-    static const std::string unknown = "UNKNOWN";
-
-    auto it = qtype_names.find(query_type);
-    return it != qtype_names.end() ? it->second : unknown;
-}
-
 static const std::string& rcode_name(uint16_t rcode)
 {
-    static const std::map<uint8_t, std::string> rcode_names =
+    static const std::map<uint16_t, std::string> rcode_names =
     {
         {0,  "NOERROR"},   // RFC 1035
         {1,  "FORMERR"},   // RFC 1035
@@ -272,7 +169,7 @@ const std::string& DnsResponseEvent::get_query_type_name() const
     static const std::string empty = "";
     if (session.hdr.questions == 0)
         return empty;
-    return qtype_name(get_query_type());
+    return DNSData::qtype_name(get_query_type());
 }
 
 uint8_t DnsResponseEvent::get_rcode() const
@@ -332,9 +229,14 @@ const std::string& DnsResponseEvent::get_TTLs() const
 
 bool DnsResponseEvent::get_rejected() const
 {
-    return session.hdr.flags & DNS_HDR_FLAG_RESPONSE &&
-        session.hdr.flags & DNS_HDR_FLAG_REPLY_CODE_MASK &&
-        !session.hdr.questions;
+    if (session.hdr.flags & DNS_HDR_FLAG_RESPONSE)
+    {
+        if (session.hdr.flags & DNS_HDR_FLAG_REPLY_CODE_MASK && !session.hdr.questions)
+            return true;
+        if (session.hdr.answers == 0 && session.hdr.authorities == 0 && session.hdr.additionals == 0)
+            return true;
+    }
+    return false;
 }
 
 const std::string& DnsResponseEvent::get_auth() const
index a4fa54ce6264ec40b87e9dcb5455e560125523d2..8e92d47e84a14afd3de64257c166a7cdc2d58bcf 100644 (file)
@@ -794,23 +794,11 @@ static uint16_t ParseDNSRData(
         if (dnsSessionData->publish_response())
             dnsSessionData->dns_events.add_fqdn(dnsSessionData->cur_fqdn_event, dnsSessionData->curr_rr.ttl);
 
-        bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData);
-        break;
-    case DNS_RR_TYPE_NS:
-    case DNS_RR_TYPE_SOA:
-    case DNS_RR_TYPE_WKS:
-    case DNS_RR_TYPE_PTR:
-    case DNS_RR_TYPE_HINFO:
-    case DNS_RR_TYPE_MX:
-    case DNS_RR_TYPE_RRSIG:
-    case DNS_RR_TYPE_NSEC:
-    case DNS_RR_TYPE_DS:
         bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData);
         break;
     default:
-        /* Not one of the known types.  Stop looking at this session
-         * as DNS. */
-        dnsSessionData->flags |= DNS_FLAG_NOT_DNS;
+        /* An unknown RR type or one w/o special handling, skip */
+        bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData);
         break;
     }
 
@@ -900,6 +888,7 @@ static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& ne
         switch (dnsSessionData->state)
         {
         case DNS_RESP_STATE_ANS_RR: /* ANSWERS section */
+            dnsSessionData->answer_tabs.emplace_back(data - p->data);
             for (i=dnsSessionData->curr_rec; i<dnsSessionData->hdr.answers; i++)
             {
                 bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData, p, dnsSessionData->answer_tabs);
@@ -945,6 +934,7 @@ static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& ne
             dnsSessionData->curr_rec = 0;
         /* Fall through */
         case DNS_RESP_STATE_AUTH_RR: /* AUTHORITIES section */
+            dnsSessionData->auth_tabs.emplace_back(data - p->data);
             for (i=dnsSessionData->curr_rec; i<dnsSessionData->hdr.authorities; i++)
             {
                 bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData, p, dnsSessionData->auth_tabs);
@@ -990,6 +980,7 @@ static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& ne
             dnsSessionData->curr_rec = 0;
         /* Fall through */
         case DNS_RESP_STATE_ADD_RR: /* ADDITIONALS section */
+            dnsSessionData->addl_tabs.emplace_back(data - p->data);
             for (i=dnsSessionData->curr_rec; i<dnsSessionData->hdr.additionals; i++)
             {
                 bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData, p, dnsSessionData->addl_tabs);
index 07de79843d5fc67a95c26f50d6a76fc393fdb0a9..5a954607fb990ce02ec1d56c87a5b90183a7f9c8 100644 (file)
@@ -94,7 +94,6 @@ struct DNSNameState
     }
 };
 
-// FIXIT-L  remove obsolete flags?
 #define DNS_RR_TYPE_A                       0x0001
 #define DNS_RR_TYPE_NS                      0x0002
 #define DNS_RR_TYPE_MD                      0x0003 // obsolete
@@ -112,9 +111,23 @@ struct DNSNameState
 #define DNS_RR_TYPE_MX                      0x000f
 #define DNS_RR_TYPE_TXT                     0x0010
 #define DNS_RR_TYPE_AAAA                    0x001c
+#define DNS_RR_TYPE_LOC                     0x001d
+#define DNS_RR_TYPE_SRV                     0x0021
+#define DNS_RR_TYPE_OPT                     0x0029
 #define DNS_RR_TYPE_RRSIG                   0x002e
 #define DNS_RR_TYPE_NSEC                    0x002f
 #define DNS_RR_TYPE_DS                      0x002b
+#define DNS_RR_TYPE_SSHFP                   0x002c
+#define DNS_RR_TYPE_DNSKEY                  0x0030
+#define DNS_RR_TYPE_NSEC3                   0x0032
+#define DNS_RR_TYPE_NSEC3PARAM              0x0033
+#define DNS_RR_TYPE_SVCB                    0x0040
+#define DNS_RR_TYPE_HTTPS                   0x0041
+#define DNS_RR_TYPE_SPF                     0x0063
+#define DNS_RR_TYPE_TKEY                    0x00f9
+#define DNS_RR_TYPE_TSIG                    0x00fa
+#define DNS_RR_TYPE_CAA                     0x0101
+#define DNS_RR_TYPE_BIND9_SIGNING           0xfffe
 
 #define DNS_RR_PTR 0xC0
 
@@ -221,7 +234,7 @@ struct DNSData
     bool has_events() const;
     bool valid_dns(const DNSHdr&) const;
 
-    void decode_rdata(const snort::Packet* p, const uint8_t* rdata, uint16_t rdlength,
+    void decode_rdata(const snort::Packet* p, const uint8_t* rr, const uint8_t* rdata, uint16_t rdlength,
         uint16_t type, std::string& rdata_str) const;
     void get_rr_data(const snort::Packet *p, const std::vector<uint16_t>& tabs,
         std::string& rrs, std::string* ttls = nullptr) const;
@@ -229,6 +242,8 @@ struct DNSData
     { get_rr_data(p, answer_tabs, answers, &ttls); }
     void get_auth(const snort::Packet *p, std::string& auth) const { get_rr_data(p, auth_tabs, auth); }
     void get_addl(const snort::Packet *p, std::string& addl) const { get_rr_data(p, addl_tabs, addl); }
+
+    static const std::string& qtype_name(uint16_t query_type, bool* is_unknown = nullptr);
 };
 
 DNSData* get_dns_session_data(snort::Packet* p, bool from_server, DNSData& udpSessionData);
index daaa2bc0414d7ed648a34e75dc73728a2834c750..1d029206ec58d188e854c9140797cc2619b65fca 100644 (file)
 
 #include "dns.h"
 
+#include <iomanip>
+#include <sstream>
 #include <string>
 #include "sfip/sf_ip.h"
 
 using namespace snort;
 
+const std::string& DNSData::qtype_name(uint16_t query_type, bool* is_unknown)
+{
+    static const std::map<uint16_t, std::string> qtype_names =
+    {
+        {1,     "A"},          // RFC 1035
+        {2,     "NS"},         // RFC 1035
+        {3,     "MD"},         // RFC 1035
+        {4,     "MF"},         // RFC 1035
+        {5,     "CNAME"},      // RFC 1035
+        {6,     "SOA"},        // RFC 1035
+        {7,     "MB"},         // RFC 1035
+        {8,     "MG"},         // RFC 1035
+        {9,     "MR"},         // RFC 1035
+        {10,    "NULL"},       // RFC 1035
+        {11,    "WKS"},        // RFC 1035
+        {12,    "PTR"},        // RFC 1035
+        {13,    "HINFO"},      // RFC 1035
+        {14,    "MINFO"},      // RFC 1035
+        {15,    "MX"},         // RFC 1035
+        {16,    "TXT"},        // RFC 1035
+        {17,    "RP"},         // RFC 1183
+        {18,    "AFSDB"},      // RFC 1183
+        {19,    "X25"},        // RFC 1183
+        {20,    "ISDN"},       // RFC 1183
+        {21,    "RT"},         // RFC 1183
+        {22,    "NSAP"},       // RFC 1706
+        {23,    "NSAP_PTR"},   // RFC 1348
+        {24,    "SIG"},        // RFC 2536
+        {25,    "KEY"},        // RFC 2536
+        {26,    "PX"},         // RFC 2163
+        {27,    "GPOS"},       // RFC 1712
+        {28,    "AAAA"},       // RFC 3596
+        {29,    "LOC"},        // RFC 1876
+        {30,    "NXT"},        // RFC 2535
+        {31,    "EID"},
+        {32,    "NIMLOC"},
+        {33,    "SRV"},        // RFC 2782
+        {34,    "ATMA"},
+        {35,    "NAPTR"},      // RFC 3403
+        {36,    "KX"},         // RFC 2230
+        {37,    "CERT"},       // RFC 4398
+        {38,    "A6"},         // RFC 2874
+        {39,    "DNAME"},      // RFC 6672
+        {40,    "SINK"},
+        {41,    "OPT"},        // RFC 6891
+        {42,    "APL"},        // RFC 3123
+        {43,    "DS"},         // RFC 4034
+        {44,    "SSHFP"},      // RFC 4255
+        {45,    "IPSECKEY"},   // RFC 4025
+        {46,    "RRSIG"},      // RFC 4034
+        {47,    "NSEC"},       // RFC 4034
+        {48,    "DNSKEY"},     // RFC 4034
+        {49,    "DHCID"},      // RFC 4701
+        {50,    "NSEC3"},      // RFC 5155
+        {51,    "NSEC3PARAM"}, // RFC 5155
+        {52,    "TLSA"},       // RFC 6698
+        {53,    "SMIMEA"},     // RFC 8162
+        {55,    "HIP"},        // RFC 8005
+        {56,    "NINFO"},
+        {57,    "RKEY"},
+        {58,    "TALINK"},
+        {59,    "CDS"},        // RFC 7344
+        {60,    "CDNSKEY"},    // RFC 7344
+        {61,    "OPENPGPKEY"}, // RFC 7929
+        {62,    "CSYNC"},      // RFC 7477
+        {63,    "ZONEMD"},     // RFC 8976
+        {64,    "SVCB"},       // RFC 9460
+        {65,    "HTTPS"},      // RFC 9460
+        {66,    "DSYNC"},
+        {99,    "SPF"},        // RFC 7208
+        {100,   "UINFO"},
+        {101,   "UID"},
+        {102,   "GID"},
+        {103,   "UNSPEC"},
+        {104,   "NID"},        // RFC 6742
+        {105,   "L32"},        // RFC 6742
+        {106,   "L64"},        // RFC 6742
+        {107,   "LP"},         // RFC 6742
+        {108,   "EUI48"},      // RFC 7043
+        {109,   "EUI64"},      // RFC 7043
+        {249,   "TKEY"},       // RFC 2930
+        {250,   "TSIG"},       // RFC 8945
+        {251,   "IXFR"},       // RFC 1995
+        {252,   "AXFR"},       // RFC 1035
+        {253,   "MAILB"},      // RFC 1035
+        {254,   "MAILA"},      // RFC 1035
+        {255,   "*"},          // RFC 1035, also known as ANY
+        {256,   "URI"},        // RFC 7553
+        {257,   "CAA"},        // RFC 8659
+        {32768, "TA"},
+        {32769, "DLV"},        // RFC 4431
+        {65281, "WINS"},       // Microsoft
+        {65282, "WINS-R"},     // Microsoft
+        {65521, "INTEGRITY"},  // Chromium Design Doc: Querying HTTPSSVC
+        {65534, "BIND9S"}      // BIND9 signing signal
+        // Add more QTYPEs as needed
+    };
+
+    static const std::string unknown = "UNKNOWN";
+
+    auto it = qtype_names.find(query_type);
+    if (it == qtype_names.end())
+    {
+        if (is_unknown)
+            *is_unknown = true;
+        return unknown;
+    }
+
+    if (is_unknown)
+        *is_unknown = false;
+    return it->second;
+}
+
 static void decode_ip(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str, uint16_t type)
 {
     int family = (type == DNS_RR_TYPE_A) ? AF_INET : AF_INET6;
@@ -116,6 +231,60 @@ static void decode_domain_name(const uint8_t* rdata, uint16_t rdlength,
     }
 }
 
+static void decode_bind9_signing(std::string& rdata_str)
+{
+    static const std::string bind9_signing = "BIND9" + part_sep + "signing" + part_sep + "signal";
+    rdata_str.append(bind9_signing);
+}
+
+static void decode_dnskey(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+{
+    static const std::string dnskey_prefix = "DNSKEY" + part_sep;
+    static const unsigned ALGORITHM_OFFSET = 3; // size is one byte
+    static const unsigned PUBLIC_KEY_OFFSET = 4;
+
+    if (rdlength <= PUBLIC_KEY_OFFSET)
+        return; // incomplete DNSKEY record
+
+    rdata_str.append(dnskey_prefix);
+    rdata_str.append(std::to_string(rdata[ALGORITHM_OFFSET]));
+}
+
+static void decode_ds(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+{
+    static const std::string ds_prefix = "DS" + part_sep;
+    static const unsigned ALGORITHM_OFFSET = 2; // size is one byte
+    static const unsigned DIGEST_TYPE_OFFSET = 3; // size is one byte
+    static const unsigned DIGEST_OFFSET = 4;
+
+    if (rdlength <= DIGEST_OFFSET)
+        return; // incomplete DS record
+
+    rdata_str.append(ds_prefix);
+    rdata_str.append(std::to_string(rdata[ALGORITHM_OFFSET]));
+    rdata_str.append(part_sep);
+    rdata_str.append(std::to_string(rdata[DIGEST_TYPE_OFFSET]));
+}
+
+static void decode_loc(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+{
+    static const std::string loc_prefix = "LOC" + part_sep;
+    static const unsigned SIZE_OFFSET = 1;  // size is one byte
+    static const unsigned HORIZ_PRE_OFFSET = 2; // size is one byte
+    static const unsigned VERT_PRE_OFFSET = 3; // size is one byte
+    static const unsigned LATITUDE_OFFSET = 4;
+
+    if (rdlength <= LATITUDE_OFFSET)
+        return; // incomplete LOC record
+
+    rdata_str.append(loc_prefix);
+    rdata_str.append(std::to_string(rdata[SIZE_OFFSET]));
+    rdata_str.append(part_sep);
+    rdata_str.append(std::to_string(rdata[HORIZ_PRE_OFFSET]));
+    rdata_str.append(part_sep);
+    rdata_str.append(std::to_string(rdata[VERT_PRE_OFFSET]));
+}
+
 static void decode_mx(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str, const Packet* p)
 {
     static const unsigned EXCHANGE_OFFSET = 2;
@@ -127,6 +296,37 @@ static void decode_mx(const uint8_t* rdata, uint16_t rdlength, std::string& rdat
     decode_domain_name(rdata, rdlength, rdata_str, p);
 }
 
+static void decode_nsec(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str,
+    const Packet* p, const uint8_t* rr_domain_name)
+{
+    static const std::string nsec_prefix = "NSEC" + part_sep;
+    static const unsigned RDATA_OFFSET = 10;
+    const uint8_t* rr_domain_name_end = rdata - RDATA_OFFSET;
+    uint16_t rr_domain_name_len = rr_domain_name_end - rr_domain_name;
+
+    rdata_str.append(nsec_prefix);
+    decode_domain_name(rr_domain_name, rr_domain_name_len, rdata_str, p);
+    rdata_str.append(part_sep);
+    decode_domain_name(rdata, rdlength, rdata_str);
+}
+
+static void decode_opt(const uint8_t* rdata, std::string& rdata_str)
+{
+    static const std::string opt_prefix = "OPT" + part_sep;
+    static const unsigned RDATA_OFFSET = 10;
+    static const unsigned EXTENDED_RCODE_OFFSET = 4; // size is one byte
+    static const unsigned DO_Z_OFFSET = 6; // size is two bytes
+    static const unsigned DO_MASK = 0x8000;
+
+    const uint8_t* rr_type = rdata - RDATA_OFFSET;
+    auto do_z_flags = (uint16_t)rr_type[DO_Z_OFFSET] << 8 | rr_type[DO_Z_OFFSET + 1];
+
+    rdata_str.append(opt_prefix);
+    rdata_str.append(std::to_string(rr_type[EXTENDED_RCODE_OFFSET]));
+    rdata_str.append(part_sep);
+    rdata_str.append(std::to_string((do_z_flags & DO_MASK) >> 15));
+}
+
 static void decode_rrsig(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
 {
     static const std::string rrsig_prefix = "RRSIG" + part_sep;
@@ -145,35 +345,73 @@ static void decode_rrsig(const uint8_t* rdata, uint16_t rdlength, std::string& r
     decode_domain_name(rdata, rdlength, rdata_str);
 }
 
-static void decode_nsec(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str,
-    const std::string& resp_query)
+static void decode_spf(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
 {
-    static const std::string nsec_prefix = "NSEC" + part_sep;
+    static const std::string spf_prefix = "SPF" + part_sep;
 
-    rdata_str.append(nsec_prefix);
-    rdata_str.append(resp_query);
+    if (rdlength <= 1)
+        return; // incomplete SPF record
+
+    rdata_str.append(spf_prefix);
+
+    uint8_t txt_len = *rdata;
+    rdata_str.append(std::to_string(txt_len));
     rdata_str.append(part_sep);
-    decode_domain_name(rdata, rdlength, rdata_str);
+    rdata++;
+    rdlength--;
+
+    uint8_t actual_len = rdlength > txt_len ? txt_len : rdlength;
+    rdata_str.append((const char*)rdata, actual_len);
 }
 
-static void decode_ds(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+static void decode_srv(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str, const Packet* p)
 {
-    static const std::string ds_prefix = "DS" + part_sep;
-    static const unsigned DIGEST_OFFSET = 4;
+    static const unsigned TARGET_OFFSET = 6;
 
-    if (rdlength <= DIGEST_OFFSET)
-        return; // incomplete DS record
+    if (rdlength <= TARGET_OFFSET)
+        return; // incomplete SRV record
 
-    rdata_str.append(ds_prefix);
-    rdata_str.append(std::to_string(rdata[2])); // algorithm
-    rdata_str.append(part_sep);
-    rdata_str.append(std::to_string(rdata[3])); // digest type
+    rdata += TARGET_OFFSET;
+    rdlength -= TARGET_OFFSET;
+    decode_domain_name(rdata, rdlength, rdata_str, p);
+}
+
+static void decode_sshfp(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+{
+    static const std::string sshfp_prefix = "SSHFP" + part_sep;
+    static const unsigned FINGERPRINT_OFFSET = 2;
+
+    if (rdlength <= FINGERPRINT_OFFSET)
+        return; // incomplete SSHFP record
+
+    const uint8_t* rdata_end = rdata + rdlength;
+    rdata += FINGERPRINT_OFFSET;
+    std::ostringstream oss;
+    while (rdata < rdata_end)
+    {
+        oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(*rdata);
+        rdata++;
+    }
+
+    rdata_str.append(sshfp_prefix);
+    rdata_str.append(oss.str());
+}
+
+static void decode_default_rr(std::string& rdata_str, uint16_t type)
+{
+    bool is_unknown = false;
+    rdata_str.append(DNSData::qtype_name(type, &is_unknown));
+    if (is_unknown)
+    {
+        rdata_str.append("-");
+        rdata_str.append(std::to_string(type));
+    }
 }
 
-void DNSData::decode_rdata(const Packet* p, const uint8_t* rdata, uint16_t rdlength, uint16_t type,
-    std::string& rdata_str) const
+void DNSData::decode_rdata(const Packet* p, const uint8_t* rr_domain_name, const uint8_t* rdata,
+    uint16_t rdlength, uint16_t type, std::string& rdata_str) const
 {
-    assert(rdata < p->data + p->dsize);
+    assert(rdata <= p->data + p->dsize);
 
     if (rdata + rdlength > p->data + p->dsize)
         rdlength = p->dsize - (rdata - p->data);
@@ -185,14 +423,14 @@ void DNSData::decode_rdata(const Packet* p, const uint8_t* rdata, uint16_t rdlen
         decode_ip(rdata, rdlength, rdata_str, type);
         break;
 
-    case DNS_RR_TYPE_TXT:
-        decode_txt(rdata, rdlength, rdata_str);
+    case DNS_RR_TYPE_BIND9_SIGNING:
+        decode_bind9_signing(rdata_str);
         break;
 
     case DNS_RR_TYPE_CNAME:
+    case DNS_RR_TYPE_MB:
     case DNS_RR_TYPE_MD:
     case DNS_RR_TYPE_MF:
-    case DNS_RR_TYPE_MB:
     case DNS_RR_TYPE_MG:
     case DNS_RR_TYPE_MR:
     case DNS_RR_TYPE_NS:
@@ -201,23 +439,60 @@ void DNSData::decode_rdata(const Packet* p, const uint8_t* rdata, uint16_t rdlen
         decode_domain_name(rdata, rdlength, rdata_str, p);
         break;
 
+    case DNS_RR_TYPE_DNSKEY:
+        decode_dnskey(rdata, rdlength, rdata_str);
+        break;
+
+    case DNS_RR_TYPE_DS:
+        decode_ds(rdata, rdlength, rdata_str);
+        break;
+
+    case DNS_RR_TYPE_LOC:
+        decode_loc(rdata, rdlength, rdata_str);
+        break;
+
     case DNS_RR_TYPE_MX:
         decode_mx(rdata, rdlength, rdata_str, p);
         break;
 
+    case DNS_RR_TYPE_NSEC:
+        decode_nsec(rdata, rdlength, rdata_str, p, rr_domain_name);
+        break;
+
+    case DNS_RR_TYPE_OPT:
+        decode_opt(rdata, rdata_str);
+        break;
+
     case DNS_RR_TYPE_RRSIG:
         decode_rrsig(rdata, rdlength, rdata_str);
         break;
 
-    case DNS_RR_TYPE_NSEC:
-        decode_nsec(rdata, rdlength, rdata_str, resp_query);
+    case DNS_RR_TYPE_SPF:
+        decode_spf(rdata, rdlength, rdata_str);
         break;
 
-    case DNS_RR_TYPE_DS:
-        decode_ds(rdata, rdlength, rdata_str);
+    case DNS_RR_TYPE_SRV:
+        decode_srv(rdata, rdlength, rdata_str, p);
+        break;
+
+    case DNS_RR_TYPE_SSHFP:
+        decode_sshfp(rdata, rdlength, rdata_str);
+        break;
+
+    case DNS_RR_TYPE_TXT:
+        decode_txt(rdata, rdlength, rdata_str);
         break;
 
+    case DNS_RR_TYPE_CAA:
+    case DNS_RR_TYPE_HINFO:
+    case DNS_RR_TYPE_HTTPS:
+    case DNS_RR_TYPE_NSEC3:
+    case DNS_RR_TYPE_NSEC3PARAM:
+    case DNS_RR_TYPE_SVCB:
+    case DNS_RR_TYPE_TKEY:
+    case DNS_RR_TYPE_TSIG:
     default:
+        decode_default_rr(rdata_str, type);
         break;
     }
 }
@@ -225,22 +500,33 @@ void DNSData::decode_rdata(const Packet* p, const uint8_t* rdata, uint16_t rdlen
 void DNSData::get_rr_data(const Packet *p, const std::vector<uint16_t>& tabs,
     std::string& rrs, std::string* ttls) const
 {
+    assert(p != nullptr);
+    assert(p->data != nullptr);
+
     static const std::string item_sep = " ";    // list item separator
     static const unsigned RDATA_OFFSET = 10;
+    const uint8_t* rr_domain_name = nullptr;
+
     for (auto tab : tabs)
     {
-        if (tab + RDATA_OFFSET >= p->dsize)
+        if (tab + RDATA_OFFSET > p->dsize)
             continue;
 
-        auto rdata = p->data + tab;
-
-        uint16_t type = (rdata[0] << 8) | rdata[1];
-        uint32_t ttl = (rdata[4] << 24) | (rdata[5] << 16) | (rdata[6] << 8) | rdata[7];
+        auto rr_type = p->data + tab;
+        if (rr_domain_name == nullptr)
+        {
+            rr_domain_name = rr_type;
+            continue;
+        }
 
-        uint16_t rdlength = (rdata[8] << 8) | rdata[9];
+        uint16_t type = (rr_type[0] << 8) | rr_type[1];
+        uint32_t ttl = (rr_type[4] << 24) | (rr_type[5] << 16) | (rr_type[6] << 8) | rr_type[7];
+        uint16_t rdlength = (rr_type[8] << 8) | rr_type[9];
 
+        auto rdata = rr_type + RDATA_OFFSET;
         std::string rdata_str;
-        decode_rdata(p, rdata + RDATA_OFFSET, rdlength, type, rdata_str);
+        decode_rdata(p, rr_domain_name, rdata, rdlength, type, rdata_str);
+        rr_domain_name = rdata + rdlength;
 
         if (rdata_str.empty())
             continue;