From: Wei Wang (weiwa) Date: Tue, 17 Jun 2025 13:36:03 +0000 (+0000) Subject: Pull request #4776: dns: handle multi DNS transactions one TCP connection X-Git-Tag: 3.9.1.0~9 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=1786ec363c980b0777cb4d2869eb00d0208a23d2;p=thirdparty%2Fsnort3.git Pull request #4776: dns: handle multi DNS transactions one TCP connection 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 Date: Tue Jun 17 03:21:13 2025 +0530 dns: handle multi DNS transactions one TCP connection --- diff --git a/src/pub_sub/dns_events.cc b/src/pub_sub/dns_events.cc index 47e8c4f5b..df15796e7 100644 --- a/src/pub_sub/dns_events.cc +++ b/src/pub_sub/dns_events.cc @@ -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; diff --git a/src/pub_sub/dns_events.h b/src/pub_sub/dns_events.h index 4c88e2aa8..d72c5219c 100644 --- a/src/pub_sub/dns_events.h +++ b/src/pub_sub/dns_events.h @@ -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; } diff --git a/src/service_inspectors/dns/dev_notes.txt b/src/service_inspectors/dns/dev_notes.txt index d7d83f11e..fcd78c631 100644 --- a/src/service_inspectors/dns/dev_notes.txt +++ b/src/service_inspectors/dns/dev_notes.txt @@ -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. + diff --git a/src/service_inspectors/dns/dns.cc b/src/service_inspectors/dns/dns.cc index 8e92d47e8..57c51a2e0 100644 --- a/src/service_inspectors/dns/dns.cc +++ b/src/service_inspectors/dns/dns.cc @@ -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)