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)
--- /dev/null
+//--------------------------------------------------------------------------
+// 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());
+ }
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
#define APPID_DNS_SESSION_H
#include <string>
+
#include "pub_sub/appid_events.h"
#define DNS_GOT_QUERY 0x01
record_type = 0;
ttl = 0;
options_offset = 0;
+ doh = false;
}
uint8_t get_state() const
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;
std::string host;
uint16_t host_offset = 0;
uint16_t options_offset = 0;
+ bool doh = false;
};
#endif
#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"
#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"
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);
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;
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)
{
DnsTcpServiceDetector(ServiceDiscovery*);
int validate(AppIdDiscoveryArgs&) override;
+ int validate_doq(AppIdDiscoveryArgs& args);
};
class DnsUdpServiceDetector : public ServiceDetector, public DnsValidator
DnsUdpServiceDetector(ServiceDiscovery*);
int validate(AppIdDiscoveryArgs&) override;
+ int validate_doh(AppIdDiscoveryArgs&);
};
#endif
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
--- /dev/null
+//--------------------------------------------------------------------------
+// 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 ⅆ
+}
+
+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;
+}
ssh_events.h
ssl_events.h
stream_event_ids.h
+ dns_payload_event.h
)
add_library( pub_sub OBJECT
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
RESPONSE_HEADER,
REQUEST_BODY,
BODY,
+ DOH_BODY,
END_OF_TRANSACTION,
HTTP_PUBLISH_LENGTH,
AUXILIARY_IP,
FILE_VERDICT,
+ DNS_PAYLOAD,
+
num_ids
}; };
{
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);
dns_rr_decoder.cc
dns_splitter.cc
dns_splitter.h
+ dns_payload_event_handler.cc
+ dns_payload_event_handler.h
)
# if (STATIC_INSPECTORS)
#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;
{ 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)
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;
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;
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.
*
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;
* 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;
}
// 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)
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
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.
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
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))
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
//-------------------------------------------------------------------------
#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.
#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:
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:
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
--- /dev/null
+//--------------------------------------------------------------------------
+// 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;
+
+}
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
{
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;
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;
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,
#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"
}
// 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);
#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;
}
#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;
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
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.
#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);
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" },
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;
}