]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4634: Extractor dns
authorAdrian Mamolea (admamole) <admamole@cisco.com>
Fri, 7 Mar 2025 18:57:44 +0000 (18:57 +0000)
committerMaya Dagon (mdagon) <mdagon@cisco.com>
Fri, 7 Mar 2025 18:57:44 +0000 (18:57 +0000)
Merge in SNORT/snort3 from ~ADMAMOLE/snort3:extractor_dns to master

Squashed commit of the following:

commit eff76203471fb2129af3d0e1ecd04b6b946f88a6
Author: Adrian Mamolea <admamole@cisco.com>
Date:   Fri Feb 14 12:28:13 2025 -0500

    extractor: dns support

12 files changed:
doc/user/extractor.txt
src/network_inspectors/extractor/CMakeLists.txt
src/network_inspectors/extractor/extractor.cc
src/network_inspectors/extractor/extractor_dns.cc [new file with mode: 0644]
src/network_inspectors/extractor/extractor_dns.h [new file with mode: 0644]
src/network_inspectors/extractor/extractor_enums.h
src/network_inspectors/extractor/extractor_service.cc
src/network_inspectors/extractor/extractor_service.h
src/pub_sub/dns_events.cc
src/pub_sub/dns_events.h
src/service_inspectors/dns/dns.cc
src/service_inspectors/dns/dns.h

index 8da1ad8ed8f7395ccd0fef6bb05ac98edb488106..7a0143e98f05f3356e6a27131206bf0cae1fd07f 100644 (file)
@@ -32,7 +32,8 @@ things it allows tenants to get independent data logging configurations.
             { service = 'http', tenant_id = 1, on_events = 'eot', fields = 'ts, uri, host, method' },
             { service = 'ftp', tenant_id = 1, on_events = 'request', fields = 'ts, command, arg' },
             { service = 'http', tenant_id = 2, on_events = 'eot', fields = 'ts, uri' },
-            { service = 'conn', tenant_id = 1, on_events = 'eof', fields = 'ts, uid, service' }
+            { service = 'conn', tenant_id = 1, on_events = 'eof', fields = 'ts, uid, service' },
+            { service = 'dns', tenant_id = 1, on_events = 'response', fields = 'ts, uid, query, answers' }
         }
     }
 
@@ -46,6 +47,8 @@ Services and their events:
   ** request
   ** response
   ** eot (a session defined by the following commands: APPE, DELE, RETR, STOR, STOU, ACCT, PORT, PASV, EPRT, EPSV)
+* DNS
+  ** response
 * connection (conn)
   ** eof (end of flow)
 
@@ -94,6 +97,25 @@ Fields supported for FTP:
 * `data_channel.resp_h` - IP address of data channel receiving point
 * `data_channel.resp_p` - TCP port of data channel receiving point
 
+Fields supported for DNS:
+
+* `proto` - transport protocol for DNS connection
+* `trans_id` - A 16 bit identifier assigned by the program that generates the query
+* `query` - The domain name that is the subject of this DNS transaction
+* `qclass` - A 16 bit integer that specifies the class of the query
+* `qclass_name` - A descriptive name for the class of the query
+* `qtype` - A 16 bit integer that specifies the type of the query
+* `qtype_name` - A descriptive name for the type of the query
+* `rcode` - A 16 bit integer that specifies the response code to the query
+* `rcode_name` - A descriptive name for the response code to the query
+* `AA` - A boolean, true when this is an Authoritative Answer to the query
+* `TC` - A boolean, true when the message was truncated due to UDP PDU size limits
+* `RD` - A boolean, true when the client asks the server to pursue the query recursively
+* `RA` - A boolean, denotes the availability of recursive query support at the server
+* `Z` - A 3 bit integer set to 0 unless DNSSEC is used (see RFC 2535)
+* `answers` - The list of answers to the query, only A and AAAA types are currently supported
+* `rejected` - A boolean, true when the server responds with an error code and no query
+
 Fields supported for connection:
 
 * `duration` - connection duration in seconds
index 23316a73b0313197c057235a924d401eb43924ec..756cceffbcda1455f6e003d1cd9acf6405f2c486 100644 (file)
@@ -12,6 +12,7 @@ set( FILE_LIST
     extractor_conn.cc
     extractor_csv_logger.cc
     extractor_csv_logger.h
+    extractor_dns.cc
     extractor_enums.h
     extractor_flow_data.cc
     extractor_flow_data.h
index 6b168a865ce93dbb2951c7092c8afc7b322fae4e..9e11eeb0b824df7c656828683fa399d316c11af0 100644 (file)
@@ -50,7 +50,7 @@ THREAD_LOCAL ExtractorLogger* Extractor::logger = nullptr;
 
 static const Parameter extractor_proto_params[] =
 {
-    { "service", Parameter::PT_ENUM, "http | ftp | conn", nullptr,
+    { "service", Parameter::PT_ENUM, "http | ftp | conn | dns", nullptr,
       "service to extract from" },
 
     { "tenant_id", Parameter::PT_INT, "0:max32", "0",
diff --git a/src/network_inspectors/extractor/extractor_dns.cc b/src/network_inspectors/extractor/extractor_dns.cc
new file mode 100644 (file)
index 0000000..7176e56
--- /dev/null
@@ -0,0 +1,221 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2025-2025 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// extractor_dns.cc author Cisco
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "extractor_dns.h"
+
+#include "flow/flow_key.h"
+#include "profiler/profiler.h"
+#include "pub_sub/dns_events.h"
+
+#include "extractor.h"
+#include "extractor_enums.h"
+
+using namespace snort;
+using namespace std;
+
+static uint64_t get_trans_id(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_trans_id();
+}
+
+static uint64_t get_qclass(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_query_class();
+}
+
+static uint64_t get_qtype(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_query_type();
+}
+
+static uint64_t get_rcode(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_rcode();
+}
+
+static uint64_t get_Z(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_Z();
+}
+
+static const map<string, ExtractorEvent::NumGetFn> sub_num_getters =
+{
+    {"trans_id", get_trans_id},
+    {"qclass", get_qclass},
+    {"qtype", get_qtype},
+    {"rcode", get_rcode},
+    {"Z", get_Z}
+};
+
+static const map<PktType, string> pkttype_to_protocol =
+{
+    {PktType::TCP, "TCP"},
+    {PktType::UDP, "UDP"}
+};
+
+static const char* get_proto(const DataEvent*, const Flow* f)
+{
+    const auto& iter = pkttype_to_protocol.find(f->pkt_type);
+    return (iter != pkttype_to_protocol.end()) ? iter->second.c_str() : "";
+}
+
+static const char* get_query(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_query().c_str();
+}
+
+static const char* get_qclass_name(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_query_class_name().c_str();
+}
+
+static const char* get_qtype_name(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_query_type_name().c_str();
+}
+
+static const char* get_rcode_name(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_rcode_name().c_str();
+}
+
+static const char* get_AA(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_AA() ? "T" : "F";
+}
+
+static const char* get_TC(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_TC() ? "T" : "F";
+}
+
+static const char* get_RD(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_RD() ? "T" : "F";
+}
+
+static const char* get_RA(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_RA() ? "T" : "F";
+}
+
+static const char* get_answers(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_answers().c_str();
+}
+
+static const char* get_rejected(const DataEvent* event, const Flow*)
+{
+    return ((const DnsResponseEvent*)event)->get_rejected() ? "T" : "F";
+}
+
+static const map<string, ExtractorEvent::BufGetFn> sub_buf_getters =
+{
+    {"proto", get_proto},
+    {"query", get_query},
+    {"qclass_name", get_qclass_name},
+    {"qtype_name", get_qtype_name},
+    {"rcode_name", get_rcode_name},
+    {"AA", get_AA},
+    {"TC", get_TC},
+    {"RD", get_RD},
+    {"RA", get_RA},
+    {"answers", get_answers},
+    {"rejected", get_rejected}
+};
+
+THREAD_LOCAL const snort::Connector::ID* DnsResponseExtractor::log_id = nullptr;
+
+DnsResponseExtractor::DnsResponseExtractor(Extractor& i, uint32_t t, const vector<string>& fields)
+    : ExtractorEvent(ServiceType::DNS, i, t)
+{
+    for (const auto& f : fields)
+    {
+        if (append(nts_fields, nts_getters, f))
+            continue;
+        if (append(sip_fields, sip_getters, f))
+            continue;
+        if (append(num_fields, num_getters, f))
+            continue;
+        if (append(num_fields, sub_num_getters, f))
+            continue;
+        if (append(buf_fields, sub_buf_getters, f))
+            continue;
+    }
+
+    DataBus::subscribe_global(dns_pub_key, DnsEventIds::DNS_RESPONSE,
+        new Resp(*this, S_NAME), i.get_snort_config());
+}
+
+void DnsResponseExtractor::internal_tinit(const snort::Connector::ID* id)
+{
+    log_id = id;
+}
+
+void DnsResponseExtractor::handle(DataEvent& event, Flow* flow)
+{
+    // cppcheck-suppress unreadVariable
+    Profile profile(extractor_perf_stats);
+
+    if (!filter(flow))
+        return;
+
+    extractor_stats.total_event++;
+
+    logger->open_record();
+    log(nts_fields, &event, flow);
+    log(sip_fields, &event, flow);
+    log(num_fields, &event, flow);
+    log(buf_fields, &event, flow);
+    logger->close_record(*log_id);
+}
+
+//-------------------------------------------------------------------------
+//  Unit Tests
+//-------------------------------------------------------------------------
+
+#ifdef UNIT_TEST
+
+#include "catch/snort_catch.h"
+
+#include <memory.h>
+
+TEST_CASE("DNS Proto", "[extractor]")
+{
+    Flow* flow = new Flow;
+    InspectionPolicy ins;
+    set_inspection_policy(&ins);
+    NetworkPolicy net;
+    set_network_policy(&net);
+
+    SECTION("unknown")
+    {
+        flow->pkt_type = PktType::NONE;
+        const char* proto = get_proto(nullptr, flow);
+        CHECK_FALSE(strcmp("", proto));
+    }
+
+    delete flow;
+}
+
+#endif
diff --git a/src/network_inspectors/extractor/extractor_dns.h b/src/network_inspectors/extractor/extractor_dns.h
new file mode 100644 (file)
index 0000000..e13989c
--- /dev/null
@@ -0,0 +1,40 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2025-2025 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// extractor_dns.h author Cisco
+
+#ifndef EXTRACTOR_DNS_H
+#define EXTRACTOR_DNS_H
+
+#include "extractors.h"
+
+class DnsResponseExtractor : public ExtractorEvent
+{
+public:
+    DnsResponseExtractor(Extractor&, uint32_t tenant, const std::vector<std::string>& fields);
+
+    void handle(DataEvent&, Flow*);
+
+private:
+    using Resp = Handler<DnsResponseExtractor>;
+
+    void internal_tinit(const snort::Connector::ID*) override;
+
+    static THREAD_LOCAL const snort::Connector::ID* log_id;
+};
+
+#endif
index 95aea348b232f2a7b5d094953e469c67a50b2f66..9d7bd15cd797e8c15fee76e15b3da3a000c54776 100644 (file)
@@ -30,6 +30,7 @@ public:
         HTTP,
         FTP,
         CONN,
+        DNS,
         ANY,
         MAX
     };
@@ -51,6 +52,8 @@ public:
             return "ftp";
         case CONN:
             return "conn";
+        case DNS:
+            return "dns";
         case ANY: // fallthrough
         case MAX: // fallthrough
         default:
index ec9f1e4285b659a80b9a1065ec25f131fe03904c..ffdbe2506844abba38110bfc585550e361bda680 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "extractor.h"
 #include "extractor_conn.h"
+#include "extractor_dns.h"
 #include "extractor_ftp.h"
 #include "extractor_http.h"
 
@@ -123,6 +124,10 @@ ExtractorService* ExtractorService::make_service(Extractor& ins, const ServiceCo
         srv = new ConnExtractorService(cfg.tenant_id, cfg.fields, cfg.on_events, cfg.service, ins);
         break;
 
+    case ServiceType::DNS:
+        srv = new DnsExtractorService(cfg.tenant_id, cfg.fields, cfg.on_events, cfg.service, ins);
+        break;
+
     case ServiceType::ANY: // fallthrough
     default:
         ErrorMessage("Extractor: '%s' service is not supported\n", cfg.service.c_str());
@@ -215,6 +220,11 @@ void ExtractorService::validate(const ServiceConfig& cfg)
         validate_fields(ConnExtractorService::blueprint, cfg.fields);
         break;
 
+    case ServiceType::DNS:
+        validate_events(DnsExtractorService::blueprint, cfg.on_events);
+        validate_fields(DnsExtractorService::blueprint, cfg.fields);
+        break;
+
     case ServiceType::ANY: // fallthrough
     default:
         ParseError("'%s' service is not supported", cfg.service.c_str());
@@ -363,6 +373,56 @@ const snort::Connector::ID& ConnExtractorService::internal_tinit()
 const snort::Connector::ID& ConnExtractorService::get_log_id()
 { return log_id; }
 
+//-------------------------------------------------------------------------
+//  DnsExtractorService
+//-------------------------------------------------------------------------
+
+const ServiceBlueprint DnsExtractorService::blueprint =
+{
+    // events
+    {
+        "response",
+    },
+    // fields
+    {
+        "proto",
+        "trans_id",
+        "query",
+        "qclass",
+        "qclass_name",
+        "qtype",
+        "qtype_name",
+        "rcode",
+        "rcode_name",
+        "AA",
+        "TC",
+        "RD",
+        "RA",
+        "Z",
+        "answers",
+        "rejected"
+    },
+};
+
+THREAD_LOCAL Connector::ID DnsExtractorService::log_id;
+
+DnsExtractorService::DnsExtractorService(uint32_t tenant, const std::vector<std::string>& srv_fields,
+    const std::vector<std::string>& srv_events, ServiceType s_type, Extractor& ins)
+    : ExtractorService(tenant, srv_fields, srv_events, blueprint, s_type, ins)
+{
+    for (const auto& event : get_events())
+    {
+        if (!strcmp("response", event.c_str()))
+            handlers.push_back(new DnsResponseExtractor(ins, tenant_id, get_fields()));
+    }
+}
+
+const snort::Connector::ID& DnsExtractorService::internal_tinit()
+{ return log_id = logger->get_id(type.c_str()); }
+
+const snort::Connector::ID& DnsExtractorService::get_log_id()
+{ return log_id; }
+
 //-------------------------------------------------------------------------
 //  Unit Tests
 //-------------------------------------------------------------------------
@@ -380,12 +440,14 @@ TEST_CASE("Service Type", "[extractor]")
         ServiceType http = ServiceType::HTTP;
         ServiceType ftp = ServiceType::FTP;
         ServiceType conn = ServiceType::CONN;
+        ServiceType dns = ServiceType::DNS;
         ServiceType any = ServiceType::ANY;
         ServiceType max = ServiceType::MAX;
 
         CHECK_FALSE(strcmp("http", http.c_str()));
         CHECK_FALSE(strcmp("ftp", ftp.c_str()));
         CHECK_FALSE(strcmp("conn", conn.c_str()));
+        CHECK_FALSE(strcmp("dns", dns.c_str()));
         CHECK_FALSE(strcmp("(not set)", any.c_str()));
         CHECK_FALSE(strcmp("(not set)", max.c_str()));
     }
index eb8b2fd05ecf5e26e6a2f082c58baa6b5577d618..c0bd0896e46309b76c6af2cb4f4796b58078daa7 100644 (file)
@@ -137,5 +137,20 @@ private:
     static THREAD_LOCAL snort::Connector::ID log_id;
 };
 
+class DnsExtractorService : public ExtractorService
+{
+public:
+    static const ServiceBlueprint blueprint;
+
+    DnsExtractorService(uint32_t tenant, const std::vector<std::string>& fields,
+        const std::vector<std::string>& events, ServiceType, Extractor&);
+
+private:
+    const snort::Connector::ID& internal_tinit() override;
+    const snort::Connector::ID& get_log_id() override;
+
+    static THREAD_LOCAL snort::Connector::ID log_id;
+};
+
 #endif
 
index 9ea0b291a55fc6e6769b16a2a51c516a44d3e82f..c6c52339969fd65f55ffff9752c4844e36260104 100644 (file)
 #include "dns_events.h"
 
 #include <algorithm>
+#include <map>
+#include <string>
 
 #include "service_inspectors/dns/dns.h"
 
 using namespace snort;
 
+static const std::string& class_name(uint16_t query_class)
+{
+    static const std::map<uint16_t, std::string> class_names =
+    {
+        {1,   "C_INTERNET"},   // RFC 1035
+        {2,   "C_CSNET"},      // RFC 1035
+        {3,   "C_CHAOS"},      // RFC 1035
+        {4,   "C_HESIOD"},     // RFC 1035
+        {254, "C_NONE"},       // RFC 2136
+        {255, "ANY"}           // RFC 1035
+        // Add more classes as needed
+    };
+
+    static const std::string unknown = "UNKNOWN";
+
+    auto it = class_names.find(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 =
+    {
+        {0,  "NOERROR"},   // RFC 1035
+        {1,  "FORMERR"},   // RFC 1035
+        {2,  "SERVFAIL"},  // RFC 1035
+        {3,  "NXDOMAIN"},  // RFC 1035, also referred as "Name Error"
+        {4,  "NOTIMP"},    // RFC 1035
+        {5,  "REFUSED"},   // RFC 1035
+        {6,  "YXDOMAIN"},  // RFC 2136, RFC 6672
+        {7,  "YXRRSET"},   // RFC 2136
+        {8,  "NXRRSET"},   // RFC 2136
+        {9,  "NOTAUTH"},   // RFC 2136, RFC 8945
+        {10, "NOTZONE"},   // RFC 2136
+        {11, "DSOTYPENI"}, // RFC 8490
+        {12, "unassigned-12"},
+        {13, "unassigned-13"},
+        {14, "unassigned-14"},
+        {15, "unassigned-15"},
+        // extended RCODEs below, see
+        // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
+        {16, "BADVERS"},   // RFC 6891, also BADSIG RFC 8945
+        {17, "BADKEY"},    // RFC 8945
+        {18, "BADTIME"},   // RFC 8945
+        {19, "BADMODE"},   // RFC 2930
+        {20, "BADNAME"},   // RFC 2930
+        {21, "BADALG"},    // RFC 2930
+        {22, "BADTRUNC"},  // RFC 8945
+        {23, "BADCOOKIE"}  // RFC 7873
+        // Add more RCODEs as needed
+    };
+
+    static const std::string unknown = "UNKNOWN";
+
+    auto it = rcode_names.find(rcode);
+    return it != rcode_names.end() ? it->second : unknown;
+}
+
 void IPFqdnCacheItem::add_ip(const SfIp& ip)
 {
     if (ip.is_set())
@@ -75,3 +238,86 @@ bool DnsResponseDataEvents::empty() const
 {
     return (dns_ips.empty() or dns_fqdns.empty());
 }
+
+uint16_t DnsResponseEvent::get_trans_id() const
+{
+    return session.hdr.id;
+}
+
+const std::string& DnsResponseEvent::get_query() const
+{
+    return session.resp_query;
+}
+
+uint16_t DnsResponseEvent::get_query_class() const
+{
+    return session.curr_q.dns_class;
+}
+
+const std::string& DnsResponseEvent::get_query_class_name() const
+{
+    static const std::string empty = "";
+    if (session.hdr.questions == 0)
+        return empty;
+    return class_name(session.curr_q.dns_class);
+}
+
+uint16_t DnsResponseEvent::get_query_type() const
+{
+    return session.curr_q.type;
+}
+
+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());
+}
+
+uint8_t DnsResponseEvent::get_rcode() const
+{
+    return session.hdr.flags & DNS_HDR_FLAG_REPLY_CODE_MASK;
+}
+
+const std::string& DnsResponseEvent::get_rcode_name() const
+{
+    return rcode_name(get_rcode());
+}
+
+bool DnsResponseEvent::get_AA() const
+{
+    return session.hdr.flags & DNS_HDR_FLAG_AUTHORITATIVE;
+}
+
+bool DnsResponseEvent::get_TC() const
+{
+    return session.hdr.flags & DNS_HDR_FLAG_TRUNCATED;
+}
+
+bool DnsResponseEvent::get_RD() const
+{
+    return session.hdr.flags & DNS_HDR_FLAG_RECURSION_DESIRED;
+}
+
+bool DnsResponseEvent::get_RA() const
+{
+    return session.hdr.flags & DNS_HDR_FLAG_RECURSION_AVAIL;
+}
+
+uint8_t DnsResponseEvent::get_Z() const
+{
+    return (session.hdr.flags & DNS_HDR_FLAG_Z) >> DNS_HDR_FLAG_Z_SHIFT;
+}
+
+const std::string& DnsResponseEvent::get_answers() const
+{
+    return session.answers;
+}
+
+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;
+}
index 6ec7383dad4104a05e307658010ddd0b20f10377..809985d4ba5db34d3db7ac779a09855610fe3a30 100644 (file)
@@ -50,6 +50,7 @@ struct DnsEventIds
     enum : unsigned
     {
         DNS_RESPONSE_DATA,
+        DNS_RESPONSE,
         num_ids
     };
 };
@@ -58,6 +59,7 @@ const snort::PubKey dns_pub_key { "dns", DnsEventIds::num_ids };
 
 class DnsResponseIp;
 class DnsResponseFqdn;
+struct DNSData;
 
 namespace snort
 {
@@ -73,6 +75,32 @@ private:
     std::vector<DnsResponseIp> dns_ips;
     std::vector<DnsResponseFqdn> dns_fqdns;
 };
+
+class SO_PUBLIC DnsResponseEvent : public snort::DataEvent
+{
+public:
+    DnsResponseEvent(const DNSData& ssn) : session(ssn) { }
+
+    uint16_t get_trans_id() const;
+    const std::string& get_query() const;
+    uint16_t get_query_class() const;
+    const std::string& get_query_class_name() const;
+    uint16_t get_query_type() const;
+    const std::string& get_query_type_name() const;
+    uint8_t get_rcode() const;
+    const std::string& get_rcode_name() const;
+    bool get_AA() const;
+    bool get_TC() const;
+    bool get_RD() const;
+    bool get_RA() const;
+    uint8_t get_Z() const;
+    const std::string& get_answers() const;
+    bool get_rejected() const;
+
+private:
+    const DNSData& session;
+};
+
 }
 
 #endif
index 045d8b227bb5de31972969c04757a065c5e98692..3352191762d715b87ba55c05420a820c5d20e0a5 100644 (file)
@@ -122,6 +122,13 @@ bool DNSData::valid_dns(const DNSHdr& dns_header) const
     return true;
 }
 
+void DNSData::add_answer(const char* answer)
+{
+    if (!answers.empty())
+        answers.append(" ");
+    answers.append(answer);
+}
+
 DNSData* get_dns_session_data(Packet* p, bool from_server, DNSData& udpSessionData)
 {
     DnsFlowData* fd;
@@ -428,11 +435,12 @@ static uint16_t ParseDNSQuestion(
 
     if (dnsSessionData->curr_rec_state < DNS_RESP_STATE_Q_NAME_COMPLETE)
     {
-        uint16_t new_bytes_unused = ParseDNSName(data, bytes_unused, dnsSessionData);
+        uint16_t new_bytes_unused = ParseDNSName(data, bytes_unused, dnsSessionData, true);
         uint16_t bytes_used = bytes_unused - new_bytes_unused;
 
         if (dnsSessionData->curr_txt.name_state == DNS_RESP_STATE_NAME_COMPLETE)
         {
+            dnsSessionData->resp_query = dnsSessionData->curr_txt.dns_name;
             dnsSessionData->curr_rec_state = DNS_RESP_STATE_Q_TYPE;
             dnsSessionData->curr_txt = DNSNameState();
             data = data + bytes_used;
@@ -738,7 +746,8 @@ static uint16_t SkipDNSRData(
 static uint16_t ParseDNSRData(
     const unsigned char* data,
     uint16_t bytes_unused,
-    DNSData* dnsSessionData)
+    DNSData* dnsSessionData,
+    void (DNSData::*save_rdata)(const char*) = nullptr)
 {
     if (bytes_unused == 0)
     {
@@ -770,13 +779,23 @@ static uint16_t ParseDNSRData(
         break;
     case DNS_RR_TYPE_A:
     case DNS_RR_TYPE_AAAA:
-        if (dnsSessionData->publish_response())
         {
-            dnsSessionData->dns_events.add_fqdn(dnsSessionData->cur_fqdn_event, dnsSessionData->curr_rr.ttl);
-            dnsSessionData->dns_events.add_ip(DnsResponseIp(data, dnsSessionData->curr_rr.type));
-        }
+            auto resp_ip = DnsResponseIp(data, dnsSessionData->curr_rr.type);
+            if (dnsSessionData->publish_response())
+            {
+                dnsSessionData->dns_events.add_fqdn(dnsSessionData->cur_fqdn_event, dnsSessionData->curr_rr.ttl);
+                dnsSessionData->dns_events.add_ip(resp_ip);
+            }
 
+            if (save_rdata)
+            {
+                SfIpString ipbuf;
+                resp_ip.get_ip().ntop(ipbuf);
+                (dnsSessionData->*save_rdata)(ipbuf);
+            }
+        }
         bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData);
+
         break;
     case DNS_RR_TYPE_CNAME:
         if (dnsSessionData->publish_response())
@@ -904,7 +923,7 @@ static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& ne
                 case DNS_RESP_STATE_RR_RDATA_MID:
                     /* Data now points to the beginning of the RDATA */
                     data = p->data + (p->dsize - bytes_unused);
-                    bytes_unused = ParseDNSRData(data, bytes_unused, dnsSessionData);
+                    bytes_unused = ParseDNSRData(data, bytes_unused, dnsSessionData, &DNSData::add_answer);
                     if (dnsSessionData->curr_rec_state != DNS_RESP_STATE_RR_COMPLETE)
                     {
                         needNextPacket = true;
@@ -1178,6 +1197,9 @@ static void snort_dns(Packet* p, const DnsConfig* dns_config)
         if (!needNextPacket and dnsSessionData->has_events())
             DataBus::publish(Dns::get_pub_id(), DnsEventIds::DNS_RESPONSE_DATA, dnsSessionData->dns_events);
 
+        DnsResponseEvent dns_response_event(*dnsSessionData);
+        DataBus::publish(Dns::get_pub_id(), DnsEventIds::DNS_RESPONSE, dns_response_event, p->flow);
+
         if (p->type() == PktType::UDP)
             p->flow->session_state |= STREAM_STATE_CLOSED;
     }
index 450e82c72162454d859056618dd9672443cbab70..9b09961bf2cd2c8a1d231e802d73a9ffe7c680a6 100644 (file)
@@ -55,6 +55,9 @@ struct DNSHdr
 #define DNS_HDR_FLAG_OPCODE_MASK            0x7800
 #define DNS_HDR_FLAG_RESPONSE               0x8000
 
+#define DNS_HDR_FLAG_Z (DNS_HDR_FLAG_NON_AUTHENTICATED_OK | DNS_HDR_FLAG_ANS_AUTHENTICATED | DNS_HDR_FLAG_RESERVED)
+#define DNS_HDR_FLAG_Z_SHIFT 4
+
 struct DNSQuestion
 {
     uint16_t type = 0;
@@ -202,10 +205,13 @@ struct DNSData
     const DnsConfig* dns_config = nullptr;
     snort::DnsResponseDataEvents dns_events;
     DnsResponseFqdn cur_fqdn_event;
+    std::string resp_query;
+    std::string answers;
 
     bool publish_response() const;
     bool has_events() const;
     bool valid_dns(const DNSHdr&) const;
+    void add_answer(const char* answer);
 };
 
 DNSData* get_dns_session_data(snort::Packet* p, bool from_server, DNSData& udpSessionData);