]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4776: dns: handle multi DNS transactions one TCP connection
authorWei Wang (weiwa) <weiwa@cisco.com>
Tue, 17 Jun 2025 13:36:03 +0000 (13:36 +0000)
committerSteve Chew (stechew) <stechew@cisco.com>
Tue, 17 Jun 2025 13:36:03 +0000 (13:36 +0000)
Merge in SNORT/snort3 from ~WEIWA/snort3:weiwa-master-dns-tcp-multi-tx to master

Squashed commit of the following:

commit 4cf7e30aa9a06bed678b723eeeb645a73d851b2c
Author: Wei Wang <weiwa@cisco.com>
Date:   Tue Jun 17 03:21:13 2025 +0530

    dns: handle multi DNS transactions one TCP connection

src/pub_sub/dns_events.cc
src/pub_sub/dns_events.h
src/service_inspectors/dns/dev_notes.txt
src/service_inspectors/dns/dns.cc

index 47e8c4f5b8d0c7585616f8f844214cd341a64df8..df15796e77e669a6ee0de592c9e672d630130e04 100644 (file)
@@ -136,6 +136,12 @@ bool DnsResponseDataEvents::empty() const
     return (dns_ips.empty() or dns_fqdns.empty());
 }
 
+void DnsResponseDataEvents::clear_data()
+{
+    dns_ips.clear();
+    dns_fqdns.clear();
+}
+
 uint16_t DnsResponseEvent::get_trans_id() const
 {
     return session.hdr.id;
index 4c88e2aa8e4e3ceac08102c1d6092f378bc41857..d72c5219c8350ce32b57e110506e3f0c54a0eca5 100644 (file)
@@ -70,6 +70,7 @@ public:
     void add_fqdn(DnsResponseFqdn& event, uint32_t ttl);
     void get_dns_data(IPFqdnCacheItem& ip_fqdn_cache_item);
     bool empty() const;
+    void clear_data();
 
     const Packet* get_packet() const override
     { return packet; }
index d7d83f11eed3def4426473aa37796968943c29e5..fcd78c6312a5a7405d5c14f63e2a0598ecdc3a54 100644 (file)
@@ -4,3 +4,35 @@ Experimental Record Types.
 
 DNS looks are DNS Response traffic over UDP and TCP and it requires Stream
 inspector to be enabled for TCP decoding.
+
+DNS Over UDP:
+
+This has simpler packet scenarios. In a UDP flow between a client and a DNS server,
+the client may send one or multiple UDP packets to the server and each of the UDP
+packets contains a complete DNS query message which has a unique DNS transaction ID
+across this UDP flow. The server may send back one UDP packet containing a complete
+DNS response message matching a query transaction ID. This DNS inspector maintains
+minimum flow data for each DNS over UDP flow: it extracts the transaction ID from
+each DNS query message and saves it into the flow data object, and it parses a DNS
+response message to look for FQDN and IP mapping only when the response message's
+transaction ID matches one of the cached query transaction IDs. It marks the
+underlying UDP flow as "closed" when all the cached query transaction IDs have been
+matched.
+
+DNS Over TCP:
+
+This has relatively more complex packet scenarios. Each DNS query message sent from
+a DNS client may be split into multiple TCP packets and each DNS response message
+sent from a DNS server may also be split into multiple TCP packets, and there may
+be one or multiple pairs of DNS query and response messages exchanged within a single
+TCP connection. This DNS inspector creates a DNS session data object and saves it into
+the TCP connection's flow data cache and this session data object is reused to serve
+multiple DNS transactions in the connection: when a new DNS response message arrives,
+the DNS response parser first updates the data buffer with the new message data and
+clears the data fields in the session data's dns event object, then it will parse the
+response message to get the domain name and IP address mapping information and save it
+into the session data's dns event object. Eventually it will publish the enclosed DNS
+event object for the event subscribers to comsume. Each event subscriber may use the
+domain name and IP address mapping information in the event object to look up the
+actual domain name and IP address values saved in the session data object's data buffer.
+
index 8e92d47e84a14afd3de64257c166a7cdc2d58bcf..57c51a2e046fe37261cc7d3ce215bc4a2944f99e 100644 (file)
@@ -381,9 +381,10 @@ static uint16_t ParseDNSName(
                     if (dnsSessionData->length > 0)
                         dnsSessionData->curr_txt.offset += 2; // first two bytes are length in TCP
 
-                    if (parse_dns_name)
+                    if (parse_dns_name && dnsSessionData->data.size() > dnsSessionData->curr_txt.offset)
                     {
-                        // parse recursively relative name
+                        // If the name field is a pointer, then parse the name field at that offset only if
+                        // the offset is within the bounds of the data buffer.
                         dnsSessionData->curr_txt.name_state = DNS_RESP_STATE_NAME_SIZE;
                         return ParseDNSName(&dnsSessionData->data[0] + dnsSessionData->curr_txt.offset,
                             dnsSessionData->bytes_unused, dnsSessionData, parse_dns_name);
@@ -810,11 +811,23 @@ static void ParseDNSResponseMessage(Packet* p, DNSData* dnsSessionData, bool& ne
     uint16_t bytes_unused = p->dsize;
     int i;
     const unsigned char* data = p->data;
-    if (dnsSessionData->dns_config->publish_response and dnsSessionData->data.empty())
+
+    // For DNS over TCP, it's possible that multiple DNS transactions may be processed in a single TCP connection.
+    // When a new transaction's DNS response message arrives, the reused DNS session's data field may not be empty,
+    // so we must use the field "state" to determine if we are processing a new DNS response message.
+    // For DNS over UDP, a new session data object is created for each DNS response message, so the following condition
+    // is always met as the data field is always empty.
+    if (dnsSessionData->dns_config->publish_response and (dnsSessionData->data.empty() or
+        dnsSessionData->state == DNS_RESP_STATE_LENGTH))
     {
+        // We are at the beginning of a new DNS response message, so we need to clear the data field
+        // and the event object, which are reused by multiple DNS transactions in a single TCP connection.
         dnsSessionData->data.resize(bytes_unused);
         memcpy((void*)&dnsSessionData->data[0], data, bytes_unused);
         dnsSessionData->bytes_unused = bytes_unused;
+        // For DNS over TCP, the reused event object may hold domain names and IP addresses extracted
+        // from previous DNS response message which must be cleared before processing a new DNS message.
+        dnsSessionData->dns_events.clear_data();
     }
 
     while (bytes_unused)