]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4896: Doh initial
authorShibin K V (shikv) <shikv@cisco.com>
Tue, 7 Oct 2025 13:38:28 +0000 (13:38 +0000)
committerShanmugam S (shanms) <shanms@cisco.com>
Tue, 7 Oct 2025 13:38:28 +0000 (13:38 +0000)
Merge in SNORT/snort3 from ~SHIKV/snort3:doh_initial to master

Squashed commit of the following:

commit bf26dd87ba5532b379784ff8f4c8b7dee26b8001
Author: shibin k v <shikv@cisco.com>
Date:   Thu Sep 18 11:44:41 2025 -0500

    stream_tcp: copy all layers from original packet during pseudo packet creation

commit b16a92f10481ad99d4196e80c8bed0fb67262e96
Author: shibin k v <shikv@cisco.com>
Date:   Wed Sep 3 07:56:16 2025 -0500

    appid, http_inspect, dns: add support for DNS over HTTPS and DNS over QUIC

26 files changed:
src/network_inspectors/appid/CMakeLists.txt
src/network_inspectors/appid/appid_dns_payload_event_handler.cc [new file with mode: 0644]
src/network_inspectors/appid/appid_dns_payload_event_handler.h [new file with mode: 0644]
src/network_inspectors/appid/appid_dns_session.h
src/network_inspectors/appid/appid_inspector.cc
src/network_inspectors/appid/detector_plugins/detector_dns.cc
src/network_inspectors/appid/detector_plugins/detector_dns.h
src/network_inspectors/appid/detector_plugins/test/CMakeLists.txt
src/network_inspectors/appid/detector_plugins/test/detector_dns_test.cc [new file with mode: 0644]
src/pub_sub/CMakeLists.txt
src/pub_sub/dns_payload_event.h [new file with mode: 0644]
src/pub_sub/http_event_ids.h
src/pub_sub/intrinsic_event_ids.h
src/pub_sub/test/pub_sub_http_request_body_event_test.cc
src/service_inspectors/dns/CMakeLists.txt
src/service_inspectors/dns/dns.cc
src/service_inspectors/dns/dns.h
src/service_inspectors/dns/dns_payload_event_handler.cc [new file with mode: 0644]
src/service_inspectors/dns/dns_payload_event_handler.h [new file with mode: 0644]
src/service_inspectors/dns/dns_splitter.cc
src/service_inspectors/http_inspect/http_enum.h
src/service_inspectors/http_inspect/http_msg_body.cc
src/service_inspectors/http_inspect/http_msg_header.cc
src/service_inspectors/http_inspect/http_stream_splitter_finish.cc
src/service_inspectors/http_inspect/http_tables.cc
src/stream/tcp/tcp_reassembler.cc

index 2d84c193669c88641f581f8f4c55d312bcf9fca8..58b2b0799b92ff75d24a9844820950a04999f985 100644 (file)
@@ -213,7 +213,9 @@ set ( APPID_SOURCES
     appid_cpu_profile_table.cc 
     appid_cpu_profile_table.h
     user_data_map.h
-    user_data_map.cc 
+    user_data_map.cc
+    appid_dns_payload_event_handler.cc
+    appid_dns_payload_event_handler.h
 )
 
 #if (STATIC_INSPECTORS)
diff --git a/src/network_inspectors/appid/appid_dns_payload_event_handler.cc b/src/network_inspectors/appid/appid_dns_payload_event_handler.cc
new file mode 100644 (file)
index 0000000..2309f5a
--- /dev/null
@@ -0,0 +1,126 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// appid_dns_payload_event_handler.cc author Shibin K V <shikv@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "appid_dns_payload_event_handler.h"
+
+#include "flow/stream_flow.h"
+#include "profiler/profiler_defs.h"
+#include "pub_sub/dns_payload_event.h"
+
+#include "appid_dns_session.h"
+#include "appid_inspector.h"
+#include "detector_plugins/detector_dns.h"
+
+using namespace snort;
+
+void AppIdDnsPayloadEventHandler::handle(DataEvent& event, Flow* flow)
+{
+    Packet *p = DetectionEngine::get_current_packet();
+    if(!flow or !p)
+        return;
+    DnsPayloadEvent* dns_payload_event = (DnsPayloadEvent*)&event;
+    int32_t payload_length = 0;
+    const uint8_t* dns_payload = dns_payload_event->get_payload(payload_length);
+    bool is_udp = dns_payload_event->is_dns_udp();
+    AppIdSession* asd = appid_api.get_appid_session(*flow);
+    if (!dns_payload or payload_length <= 0 or !dns_payload_event->is_last_piece() or !asd)
+        return;
+    AppidChangeBits change_bits;
+    // Skip sessions using old odp context after reload detectors
+    if (!pkt_thread_odp_ctxt or
+        (pkt_thread_odp_ctxt->get_version() != asd->get_odp_ctxt_version()))
+        return;
+
+    bool is_appid_cpu_profiling_running = (asd->get_odp_ctxt().is_appid_cpu_profiler_running());
+    Stopwatch<SnortClock> per_appid_event_cpu_timer;
+
+    if (is_appid_cpu_profiling_running)
+        per_appid_event_cpu_timer.start();
+
+    AppidSessionDirection dir = dns_payload_event->is_from_client() ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER;
+    AppIdDiscoveryArgs args(dns_payload, payload_length, dir, *asd, nullptr, change_bits);
+    APPID_LOG(p, TRACE_DEBUG_LEVEL, "Processing DNS-%s payload event\n", is_udp ? "UDP" : "TCP");
+    int rval;
+    if (is_udp)
+    {
+        if (!dns_udp_detector)
+            return;
+        rval = static_cast<DnsUdpServiceDetector*>(dns_udp_detector)->validate_doh(args);
+    }
+    else
+    {
+        if (!dns_tcp_detector)
+            return;
+        rval = static_cast<DnsTcpServiceDetector*>(dns_tcp_detector)->validate_doq(args);
+    }
+    APPID_LOG(p, TRACE_DEBUG_LEVEL, "DNS-%s detector returned %d\n",
+                is_udp ? "UDP" : "TCP", rval);
+
+    if (rval == APPID_SUCCESS || rval == APPID_INPROCESS)
+    {
+        AppId service_id = asd->pick_service_app_id();
+        if (service_id == APP_ID_HTTP2 || service_id == APP_ID_HTTP3)
+        {
+            if (flow->stream_intf)
+            {
+                int64_t stream_id = -1;
+                flow->stream_intf->get_stream_id(flow, stream_id);
+                if (stream_id != -1)
+                {
+                    AppIdHttpSession* hsession = asd->get_matching_http_session(stream_id);
+                    if (hsession)
+                        hsession->set_payload(APP_ID_DNS, change_bits, "body");
+                }
+                else
+                {
+                    assert(false);
+                    return; // we should not reach here
+                }
+            }
+        }
+        else
+        {
+            AppIdHttpSession* hsession = asd->get_http_session();
+            if (hsession)
+                hsession->set_payload(APP_ID_DNS, change_bits, "body");
+            if (asd->get_payload_id() != APP_ID_DNS)
+                asd->set_payload_appid_data(APP_ID_DNS, nullptr);
+            asd->set_ss_application_ids_payload(APP_ID_DNS, change_bits);
+        }
+        AppIdDnsSession* dsession = asd->get_dns_session();
+        assert(dsession);
+        if (!dsession)
+            return;
+        dsession->set_doh(true);
+        asd->publish_appid_event(change_bits, *p);
+        // TODO: add DNS hostname based appid detection
+    }
+
+    if (is_appid_cpu_profiling_running)
+    {
+        per_appid_event_cpu_timer.stop();
+        asd->stats.processing_time += TO_USECS(per_appid_event_cpu_timer.get());
+    }
+}
+
diff --git a/src/network_inspectors/appid/appid_dns_payload_event_handler.h b/src/network_inspectors/appid/appid_dns_payload_event_handler.h
new file mode 100644 (file)
index 0000000..5e4ddf1
--- /dev/null
@@ -0,0 +1,58 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// appid_doh_event_handler.h author Shibin K V <shikv@cisco.com>
+
+#ifndef APPID_DNS_PAYLOAD_EVENT_HANDLER_H
+#define APPID_DNS_PAYLOAD_EVENT_HANDLER_H
+
+#include "appid_detector.h"
+#include "appid_inspector.h"
+#include "appid_module.h"
+
+class AppIdDnsPayloadEventHandler : public snort::DataHandler
+{
+public:
+    AppIdDnsPayloadEventHandler(AppIdInspector& inspector) :
+        DataHandler(MOD_NAME)
+    {
+        order = 100; // to make sure appid receives events before DNS inspector
+        AppIdDetectors *appid_udp_detectors = nullptr;
+        AppIdDetectors *appid_tcp_detectors = nullptr;
+        appid_udp_detectors = inspector.get_ctxt().get_odp_ctxt().get_service_disco_mgr().get_udp_detectors();
+        appid_tcp_detectors = inspector.get_ctxt().get_odp_ctxt().get_service_disco_mgr().get_tcp_detectors();
+        assert(appid_udp_detectors);
+        if (!appid_udp_detectors)
+            return;
+        auto udp_detector = appid_udp_detectors->find("DNS-UDP");
+        if (udp_detector != appid_udp_detectors->end())
+            dns_udp_detector = udp_detector->second;
+        auto tcp_detector = appid_tcp_detectors->find("DNS-TCP");
+        if (tcp_detector != appid_tcp_detectors->end())
+            dns_tcp_detector = tcp_detector->second;
+    }
+
+    void handle(snort::DataEvent& event, snort::Flow* flow) override;
+
+private:
+    AppIdDetector *dns_udp_detector = nullptr;
+    AppIdDetector *dns_tcp_detector = nullptr;
+};
+
+#endif // APPID_DNS_PAYLOAD_EVENT_HANDLER_H
+
index eeeccc43f8c89b16c0f090fabe3eb8fad3157471..b20e1cef84dc5402291ddbb5049dab61a5a54bd0 100644 (file)
@@ -23,6 +23,7 @@
 #define APPID_DNS_SESSION_H
 
 #include <string>
+
 #include "pub_sub/appid_events.h"
 
 #define DNS_GOT_QUERY    0x01
@@ -44,6 +45,7 @@ public:
         record_type = 0;
         ttl = 0;
         options_offset = 0;
+        doh = false;
     }
 
     uint8_t get_state() const
@@ -109,6 +111,12 @@ public:
     void set_options_offset(uint16_t optionsOffset)
     { options_offset = optionsOffset; }
 
+    void set_doh(bool Doh)
+    { doh = Doh; }
+
+    bool is_doh() const
+    { return doh; }
+
 protected:
     uint8_t state = 0;
     uint8_t response_type = 0;
@@ -118,5 +126,6 @@ protected:
     std::string host;
     uint16_t host_offset = 0;
     uint16_t options_offset = 0;
+    bool doh = false;
 };
 #endif
index 5e51136910ce5baca8f119396d16de4e5ced67e2..0c1f28b9fbaabebb77e101542f0f0debbde90719 100644 (file)
@@ -35,6 +35,7 @@
 #include "packet_io/packet_tracer.h"
 #include "profiler/profiler.h"
 #include "pub_sub/appid_event_ids.h"
+#include "pub_sub/dns_events.h"
 #include "pub_sub/intrinsic_event_ids.h"
 
 #include "appid_cip_event_handler.h"
@@ -42,6 +43,7 @@
 #include "appid_dcerpc_event_handler.h"
 #include "appid_debug.h"
 #include "appid_discovery.h"
+#include "appid_dns_payload_event_handler.h"
 #include "appid_eve_process_event_handler.h"
 #include "appid_ha.h"
 #include "appid_http_event_handler.h"
@@ -150,6 +152,7 @@ bool AppIdInspector::configure(SnortConfig* sc)
         new HttpEventHandler(HttpEventHandler::RESPONSE_EVENT, *this), *sc);
 
     DataBus::subscribe_global(http_pub_key, HttpEventIds::REQUEST_BODY, new AppIdHttpXReqBodyEventHandler(), *sc);
+    DataBus::subscribe_global(intrinsic_pub_key, IntrinsicEventIds::DNS_PAYLOAD, new AppIdDnsPayloadEventHandler(*this), *sc);
     DataBus::subscribe_global(sip_pub_key, SipEventIds::DIALOG, new SipEventHandler(*this), *sc);
     DataBus::subscribe_global(dce_tcp_pub_key, DceTcpEventIds::EXP_SESSION, new DceExpSsnEventHandler(), *sc);
     DataBus::subscribe_global(ssh_pub_key, SshEventIds::STATE_CHANGE, new SshEventHandler(), *sc);
index 851997b233a7288f735d74a54d8ccddf66c84d86..f850bb8dd2aee4a0a40f46ae9067469a6404b36f 100644 (file)
@@ -567,6 +567,27 @@ int DnsValidator::validate_packet(const uint8_t* data, uint16_t size, const int,
     return APPID_SUCCESS;
 }
 
+int DnsUdpServiceDetector::validate_doh(AppIdDiscoveryArgs& args)
+{
+    int rval;
+
+    if (!args.size)
+        return APPID_INPROCESS;
+
+    if (args.size < sizeof(DNSHeader))
+        return APPID_NOMATCH;
+
+    if ((rval = dns_validate_header(args.dir, (const DNSHeader*)args.data,
+        args.asd.get_odp_ctxt().dns_host_reporting, args.asd)) != APPID_SUCCESS)
+        return rval;
+
+    // Coverity doesn't realize that validate_packet() checks the packet data for valid values
+    // coverity[tainted_scalar]
+    rval = validate_packet(args.data, args.size, args.dir,
+        args.asd.get_odp_ctxt().dns_host_reporting, args.asd, args.change_bits);
+    return rval;
+}
+
 int DnsUdpServiceDetector::validate(AppIdDiscoveryArgs& args)
 {
     int rval;
@@ -652,6 +673,125 @@ inprocess:
         return rval;
     }
 }
+int DnsTcpServiceDetector::validate_doq(AppIdDiscoveryArgs& args)
+{
+    int rval;
+    uint8_t* reallocated_data = nullptr;
+    const uint8_t* data = args.data;
+    uint16_t size = args.size;
+    ServiceDNSData* dd = static_cast<ServiceDNSData*>(data_get(args.asd));
+    {
+        if (!args.size)
+            goto inprocess;
+
+        if (args.size < sizeof(DNSTCPHeader))
+        {
+            if (args.dir == APP_ID_FROM_INITIATOR)
+                goto not_compatible;
+            else
+                goto fail;
+        }
+
+        if (!dd)
+        {
+            dd = new ServiceDNSData;
+            data_add(args.asd, dd);
+        }
+
+        if (dd->cached_data and dd->cached_len and args.dir == APP_ID_FROM_INITIATOR)
+        {
+            reallocated_data = static_cast<uint8_t*>(snort_calloc(dd->cached_len + args.size, sizeof(uint8_t)));
+            memcpy(reallocated_data, dd->cached_data, dd->cached_len);
+            memcpy(reallocated_data + dd->cached_len, args.data, args.size);
+            size = dd->cached_len + args.size;
+            dd->free_dns_cache();
+            data = reallocated_data;
+        }
+
+        const DNSTCPHeader* hdr = (const DNSTCPHeader*)data;
+        data = data + sizeof(DNSTCPHeader);
+        size = size - sizeof(DNSTCPHeader);
+        uint16_t tmp = ntohs(hdr->length);
+
+        if (tmp > size and args.dir == APP_ID_FROM_INITIATOR)
+        {
+            dd->save_dns_cache(args.size, args.data);
+            goto inprocess;
+        } else if (tmp > size and args.dir == APP_ID_FROM_RESPONDER) {
+            goto not_compatible;
+        }
+
+        if (tmp < sizeof(DNSHeader) || dns_validate_header(args.dir, (const DNSHeader*)data,
+            args.asd.get_odp_ctxt().dns_host_reporting, args.asd))
+        {
+            if (args.dir == APP_ID_FROM_INITIATOR)
+                goto not_compatible;
+            else
+                goto fail;
+        }
+
+        // Coverity doesn't realize that validate_packet() checks the packet data for valid values
+        // coverity[tainted_scalar]
+        rval = validate_packet(data, size, args.dir,
+            args.asd.get_odp_ctxt().dns_host_reporting, args.asd, args.change_bits);
+        if (rval != APPID_SUCCESS)
+            goto tcp_done;
+
+        if (dd->state == DNS_STATE_QUERY || dd->state == DNS_STATE_MULTI_QUERY)
+        {
+            if (args.dir != APP_ID_FROM_INITIATOR)
+                goto fail;
+            dd->id = ((const DNSHeader*)data)->id;
+            DNSState current_state = dd->state;
+            dd->state = DNS_STATE_RESPONSE;
+            if (current_state == DNS_STATE_QUERY)
+                goto inprocess;
+            goto success;
+        }
+        else if (args.dir == APP_ID_FROM_RESPONDER && dd->id == ((const DNSHeader*)data)->id)
+            dd->state = DNS_STATE_MULTI_QUERY;
+        else
+            goto fail;
+    }
+
+tcp_done:
+    switch (rval)
+    {
+    case APPID_SUCCESS:
+        goto success;
+    case APPID_INVALID_CLIENT:
+        goto not_compatible;
+    case APPID_NOMATCH:
+        goto fail;
+    case APPID_INPROCESS:
+        goto inprocess;
+    default:
+        dd->free_dns_cache();
+        return rval;
+    }
+
+success:
+    if (reallocated_data)
+        snort_free(reallocated_data);
+    args.asd.set_session_flags(APPID_SESSION_CONTINUE);
+    return APPID_SUCCESS;
+
+not_compatible:
+    if (reallocated_data)
+        snort_free(reallocated_data);
+    return APPID_NOT_COMPATIBLE;
+
+fail:
+    if (reallocated_data)
+        snort_free(reallocated_data);
+    return APPID_NOMATCH;
+
+inprocess:
+    if (reallocated_data)
+        snort_free(reallocated_data);
+    return APPID_INPROCESS;
+
+}
 
 int DnsTcpServiceDetector::validate(AppIdDiscoveryArgs& args)
 {
index 5053892f49c3e6123520fdd023062b3e37a9266d..529cf5ca41c518bb1bf8de4f063b35cf02bdce15 100644 (file)
@@ -49,6 +49,7 @@ public:
     DnsTcpServiceDetector(ServiceDiscovery*);
 
     int validate(AppIdDiscoveryArgs&) override;
+    int validate_doq(AppIdDiscoveryArgs& args);
 };
 
 class DnsUdpServiceDetector : public ServiceDetector, public DnsValidator
@@ -57,6 +58,7 @@ public:
     DnsUdpServiceDetector(ServiceDiscovery*);
 
     int validate(AppIdDiscoveryArgs&) override;
+    int validate_doh(AppIdDiscoveryArgs&);
 };
 #endif
 
index 4852e9173c84108ac324550044b5190391ae0aec..20f3572602cfb8579111d35fc57050e491a4918c 100644 (file)
@@ -17,3 +17,7 @@ add_cpputest( detector_sip_test
 add_cpputest( detector_pop3_test
     SOURCES
         ../../../../utils/util_cstring.cc )
+
+add_cpputest( detector_dns_test
+    SOURCES
+        ../../../../utils/util_cstring.cc )
\ No newline at end of file
diff --git a/src/network_inspectors/appid/detector_plugins/test/detector_dns_test.cc b/src/network_inspectors/appid/detector_plugins/test/detector_dns_test.cc
new file mode 100644 (file)
index 0000000..9c080c6
--- /dev/null
@@ -0,0 +1,238 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// detector_dns_test.cc author Shibin k v <shikv@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "appid_inspector.h"
+#include "appid_session.h"
+#include "detector_plugins_mock.h"
+
+#include "../detector_dns.h"
+#include "../detector_dns.cc"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+using namespace snort;
+
+static ServiceDiscovery test_discovery;
+
+static AppIdDnsSession static_dns_session;
+
+AppIdDnsSession* AppIdSession::get_dns_session() const
+{
+    return &static_dns_session;
+}
+
+AppIdDnsSession* AppIdSession::create_dns_session()
+{
+    return &static_dns_session;
+}
+
+int ServiceDetector::incompatible_data(AppIdSession&, const snort::Packet*, AppidSessionDirection)
+{
+    return 0;
+}
+// Stubs for AppIdInspector
+static ServiceDNSData dd;
+AppIdConfig test_app_config;
+
+AppIdInspector::AppIdInspector(AppIdModule&) : config(&test_app_config), ctxt(test_app_config) { }
+
+void AppIdDetector::add_user(AppIdSession&, const char*, AppId, bool, AppidChangeBits&){}
+
+int AppIdDetector::data_add(AppIdSession&, AppIdFlowData*) { return 1; }
+
+AppIdFlowData* AppIdDetector::data_get(const AppIdSession&)
+{
+    return &dd;
+}
+
+int ServiceDetector::fail_service(AppIdSession& asd, const Packet* pkt, AppidSessionDirection dir) { return 1; }
+
+TEST_GROUP(detector_dns_doq_tests)
+{
+    DnsTcpServiceDetector* test_detector = nullptr;
+    void setup() override
+    {
+        test_detector = new DnsTcpServiceDetector(&test_discovery);
+    }
+
+    void teardown() override
+    {   
+        delete test_detector;
+        test_detector = nullptr;
+        dd.free_dns_cache();
+    }
+};
+
+TEST(detector_dns_doq_tests, doq_validator_match_full_session)
+{
+    OdpContext test_odp_ctxt(test_app_config, nullptr);
+    AppIdModule test_module;
+    AppIdInspector test_inspector(test_module);
+    dd.state = DNS_STATE_QUERY;
+    AppIdSession test_asd(IpProtocol::TCP, nullptr, (uint16_t)0, test_inspector, test_odp_ctxt, (uint32_t)0, 0);
+        uint8_t dns_tcp_packet[] = {
+        0x00, 0x1c, // TCP length (28 bytes)
+        0x12, 0x34, // id
+        0x01, 0x00, // flags
+        0x00, 0x01, // QDCount
+        0x00, 0x00, // ANCount
+        0x00, 0x00, // NSCount
+        0x00, 0x00, // ARCount
+        0x03, 'w', 'w', 'w', 0x00, // "www"
+        0x00, 0x01, // QType A
+        0x00, 0x01  // QClass IN
+    };
+
+    AppidChangeBits change_bits;
+    AppIdDiscoveryArgs args(dns_tcp_packet, sizeof(dns_tcp_packet), APP_ID_FROM_INITIATOR, test_asd, nullptr, change_bits);
+    auto result = test_detector->validate_doq(args);
+    dd.free_dns_cache();
+    CHECK_EQUAL(APPID_INPROCESS, result);
+
+    // Now send a response packet
+    dd.state = DNS_STATE_RESPONSE;
+    dd.id = 13330;
+    uint8_t dns_tcp_response[] = {
+        0x00, 0x25,             // TCP length (37 bytes)
+        0x12, 0x34,             // id
+        0x81, 0x80,             // flags: standard response, no error
+        0x00, 0x01,             // QDCount: 1
+        0x00, 0x01,             // ANCount: 1
+        0x00, 0x00,             // NSCount: 0
+        0x00, 0x00,             // ARCount: 0
+        // Query: www
+        0x03, 'w', 'w', 'w', 0x00, // QNAME: "www"
+        0x00, 0x01,             // QType: A
+        0x00, 0x01,             // QClass: IN
+        // Answer
+        0xc0, 0x0c,             // Name (pointer to offset 12)
+        0x00, 0x01,             // Type: A
+        0x00, 0x01,             // Class: IN
+        0x00, 0x00, 0x00, 0x3c, // TTL: 60
+        0x00, 0x04,             // RDLENGTH: 4
+        0x7f, 0x00, 0x00, 0x01  // RDATA: 127.0.0.1
+    };
+
+    AppIdDiscoveryArgs response_args(dns_tcp_response, sizeof(dns_tcp_response), APP_ID_FROM_RESPONDER, test_asd, nullptr, change_bits);
+    result = test_detector->validate_doq(response_args);
+
+    CHECK_EQUAL(APPID_SUCCESS, result);
+}
+
+TEST(detector_dns_doq_tests, doq_validator_in_process_cached)
+{
+    OdpContext test_odp_ctxt(test_app_config, nullptr);
+    AppIdModule test_module;
+    AppIdInspector test_inspector(test_module);
+    dd.state = DNS_STATE_QUERY;
+    AppIdSession test_asd(IpProtocol::TCP, nullptr, (uint16_t)0, test_inspector, test_odp_ctxt, (uint32_t)0, 0);
+    // partial data
+    uint8_t dns_tcp_packet_1[] = {
+        0x00, 0x1c, // TCP length (28 bytes)
+        0x12, 0x34, // id
+        0x01, 0x00, // flags
+    };
+
+    AppidChangeBits change_bits;
+    AppIdDiscoveryArgs args(dns_tcp_packet_1, sizeof(dns_tcp_packet_1), APP_ID_FROM_INITIATOR, test_asd, nullptr, change_bits);
+    auto result = test_detector->validate_doq(args);
+    CHECK_EQUAL(6, dd.cached_len);
+    CHECK_EQUAL(APPID_INPROCESS, result);
+
+    uint8_t dns_tcp_packet_2[]= {
+        0x00, 0x01, // QDCount
+        0x00, 0x00, // ANCount
+        0x00, 0x00, // NSCount
+        0x00, 0x00, // ARCount
+        0x03, 'w', 'w', 'w', 0x00, // "www"
+        0x00, 0x01, // QType A
+        0x00, 0x01  // QClass IN
+    };
+    AppIdDiscoveryArgs args2(dns_tcp_packet_2, sizeof(dns_tcp_packet_2), APP_ID_FROM_INITIATOR, test_asd, nullptr, change_bits);
+    result = test_detector->validate_doq(args2);
+    CHECK_EQUAL(APPID_INPROCESS, result);
+}
+
+TEST(detector_dns_doq_tests, doq_validator_not_compatible)
+{
+    OdpContext test_odp_ctxt(test_app_config, nullptr);
+    AppIdModule test_module;
+    AppIdInspector test_inspector(test_module);
+    dd.state = DNS_STATE_QUERY;
+    AppIdSession test_asd(IpProtocol::TCP, nullptr, (uint16_t)0, test_inspector, test_odp_ctxt, (uint32_t)0, 0);
+        uint8_t dns_tcp_packet[] = {
+        0x00, 0x01, // TCP length (1 bytes)
+        0x12, 0x34, // id
+        0x01, 0x00, // flags
+        0x00, 0x01, // QDCount
+        0x00, 0x00, // ANCount
+        0x00, 0x00, // NSCount
+        0x00, 0x00, // ARCount
+        0x03, 'w', 'w', 'w', 0x00, // "www"
+        0x00, 0x01, // QType A
+        0x00, 0x01  // QClass IN
+    };
+
+    AppidChangeBits change_bits;
+    AppIdDiscoveryArgs args(dns_tcp_packet, sizeof(dns_tcp_packet), APP_ID_FROM_INITIATOR, test_asd, nullptr, change_bits);
+    auto result = test_detector->validate_doq(args);
+    CHECK_EQUAL(APPID_NOT_COMPATIBLE, result);
+
+}
+
+TEST(detector_dns_doq_tests, doq_validator_no_match)
+{
+    OdpContext test_odp_ctxt(test_app_config, nullptr);
+    AppIdModule test_module;
+    AppIdInspector test_inspector(test_module);
+    dd.state = DNS_STATE_QUERY;
+    AppIdSession test_asd(IpProtocol::TCP, nullptr, (uint16_t)0, test_inspector, test_odp_ctxt, (uint32_t)0, 0);
+        uint8_t dns_tcp_packet[] = {
+        0x00, 0x10, // TCP length (28 bytes)
+        0x12, 0x34, // id
+        0x01, 0x00, // flags
+        0x00, 0x01, // QDCount
+        0x00, 0x00, // ANCount
+        0x00, 0x00, // NSCount
+        0x00, 0x00, // ARCount
+        0x03, 'w', 'w', 'w', 0x00, // "www"
+        0x00, 0x01, // QType A
+        0x00, 0x01  // QClass IN
+    };
+
+    AppidChangeBits change_bits;
+    AppIdDiscoveryArgs args(dns_tcp_packet, sizeof(dns_tcp_packet), APP_ID_FROM_RESPONDER, test_asd, nullptr, change_bits);
+    auto result = test_detector->validate_doq(args);
+    CHECK_EQUAL(APPID_NOMATCH, result);
+}
+
+
+
+int main(int argc, char** argv)
+{
+    int return_value = CommandLineTestRunner::RunAllTests(argc, argv);
+    return return_value;
+}
index d5bcf597f31d14127d6dd5e177dc030fd4eb7601..a6b0c55828a4ba14bd330b12f0f89f85610dba30 100644 (file)
@@ -38,6 +38,7 @@ set (PUB_SUB_INCLUDES
     ssh_events.h
     ssl_events.h
     stream_event_ids.h
+    dns_payload_event.h
 )
 
 add_library( pub_sub OBJECT
diff --git a/src/pub_sub/dns_payload_event.h b/src/pub_sub/dns_payload_event.h
new file mode 100644 (file)
index 0000000..b5538e0
--- /dev/null
@@ -0,0 +1,75 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2022-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.
+//--------------------------------------------------------------------------
+// dns_payload_event.h author Shibin k v <shikv@cisco.com>
+#ifndef DNS_PAYLOAD_EVENT_H
+#define DNS_PAYLOAD_EVENT_H
+
+#include "flow/flow_stash.h"
+#include "framework/data_bus.h"
+
+#define STASH_DNS_DATA "dns_data"
+
+class DnsDataStash : public snort::StashGenericObject
+{
+public:
+    DnsDataStash(const uint8_t* data, int32_t length)
+        : dns_data(data), dns_data_length(length)
+    { }
+    DnsDataStash() = delete;
+    const uint8_t* get_data(uint32_t& length) const
+    {
+        length = dns_data_length;
+        return dns_data;
+    }
+    ~DnsDataStash() override = default;
+
+private:
+    const uint8_t* dns_data;
+    int32_t dns_data_length;
+};
+
+class SO_PUBLIC DnsPayloadEvent : public snort::DataEvent
+{
+public:
+    DnsPayloadEvent(const uint8_t* dns_data, const int32_t dns_data_length,
+        const bool from_client, const bool is_udp, const bool last_piece)
+        : dns_data(dns_data), dns_data_length(dns_data_length),
+        from_client(from_client), is_udp(is_udp), last_piece(last_piece)
+    { }
+    DnsPayloadEvent() = delete;
+    const uint8_t* get_payload(int32_t& length) const
+    {
+        length = dns_data_length;
+        return dns_data;
+    }
+    bool is_from_client() const
+    { return from_client; }
+    bool is_last_piece() const
+    { return last_piece; }
+    bool is_dns_udp() const
+    { return is_udp; }
+
+private:
+    const uint8_t* dns_data;
+    const int32_t dns_data_length;
+    const bool from_client;
+    const bool is_udp;
+    const bool last_piece;
+};
+
+#endif // DNS_PAYLOAD_EVENT_H
index febbb55e54ef94cbfe7fc799ffc543e6025dc33c..2b286f7e6055df855bb39aca06da910f749704b9 100644 (file)
@@ -36,6 +36,7 @@ struct HttpEventIds
     RESPONSE_HEADER,
     REQUEST_BODY,
     BODY,
+    DOH_BODY,
     END_OF_TRANSACTION,
     HTTP_PUBLISH_LENGTH,
 
index 9fd95fc04ebd109ea96172ed4dcacd97990fc57a..aab4c206a20e4b8751f6896dc41a1aecef08efc7 100644 (file)
@@ -61,6 +61,8 @@ struct IntrinsicEventIds
     AUXILIARY_IP,
     FILE_VERDICT,
 
+    DNS_PAYLOAD,
+
     num_ids
 }; };
 
index 5c23539a7f4a62db5c2dc3798167cacdc36a9eb8..96eb4507d3a690ec0baef451b622648ced04a5ef 100644 (file)
@@ -151,7 +151,7 @@ TEST(pub_sub_http_request_body_event_test, last_event)
 {
     int32_t msg_len = 500;
     int32_t max_pub_len = 2000;
-    int32_t in_offset = REQUEST_PUBLISH_DEPTH - msg_len;
+    int32_t in_offset = BODY_PUBLISH_DEPTH - msg_len;
     int32_t length, offset;
     uint32_t stream_id = 3;
     mock().setData("stream_id", stream_id);
index cc32d6af8fccad8912a927d24eea5d8058d1a4ed..1fb89ae6b26f7422e36779dfe48decd94ccc6d95 100644 (file)
@@ -9,6 +9,8 @@ set( FILE_LIST
     dns_rr_decoder.cc
     dns_splitter.cc
     dns_splitter.h
+    dns_payload_event_handler.cc
+    dns_payload_event_handler.h
 )
 
 # if (STATIC_INSPECTORS)
index 57c51a2e046fe37261cc7d3ce215bc4a2944f99e..c86e71521b2aa3c7c11b91de2893baa9dc65117f 100644 (file)
 #include "dns.h"
 
 #include "detection/detection_engine.h"
-#include "dns_config.h"
 #include "log/messages.h"
 #include "profiler/profiler.h"
+#include "pub_sub/dns_events.h"
+#include "pub_sub/intrinsic_event_ids.h"
 #include "stream/stream.h"
 
+#include "dns_config.h"
 #include "dns_module.h"
+#include "dns_payload_event_handler.h"
 #include "dns_splitter.h"
-#include "pub_sub/dns_events.h"
 
 using namespace snort;
 
@@ -56,11 +58,6 @@ const PegInfo dns_peg_names[] =
     { CountType::END, nullptr, nullptr }
 };
 
-/*
- * Function prototype(s)
- */
-static void snort_dns(Packet* p, const DnsConfig* dns_config);
-
 unsigned DnsFlowData::inspector_id = 0;
 
 DnsFlowData::DnsFlowData() : FlowData(inspector_id)
@@ -90,11 +87,11 @@ bool DNSData::has_events() const
     return !dns_events.empty();
 }
 
-static DNSData* SetNewDNSData(Packet* p)
+static DNSData* SetNewDNSData(Packet* p, bool udp)
 {
     DnsFlowData* fd;
 
-    if (p->is_udp())
+    if (udp)
         return nullptr;
 
     fd = new DnsFlowData;
@@ -126,10 +123,10 @@ bool DNSData::valid_dns(const DNSHdr& dns_header) const
     return true;
 }
 
-DNSData* get_dns_session_data(Packet* p, bool from_server, DNSData& udpSessionData)
+DNSData* get_dns_session_data(Packet* p, bool from_server, DNSData& udpSessionData, bool udp)
 {
     DnsFlowData* fd;
-    if (p->is_udp())
+    if (udp)
     {
         if(p->dsize > MAX_UDP_PAYLOAD)
             return nullptr;
@@ -622,6 +619,53 @@ static uint16_t ParseDNSAnswer(
     return bytes_unused;
 }
 
+// Get the DNS transaction ID from a UDP packet's data field
+static inline uint16_t get_udp_trans_id(Packet* p)
+{
+    // The length of packet's data field should have already been validated
+    return (static_cast<uint16_t>(p->data[0]) << 8) | static_cast<uint16_t>(p->data[1]);
+}
+
+// Check if the DNS transaction ID is found in the UDP packet's flow data object
+static bool is_in_udp_flow(Packet* p, uint16_t trans_id)
+{
+    bool found = false;
+    DnsUdpFlowData* udp_flow_data = (DnsUdpFlowData*)((p->flow)->get_flow_data(DnsUdpFlowData::inspector_id));
+    if (udp_flow_data)
+        found = udp_flow_data->trans_ids.find(trans_id) != udp_flow_data->trans_ids.end();
+    return found;
+}
+
+// Add DNS transaction ID to the UDP packet's flow data object
+static void add_to_udp_flow(Packet* p, uint16_t trans_id)
+{
+    DnsUdpFlowData* udp_flow_data = (DnsUdpFlowData*)((p->flow)->get_flow_data(DnsUdpFlowData::inspector_id));
+    if (!udp_flow_data)
+    {
+        udp_flow_data = new DnsUdpFlowData();
+        p->flow->set_flow_data(udp_flow_data);
+    }
+    udp_flow_data->trans_ids.emplace(trans_id);
+}
+
+// Remove DNS transaction ID from the UDP packet's flow data object
+static void rm_from_udp_flow(Packet* p, uint16_t trans_id)
+{
+    DnsUdpFlowData* udp_flow_data = (DnsUdpFlowData*)((p->flow)->get_flow_data(DnsUdpFlowData::inspector_id));
+    bool should_close = true;
+    if (udp_flow_data)
+    {
+        udp_flow_data->trans_ids.erase(trans_id);
+        should_close = udp_flow_data->trans_ids.empty();
+    }
+    if (should_close && !p->flow->is_proxied())
+    {
+        // Mark the UDP flow as "closed" only when all trans_ids are matched
+        // and removed by DNS-reply packets, or if the flow data object is not found
+        p->flow->session_state |= STREAM_STATE_CLOSED;
+    }
+}
+
 /* The following check is to look for an attempt to exploit
  * a vulnerability in the DNS client, per MS 06-041.
  *
@@ -806,7 +850,7 @@ static uint16_t ParseDNSRData(
     return bytes_unused;
 }
 
-static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& needNextPacket)
+static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& needNextPacket, bool udp)
 {
     uint16_t bytes_unused = p->dsize;
     int i;
@@ -839,7 +883,7 @@ static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& ne
              * if at beginning of a UDP Response.
              */
             if ((dnsSessionData->state == DNS_RESP_STATE_LENGTH) &&
-                p->is_udp())
+                udp)
             {
                 dnsSessionData->state = DNS_RESP_STATE_HDR_ID;
             }
@@ -1082,26 +1126,6 @@ void DnsResponseFqdn::update_ttl(uint32_t ttl)
 // class stuff
 //-------------------------------------------------------------------------
 
-class Dns : public Inspector
-{
-public:
-    Dns(DnsModule*);
-    ~Dns() override;
-
-    void eval(Packet*) override;
-    StreamSplitter* get_splitter(bool) override;
-    bool configure(snort::SnortConfig*) override;
-    void show(const snort::SnortConfig*) const override;
-    static unsigned get_pub_id() { return pub_id; }
-
-    bool supports_no_ips() const override
-    { return true; }
-
-private:
-    const DnsConfig* config = nullptr;
-    static unsigned pub_id;
-};
-
 unsigned Dns::pub_id = 0;
 
 Dns::Dns(DnsModule* m)
@@ -1120,83 +1144,14 @@ void Dns::show(const SnortConfig*) const
     config->show();
 }
 
-void Dns::eval(Packet* p)
-{
-    // precondition - what we registered for
-    assert((p->is_udp() and p->dsize and p->data) or p->has_tcp_data() or p->has_udp_quic_data());
-    assert(p->flow);
-
-    ++dnsstats.packets;
-    snort_dns(p, config);
-}
-
-bool Dns::configure(snort::SnortConfig*)
-{
-    if ( !pub_id )
-        pub_id = DataBus::get_id(dns_pub_key);
-
-    return true;
-}
-
-StreamSplitter* Dns::get_splitter(bool c2s)
-{
-    return new DnsSplitter(c2s);
-}
-
-// Get the DNS transaction ID from a UDP packet's data field
-static inline uint16_t get_udp_trans_id(Packet* p)
-{
-    // The length of packet's data field should have already been validated
-    return (static_cast<uint16_t>(p->data[0]) << 8) | static_cast<uint16_t>(p->data[1]);
-}
-
-// Add DNS transaction ID to the UDP packet's flow data object
-static void add_to_udp_flow(Packet* p, uint16_t trans_id)
-{
-    DnsUdpFlowData* udp_flow_data = (DnsUdpFlowData*)((p->flow)->get_flow_data(DnsUdpFlowData::inspector_id));
-    if (!udp_flow_data)
-    {
-        udp_flow_data = new DnsUdpFlowData();
-        p->flow->set_flow_data(udp_flow_data);
-    }
-    udp_flow_data->trans_ids.emplace(trans_id);
-}
-
-// Check if the DNS transaction ID is found in the UDP packet's flow data object
-static bool is_in_udp_flow(Packet* p, uint16_t trans_id)
-{
-    bool found = false;
-    DnsUdpFlowData* udp_flow_data = (DnsUdpFlowData*)((p->flow)->get_flow_data(DnsUdpFlowData::inspector_id));
-    if (udp_flow_data)
-        found = udp_flow_data->trans_ids.find(trans_id) != udp_flow_data->trans_ids.end();
-    return found;
-}
-
-// Remove DNS transaction ID from the UDP packet's flow data object
-static void rm_from_udp_flow(Packet* p, uint16_t trans_id)
-{
-    DnsUdpFlowData* udp_flow_data = (DnsUdpFlowData*)((p->flow)->get_flow_data(DnsUdpFlowData::inspector_id));
-    bool should_close = true;
-    if (udp_flow_data)
-    {
-        udp_flow_data->trans_ids.erase(trans_id);
-        should_close = udp_flow_data->trans_ids.empty();
-    }
-    if (should_close)
-    {
-        // Mark the UDP flow as "closed" only when all trans_ids are matched
-        // and removed by DNS-reply packets, or if the flow data object is not found
-        p->flow->session_state |= STREAM_STATE_CLOSED;
-    }
-}
-
-static void snort_dns(Packet* p, const DnsConfig* dns_config)
+void Dns::snort_dns(Packet* p, bool udp)
 {
     // cppcheck-suppress unreadVariable
     Profile profile(dnsPerfStats);
 
+    ++dnsstats.packets;
     // For TCP, do a few extra checks...
-    if ( p->has_tcp_data() )
+    if (!udp)
     {
         // If session picked up mid-stream, do not process further.
         // Would be almost impossible to tell where we are in the
@@ -1213,13 +1168,13 @@ static void snort_dns(Packet* p, const DnsConfig* dns_config)
 
     DNSData udp_session_data;
     // Attempt to get a previously allocated DNS block.
-    DNSData* dnsSessionData = get_dns_session_data(p, from_server, udp_session_data);
+    DNSData* dnsSessionData = get_dns_session_data(p, from_server, udp_session_data, udp);
 
     if (dnsSessionData == nullptr)
     {
         // Check the stream session. If it does not currently
         // have our DNS data-block attached, create one.
-        dnsSessionData = SetNewDNSData(p);
+        dnsSessionData = SetNewDNSData(p, udp);
 
         if ( !dnsSessionData )
             // Could not get/create the session data for this packet.
@@ -1229,13 +1184,13 @@ static void snort_dns(Packet* p, const DnsConfig* dns_config)
     if (dnsSessionData->flags & DNS_FLAG_NOT_DNS)
         return;
 
-    dnsSessionData->dns_config = dns_config;
+    dnsSessionData->dns_config = config;
     if ( from_server )
     {
         uint16_t trans_id = 0;
         // Always parse the response packet for TCP flows
         bool should_parse_response = true;
-        if (p->is_udp())
+        if (udp)
         {
             // If this is a DNS-over-UDP flow then parse the response packet and publish events
             // only when the response packet's DNS transaction-ID is found in the flow data object
@@ -1246,7 +1201,7 @@ static void snort_dns(Packet* p, const DnsConfig* dns_config)
         if (should_parse_response)
         {
             bool needNextPacket = false;
-            ParseDNSResponseMessage(p, dnsSessionData, needNextPacket);
+            ParseDNSResponseMessage(p, dnsSessionData, needNextPacket, udp);
             trans_id = dnsSessionData->hdr.id;
 
             if (!dnsSessionData->valid_dns(dnsSessionData->hdr))
@@ -1262,17 +1217,42 @@ static void snort_dns(Packet* p, const DnsConfig* dns_config)
             DataBus::publish(Dns::get_pub_id(), DnsEventIds::DNS_RESPONSE, dns_response_event, p->flow);
         }
 
-        if (p->is_udp())
+        if (udp)
             rm_from_udp_flow(p, trans_id);
     }
     else
     {
         dnsstats.requests++;
-        if (p->is_udp())
+        if (udp)
             add_to_udp_flow(p, get_udp_trans_id(p));
     }
 }
 
+void Dns::eval(Packet* p)
+{
+    // precondition - what we registered for
+    assert((p->is_udp() and p->dsize and p->data) or p->has_tcp_data() or p->has_udp_quic_data());
+    assert(p->flow);
+    if (p->has_udp_quic_data()) // DNS over QUIC follows DNS over TCP
+        snort_dns(p, false);
+    else
+        snort_dns(p, p->is_udp());
+}
+
+bool Dns::configure(snort::SnortConfig*)
+{
+    if ( !pub_id )
+        pub_id = DataBus::get_id(dns_pub_key);
+
+    DataBus::subscribe(intrinsic_pub_key, IntrinsicEventIds::DNS_PAYLOAD, new DnsPayloadEventHandler(*this));
+    return true;
+}
+
+StreamSplitter* Dns::get_splitter(bool c2s)
+{
+    return new DnsSplitter(c2s);
+}
+
 //-------------------------------------------------------------------------
 // api stuff
 //-------------------------------------------------------------------------
index 5a954607fb990ce02ec1d56c87a5b90183a7f9c8..916a29c044eba7796d641e2a564cf814316a091f 100644 (file)
 #include <set>
 
 #include "flow/flow.h"
-
 #include "protocols/packet.h"
 #include "pub_sub/dns_events.h"
 
+#include "dns_module.h"
+
 // Implementation header with definitions, datatypes and flowdata class for
 // DNS service inspector.
 
@@ -183,9 +184,12 @@ struct DNSNameState
 #define DNS_RESP_STATE_AUTH_RR          0x50
 #define DNS_RESP_STATE_ADD_RR           0x60
 
+
 class DnsConfig;
 struct DNSData;
 
+DNSData* get_dns_session_data(snort::Packet* p, bool from_server, DNSData& udpSessionData, bool udp);
+
 class DnsResponseFqdn
 {
 public:
@@ -246,8 +250,6 @@ struct DNSData
     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);
-
 class DnsResponseIp
 {
 public:
@@ -294,5 +296,31 @@ public:
     std::set<uint16_t> trans_ids;
 };
 
+class Dns : public snort::Inspector
+{
+public:
+    Dns(DnsModule*);
+    ~Dns() override;
+
+    void eval(snort::Packet*) override;
+    snort::StreamSplitter* get_splitter(bool) override;
+    bool configure(snort::SnortConfig*) override;
+    void show(const snort::SnortConfig*) const override;
+    static unsigned get_pub_id()
+    { return pub_id; }
+
+    bool supports_no_ips() const override
+    { return true; }
+
+    void snort_dns(snort::Packet* p, bool udp);
+
+    const DnsConfig* get_config() const
+    { return config; }
+
+private:
+    const DnsConfig* config = nullptr;
+    static unsigned pub_id;
+};
+
 #endif
 
diff --git a/src/service_inspectors/dns/dns_payload_event_handler.cc b/src/service_inspectors/dns/dns_payload_event_handler.cc
new file mode 100644 (file)
index 0000000..65e2c58
--- /dev/null
@@ -0,0 +1,66 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// dns_payload_event_handler.cc author Shibin K V <shikv@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dns_payload_event_handler.h"
+
+#include "detection/detection_engine.h"
+#include "pub_sub/dns_payload_event.h"
+
+#include "dns.h"
+
+using namespace snort;
+
+void DnsPayloadEventHandler::handle(DataEvent& event, Flow* flow)
+{
+    Packet *p = DetectionEngine::get_current_wire_packet();
+    if (!flow or !p )
+        return;
+    DnsPayloadEvent* dns_payload_event = (DnsPayloadEvent*)&event;
+    int32_t payload_length = 0;
+    const uint8_t* dns_payload = dns_payload_event->get_payload(payload_length);
+    bool is_udp = dns_payload_event->is_dns_udp();
+
+    if (!dns_payload or payload_length <= 0)
+        return;
+
+    const uint8_t* old_data = p->data;
+    const uint32_t old_dsize = p->dsize;
+    SnortProtocolId old_protocol_id = p->flow->ssn_state.snort_protocol_id;
+
+    {
+        p->data = dns_payload;
+        p->dsize = payload_length;
+        p->flow->ssn_state.snort_protocol_id = inspector.get_service();
+        p->packet_flags |= PKT_ALLOW_MULTIPLE_DETECT;
+        DetectionEngine::detect(p);
+    }
+
+    if (is_udp)
+        static_cast<Dns&>(inspector).snort_dns(p, true);
+
+    p->data = old_data;
+    p->dsize = old_dsize;
+    p->flow->ssn_state.snort_protocol_id = old_protocol_id;
+
+}
diff --git a/src/service_inspectors/dns/dns_payload_event_handler.h b/src/service_inspectors/dns/dns_payload_event_handler.h
new file mode 100644 (file)
index 0000000..e064dee
--- /dev/null
@@ -0,0 +1,42 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// dns_payload_event_handler.h author Shibin K V <shikv@cisco.com>
+
+#ifndef DNS_PAYLOAD_EVENT_HANDLER_H
+#define DNS_PAYLOAD_EVENT_HANDLER_H
+
+#include "framework/data_bus.h"
+
+#include "dns.h"
+#include "dns_module.h"
+
+class DnsPayloadEventHandler : public snort::DataHandler
+{
+public:
+    DnsPayloadEventHandler(snort::Inspector& inspector_) : 
+        snort::DataHandler(DNS_NAME), inspector(inspector_)
+    {
+        order = 200;
+    }
+    void handle(snort::DataEvent& event, snort::Flow* flow) override;
+private:
+    snort::Inspector& inspector;
+};
+
+#endif // DNS_PAYLOAD_EVENT_HANDLER_H
index 97133d804818858c2136daa55669941bbcb04f6d..78678b27c2d3b1f46317a8199343c0ca9ba10ba4 100644 (file)
@@ -39,11 +39,9 @@ StreamSplitter::Status DnsSplitter::scan(
 {
     assert(len > 0);
 
-    DNSData udp_session_data;
-    bool from_server = p->is_from_server();
-    DNSData* dnsSessionData = get_dns_session_data(p, from_server, udp_session_data);
-
-    if ( dnsSessionData and ( dnsSessionData->flags & DNS_FLAG_NOT_DNS ) )
+    DnsFlowData* fd;
+    fd = (DnsFlowData*)((p->flow)->get_flow_data(DnsFlowData::inspector_id));
+    if (fd && (fd->session.flags & DNS_FLAG_NOT_DNS))
     {
         dnsstats.aborted_sessions++;
         return ABORT;
index cae6c464345669ba2b884d60e6f5838a1df4b43a..edbeb4db8464d7f8ed54e6520147f2132ede2585 100755 (executable)
@@ -29,7 +29,7 @@ namespace HttpEnums
 static const int MAX_OCTETS = 63780;
 static const int GZIP_BLOCK_SIZE = 2048;
 static const int MAX_SECTION_STRETCH = 1460;
-static const int REQUEST_PUBLISH_DEPTH = 2000;
+static const int BODY_PUBLISH_DEPTH = 2000;
 
 static const uint32_t HTTP_GID = 119;
 static const int GZIP_WINDOW_BITS = 31;
@@ -128,7 +128,7 @@ enum Contentcoding { CONTENTCODE__OTHER=1, CONTENTCODE_GZIP, CONTENTCODE_DEFLATE
     CONTENTCODE_XPRESS, CONTENTCODE_XZ };
 
 // Content media-types (MIME types)
-enum ContentType { CT__OTHER=1, CT_APPLICATION_PDF, CT_APPLICATION_OCTET_STREAM,
+enum ContentType { CT__OTHER=1, CT_APPLICATION_DNS, CT_APPLICATION_PDF, CT_APPLICATION_OCTET_STREAM,
     CT_APPLICATION_JAVASCRIPT, CT_APPLICATION_ECMASCRIPT, CT_APPLICATION_X_JAVASCRIPT,
     CT_APPLICATION_X_ECMASCRIPT, CT_APPLICATION_XHTML_XML, CT_TEXT_JAVASCRIPT,
     CT_TEXT_JAVASCRIPT_1_0, CT_TEXT_JAVASCRIPT_1_1, CT_TEXT_JAVASCRIPT_1_2, CT_TEXT_JAVASCRIPT_1_3,
index 79c976ef85a19ab21eb68f7364a9d6a3a6ca2ae9..8948001e256a6fe4bcfd6530eac27555aafe397d 100644 (file)
 #include "helpers/buffer_data.h"
 #include "http_module.h"
 #include "js_norm/js_enum.h"
+#include "pub_sub/dns_payload_event.h"
 #include "pub_sub/http_request_body_event.h"
 #include "pub_sub/http_body_event.h"
+#include "pub_sub/intrinsic_event_ids.h"
 
 #include "http_api.h"
 #include "http_common.h"
@@ -112,20 +114,20 @@ void HttpMsgBody::publish(unsigned pub_id)
     }
 
     // Publish request body limited statically
-    if (params->publish_request_body and is_request and (publish_octets < REQUEST_PUBLISH_DEPTH))
+    if (params->publish_request_body and is_request and (publish_octets < BODY_PUBLISH_DEPTH))
     {
         // Exclude already published octets (publish_octets):
-        const auto request_publish_depth_remaining = REQUEST_PUBLISH_DEPTH - publish_octets;
+        const auto request_publish_depth_remaining = BODY_PUBLISH_DEPTH - publish_octets;
 
         // We should not publish more than the remaining publish depth:
         auto request_publish_length = (publish_length > request_publish_depth_remaining) ?
             request_publish_depth_remaining : publish_length;
 
-        // If it is not the last piece of the request, it should be marked as such because of REQUEST_PUBLISH_DEPTH limit:
+        // If it is not the last piece of the request, it should be marked as such because of BODY_PUBLISH_DEPTH limit:
         // if sum of already published octets (publish_octets) and current publishing length (request_publish_length)
         // is greater than the request publish depth.
         auto request_last_piece = last_piece ?
-            true : (publish_octets + request_publish_length >= REQUEST_PUBLISH_DEPTH);
+            true : (publish_octets + request_publish_length >= BODY_PUBLISH_DEPTH);
 
         HttpRequestBodyEvent http_request_body_event(this, request_publish_length, publish_octets, request_last_piece, session_data);
         DataBus::publish(pub_id, HttpEventIds::REQUEST_BODY, http_request_body_event, flow);
@@ -140,6 +142,30 @@ void HttpMsgBody::publish(unsigned pub_id)
     #endif
     }
 
+    // publish DOH body if applicable
+    if (get_header(source_id) and (get_header(source_id)->get_content_type() == CT_APPLICATION_DNS) 
+        and (publish_octets < BODY_PUBLISH_DEPTH))
+    {
+        const auto doh_publish_depth_remaining = BODY_PUBLISH_DEPTH - publish_octets;        
+        auto doh_publish_length = (publish_length > doh_publish_depth_remaining) ?
+            doh_publish_depth_remaining : publish_length;
+        auto doh_last_piece = last_piece ? true : (publish_octets + doh_publish_length >= BODY_PUBLISH_DEPTH);
+        DnsPayloadEvent dns_payload_event(msg_text_new.start(), publish_length, is_request,
+            true, doh_last_piece);
+        DnsDataStash* stash = new DnsDataStash(msg_text_new.start(), publish_length);
+        flow->set_attr(STASH_DNS_DATA, stash);
+        DataBus::publish(intrinsic_pub_id, IntrinsicEventIds::DNS_PAYLOAD, dns_payload_event, flow);
+    #ifdef REG_TEST
+        if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP))
+        {
+            fprintf(HttpTestManager::get_output_file(),
+                "Published %" PRId32 " bytes of DOH body. last: %s\n", doh_publish_length,
+                (doh_last_piece ? "true" : "false"));
+            fflush(HttpTestManager::get_output_file());
+        }
+    #endif
+    }
+
     publish_octets += publish_length;
 }
 
index 393636713f379c1c35ff4d1ee1d9210554d6ff31..0088433644269302fba438cfc67226130af36b6f 100755 (executable)
 #include "http_msg_body.h"
 #include "http_normalizers.h"
 
+#include "mime/decode_b64.h"
+#include "pub_sub/dns_payload_event.h"
+#include "pub_sub/intrinsic_event_ids.h"
+
+
 using namespace snort;
 using namespace HttpCommon;
 using namespace HttpEnums;
@@ -72,6 +77,74 @@ void HttpMsgHeader::publish(unsigned pub_id)
         HttpEventIds::REQUEST_HEADER : HttpEventIds::RESPONSE_HEADER;
 
     DataBus::publish(pub_id, evid, http_header_event, flow);
+
+    // Look for DNS over HTTPS (DOH) and if found publish a DNS payload event
+    int32_t uri_length = 0;
+    const uint8_t* uri = http_header_event.get_uri_query(uri_length);
+    if (uri_length == 0)
+        return;
+    constexpr char doh_pattern[] = "dns=";
+    constexpr int8_t doh_pattern_len = sizeof(doh_pattern) - 1;
+    // For DOH, get request will look like GET /dns-query?dns=BASE64_ENCODED_DNS_QUERY
+    if (uri_length >= doh_pattern_len && memcmp(uri, doh_pattern, doh_pattern_len) == 0 && get_method_id() == METH_GET)
+    {
+        size_t b64_start = doh_pattern_len;
+        if (b64_start < (size_t)uri_length)
+        {
+            const uint8_t* b64_data_start = uri + b64_start;
+            const uint8_t* b64_data_end = uri + uri_length;
+            size_t b64_len = b64_data_end - b64_data_start;
+
+            // Copy base64 data to a vector for padding and conversion from base64url to base64
+            std::vector<uint8_t> b64_data(b64_data_start, b64_data_end);
+
+            for (auto& c : b64_data)
+            {
+                if (c == '-')
+                    c = '+';
+                else if (c == '_')
+                    c = '/';
+            }
+            // DOH RFC requires base64url encoding which omits padding(rfc8484:4.1.1). Add it back if needed.
+            // Base64 length must be a multiple of 4
+            size_t remainder = b64_len % 4;
+            if (remainder == 1)
+            {
+                // Invalid base64 length, do not decode
+                return;
+            }
+            else if (remainder == 2)
+            {
+                b64_data.push_back('=');
+                b64_data.push_back('=');
+            }
+            else if (remainder == 3)
+                b64_data.push_back('=');
+
+            std::vector<uint8_t> decode_buf(1024);
+            B64Decode decoder = B64Decode(1024, 1024);
+            DecodeResult decode_result = decoder.decode_data(b64_data.data(), b64_data.data() + b64_data.size(),
+                decode_buf.data());
+            if (decode_result == DECODE_SUCCESS)
+            {
+                const uint8_t* decoded_data = nullptr;
+                uint32_t decode_buf_size = 0;
+                decoder.get_decoded_data(&decoded_data, &decode_buf_size);
+                DnsPayloadEvent dns_payload_event(decoded_data, decode_buf_size, (source_id == SRC_CLIENT), true, true);
+                DnsDataStash* stash = new DnsDataStash(decoded_data, decode_buf_size);
+                flow->set_attr(STASH_DNS_DATA, stash);
+                DataBus::publish(intrinsic_pub_id, IntrinsicEventIds::DNS_PAYLOAD, dns_payload_event, flow);
+                #ifdef REG_TEST
+                if (HttpTestManager::use_test_output(HttpTestManager::IN_HTTP))
+                {
+                    fprintf(HttpTestManager::get_output_file(),
+                        "Published %" PRId32 " bytes of DOH payload from base64 decoded query.\n", decode_buf_size);
+                    fflush(HttpTestManager::get_output_file());
+                }
+                #endif
+            }
+        }
+    }
 }
 
 bool HttpMsgHeader::has_supported_encoding() const
@@ -515,11 +588,11 @@ void HttpMsgHeader::prepare_body()
         session_data->detect_depth_remaining[source_id] = INT64_MAX;
     }
 
-    // Set the default depth if we're publishing the request body.
-    if (params->publish_request_body and is_request)
+    // Set the default depth if we're publishing the request body or DOH body.
+    if ( (params->publish_request_body and is_request) || (get_content_type() == CT_APPLICATION_DNS) )
     {
         session_data->publish_octets[source_id] = 0;
-        session_data->publish_depth_remaining[source_id] = REQUEST_PUBLISH_DEPTH;
+        session_data->publish_depth_remaining[source_id] = BODY_PUBLISH_DEPTH;
     }
 
     // Dynamically update the publish depth.
index 5b0dd4b2f1db1ad2691a6c6d9e75b1305cba2c3f..1413c37894a0e4f892e17287c483f396a9d31f94 100644 (file)
@@ -220,7 +220,7 @@ bool HttpStreamSplitter::finish(Flow* flow)
 #endif
             }
 
-            if ((source_id == SRC_CLIENT) and (session_data->publish_octets[source_id] < REQUEST_PUBLISH_DEPTH))
+            if ((source_id == SRC_CLIENT) and (session_data->publish_octets[source_id] < BODY_PUBLISH_DEPTH))
             {
                 HttpRequestBodyEvent http_request_body_event(nullptr, 0,
                     session_data->publish_octets[source_id], true, session_data);
index f5cfdf79e9d267f421f1a36ca408be82b7e7fdc0..3c2ed9ec6b7e8d4ff56fd54ef41524b54570df4e 100755 (executable)
@@ -174,6 +174,7 @@ const StrCode HttpMsgHeadShared::content_code_list[] =
 
 const StrCode HttpMsgHeadShared::content_type_list[] =
 {
+    { CT_APPLICATION_DNS,          "application/dns-message" },
     { CT_APPLICATION_PDF,          "application/pdf" },
     { CT_APPLICATION_OCTET_STREAM, "application/octet-stream" },
     { CT_APPLICATION_JAVASCRIPT,   "application/javascript" },
index 51ab31c8e52ff67886b26db68abc7ae2bf72268d..66140d871941bf18a11f2ce214b202262569308d 100644 (file)
@@ -254,12 +254,11 @@ Packet* TcpReassemblerBase::initialize_pdu(Packet* p, uint32_t pkt_flags, struct
     pdu->dsize = 0;
     pdu->data = nullptr;
     pdu->ip_proto_next = (IpProtocol)p->flow->ip_proto;
-
+    memcpy( pdu->layers, p->layers, p->num_layers * sizeof(Layer));
+    pdu->num_layers = p->num_layers;
 
     if ( p->proto_bits & PROTO_BIT__VLAN )
     {
-        memcpy( pdu->layers, p->layers, p->num_layers * sizeof(Layer));
-        pdu->num_layers = p->num_layers;
         pdu->proto_bits |= PROTO_BIT__VLAN;
         pdu->vlan_idx = p->vlan_idx;
     }