* `RD` - A boolean, true when the client asks the server to pursue the query recursively
* `RA` - A boolean, denotes the availability of recursive query support at the server
* `Z` - A 3 bit integer set to 0 unless DNSSEC is used (see RFC 2535)
-* `answers` - The list of answers to the query, only A and AAAA types are currently supported
+* `answers` - The list of answers to the query
+* `TTLs` - The list of caching intervals for the corresponding answers
* `rejected` - A boolean, true when the server responds with an error code and no query
+* `auth` - The list of authoritative responses
+* `addl` - The list of additional responses
+
+In the answers, auth, and addl lists the decoding of the following RR types is supported:
+A, AAAA, CNAME, DS, MX, NS, NSEC, PTR, RRSIG, SOA, TXT
Fields supported for connection:
return ((const DnsResponseEvent*)event)->get_answers().c_str();
}
+static const char* get_TTLs(const DataEvent* event, const Flow*)
+{
+ return ((const DnsResponseEvent*)event)->get_TTLs().c_str();
+}
+
static const char* get_rejected(const DataEvent* event, const Flow*)
{
return ((const DnsResponseEvent*)event)->get_rejected() ? "T" : "F";
}
+static const char* get_auth(const DataEvent* event, const Flow*)
+{
+ return ((const DnsResponseEvent*)event)->get_auth().c_str();
+}
+
+static const char* get_addl(const DataEvent* event, const Flow*)
+{
+ return ((const DnsResponseEvent*)event)->get_addl().c_str();
+}
+
static const map<string, ExtractorEvent::BufGetFn> sub_buf_getters =
{
{"proto", get_proto},
{"RD", get_RD},
{"RA", get_RA},
{"answers", get_answers},
- {"rejected", get_rejected}
+ {"TTLs", get_TTLs},
+ {"rejected", get_rejected},
+ {"auth", get_auth},
+ {"addl", get_addl}
};
THREAD_LOCAL const snort::Connector::ID* DnsResponseExtractor::log_id = nullptr;
"RA",
"Z",
"answers",
- "rejected"
+ "TTLs",
+ "rejected",
+ "auth",
+ "addl"
},
};
const std::string& DnsResponseEvent::get_answers() const
{
- return session.answers;
+ if (!answers_set)
+ {
+ session.get_answers(packet, answers, ttls);
+ answers_set = true;
+ }
+ return answers;
+}
+
+const std::string& DnsResponseEvent::get_TTLs() const
+{
+ if (!answers_set)
+ {
+ session.get_answers(packet, answers, ttls);
+ answers_set = true;
+ }
+ return ttls;
}
bool DnsResponseEvent::get_rejected() const
session.hdr.flags & DNS_HDR_FLAG_REPLY_CODE_MASK &&
!session.hdr.questions;
}
+
+const std::string& DnsResponseEvent::get_auth() const
+{
+ if (!auth_set)
+ {
+ session.get_auth(packet, auth);
+ auth_set = true;
+ }
+ return auth;
+}
+
+const std::string& DnsResponseEvent::get_addl() const
+{
+ if (!addl_set)
+ {
+ session.get_addl(packet, addl);
+ addl_set = true;
+ }
+ return addl;
+}
class SO_PUBLIC DnsResponseEvent : public snort::DataEvent
{
public:
- DnsResponseEvent(const DNSData& ssn) : session(ssn) { }
+ DnsResponseEvent(const DNSData& ssn, Packet* p) : session(ssn), packet(p) { }
uint16_t get_trans_id() const;
const std::string& get_query() const;
bool get_RA() const;
uint8_t get_Z() const;
const std::string& get_answers() const;
+ const std::string& get_TTLs() const;
bool get_rejected() const;
+ const std::string& get_auth() const;
+ const std::string& get_addl() const;
private:
const DNSData& session;
+ const Packet* packet = nullptr;
+ mutable std::string answers;
+ mutable std::string ttls;
+ mutable std::string auth;
+ mutable std::string addl;
+ mutable bool answers_set = false;
+ mutable bool auth_set = false;
+ mutable bool addl_set = false;
};
}
dns_config.h
dns_module.cc
dns_module.h
+ dns_rr_decoder.cc
dns_splitter.cc
dns_splitter.h
)
using namespace snort;
#define MAX_UDP_PAYLOAD 0x1FFF
-#define DNS_RR_PTR 0xC0
THREAD_LOCAL ProfileStats dnsPerfStats;
THREAD_LOCAL DnsStats dnsstats;
return true;
}
-void DNSData::add_answer(const char* answer)
-{
- if (!answers.empty())
- answers.append(" ");
- answers.append(answer);
-}
-
DNSData* get_dns_session_data(Packet* p, bool from_server, DNSData& udpSessionData)
{
DnsFlowData* fd;
}
static uint16_t ParseDNSAnswer(
- const unsigned char* data, uint16_t bytes_unused, DNSData* dnsSessionData)
+ const unsigned char* data, uint16_t bytes_unused, DNSData* dnsSessionData,
+ const Packet* p, std::vector<uint16_t>& tabs)
{
if ( !bytes_unused )
return 0;
switch (dnsSessionData->curr_rec_state)
{
case DNS_RESP_STATE_RR_TYPE:
+ tabs.emplace_back(data - p->data);
dnsSessionData->curr_rr.type = (uint8_t)*data << 8;
data++;
static uint16_t ParseDNSRData(
const unsigned char* data,
uint16_t bytes_unused,
- DNSData* dnsSessionData,
- void (DNSData::*save_rdata)(const char*) = nullptr)
+ DNSData* dnsSessionData)
{
if (bytes_unused == 0)
{
break;
case DNS_RR_TYPE_A:
case DNS_RR_TYPE_AAAA:
+ if (dnsSessionData->publish_response())
{
- auto resp_ip = DnsResponseIp(data, dnsSessionData->curr_rr.type);
- if (dnsSessionData->publish_response())
- {
- dnsSessionData->dns_events.add_fqdn(dnsSessionData->cur_fqdn_event, dnsSessionData->curr_rr.ttl);
- dnsSessionData->dns_events.add_ip(resp_ip);
- }
-
- if (save_rdata)
- {
- SfIpString ipbuf;
- resp_ip.get_ip().ntop(ipbuf);
- (dnsSessionData->*save_rdata)(ipbuf);
- }
+ dnsSessionData->dns_events.add_fqdn(dnsSessionData->cur_fqdn_event, dnsSessionData->curr_rr.ttl);
+ dnsSessionData->dns_events.add_ip(DnsResponseIp(data, dnsSessionData->curr_rr.type));
}
- bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData);
+ bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData);
break;
case DNS_RR_TYPE_CNAME:
if (dnsSessionData->publish_response())
case DNS_RR_TYPE_PTR:
case DNS_RR_TYPE_HINFO:
case DNS_RR_TYPE_MX:
+ case DNS_RR_TYPE_RRSIG:
+ case DNS_RR_TYPE_NSEC:
+ case DNS_RR_TYPE_DS:
bytes_unused = SkipDNSRData(data, bytes_unused, dnsSessionData);
break;
default:
case DNS_RESP_STATE_ANS_RR: /* ANSWERS section */
for (i=dnsSessionData->curr_rec; i<dnsSessionData->hdr.answers; i++)
{
- bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData);
+ bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData, p, dnsSessionData->answer_tabs);
if (bytes_unused == 0)
{
case DNS_RESP_STATE_RR_RDATA_MID:
/* Data now points to the beginning of the RDATA */
data = p->data + (p->dsize - bytes_unused);
- bytes_unused = ParseDNSRData(data, bytes_unused, dnsSessionData, &DNSData::add_answer);
+ bytes_unused = ParseDNSRData(data, bytes_unused, dnsSessionData);
if (dnsSessionData->curr_rec_state != DNS_RESP_STATE_RR_COMPLETE)
{
needNextPacket = true;
case DNS_RESP_STATE_AUTH_RR: /* AUTHORITIES section */
for (i=dnsSessionData->curr_rec; i<dnsSessionData->hdr.authorities; i++)
{
- bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData);
+ bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData, p, dnsSessionData->auth_tabs);
if (bytes_unused == 0)
{
case DNS_RESP_STATE_ADD_RR: /* ADDITIONALS section */
for (i=dnsSessionData->curr_rec; i<dnsSessionData->hdr.additionals; i++)
{
- bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData);
+ bytes_unused = ParseDNSAnswer(data, bytes_unused, dnsSessionData, p, dnsSessionData->addl_tabs);
if (bytes_unused == 0)
{
if (!needNextPacket and dnsSessionData->has_events())
DataBus::publish(Dns::get_pub_id(), DnsEventIds::DNS_RESPONSE_DATA, dnsSessionData->dns_events);
- DnsResponseEvent dns_response_event(*dnsSessionData);
+ DnsResponseEvent dns_response_event(*dnsSessionData, p);
DataBus::publish(Dns::get_pub_id(), DnsEventIds::DNS_RESPONSE, dns_response_event, p->flow);
}
#define DNS_RR_TYPE_MX 0x000f
#define DNS_RR_TYPE_TXT 0x0010
#define DNS_RR_TYPE_AAAA 0x001c
+#define DNS_RR_TYPE_RRSIG 0x002e
+#define DNS_RR_TYPE_NSEC 0x002f
+#define DNS_RR_TYPE_DS 0x002b
+
+#define DNS_RR_PTR 0xC0
#define DNS_FLAG_NOT_DNS 0x01
snort::DnsResponseDataEvents dns_events;
DnsResponseFqdn cur_fqdn_event;
std::string resp_query;
- std::string answers;
+ std::vector<uint16_t> answer_tabs;
+ std::vector<uint16_t> auth_tabs;
+ std::vector<uint16_t> addl_tabs;
bool publish_response() const;
bool has_events() const;
bool valid_dns(const DNSHdr&) const;
- void add_answer(const char* answer);
+
+ void decode_rdata(const snort::Packet* p, const uint8_t* rdata, uint16_t rdlength,
+ uint16_t type, std::string& rdata_str) const;
+ void get_rr_data(const snort::Packet *p, const std::vector<uint16_t>& tabs,
+ std::string& rrs, std::string* ttls = nullptr) const;
+ void get_answers(const snort::Packet *p, std::string& answers, std::string& ttls) const
+ { get_rr_data(p, answer_tabs, answers, &ttls); }
+ void get_auth(const snort::Packet *p, std::string& auth) const { get_rr_data(p, auth_tabs, auth); }
+ void get_addl(const snort::Packet *p, std::string& addl) const { get_rr_data(p, addl_tabs, addl); }
};
DNSData* get_dns_session_data(snort::Packet* p, bool from_server, DNSData& udpSessionData);
--- /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.cc author Cisco
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dns.h"
+
+#include <string>
+#include "sfip/sf_ip.h"
+
+using namespace snort;
+
+static void decode_ip(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str, uint16_t type)
+{
+ int family = (type == DNS_RR_TYPE_A) ? AF_INET : AF_INET6;
+ if (family == AF_INET && rdlength != 4)
+ return;
+ if (family == AF_INET6 && rdlength != 16)
+ return;
+
+ SfIpString ipbuf;
+ SfIp rr_ip(rdata, family);
+ rr_ip.ntop(ipbuf);
+
+ rdata_str = ipbuf;
+}
+
+static const std::string part_sep = " "; // separates between parts of an item in a list
+
+static void decode_txt(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+{
+ static const std::string txt_prefix = "TXT" + part_sep;
+
+ while (rdlength)
+ {
+ if (!rdata_str.empty())
+ rdata_str.append(part_sep);
+ rdata_str.append(txt_prefix);
+
+ uint8_t txt_len = *rdata;
+ rdata_str.append(std::to_string(txt_len));
+ rdata_str.append(part_sep);
+ rdata++;
+ rdlength--;
+
+ uint8_t actual_len = rdlength > txt_len ? txt_len : rdlength;
+ rdata_str.append((const char*)rdata, actual_len);
+ rdata += actual_len;
+ rdlength -= actual_len;
+ }
+}
+
+static void decode_domain_name(const uint8_t* rdata, uint16_t rdlength,
+ std::string& rdata_str, const Packet* p = nullptr)
+{
+ static const int MAX_LINKS = 10;
+ int link_count = 0;
+
+ bool first_label = true;
+ while (rdlength)
+ {
+ uint8_t label_len = *rdata;
+ rdata++;
+ rdlength--;
+
+ if (label_len == 0)
+ break;
+
+ if ((label_len & DNS_RR_PTR) == DNS_RR_PTR)
+ {
+ if (p == nullptr)
+ break; // compression not supported
+
+ if (rdlength < 1)
+ break; // incomplete offset
+
+ uint16_t offset = ((label_len & ~DNS_RR_PTR) << 8) | *rdata;
+ if (offset >= p->dsize)
+ break; // invalid offset
+ if (link_count++ > MAX_LINKS)
+ break; // too many links
+
+ rdata = p->data + offset;
+ rdlength = p->dsize - offset;
+ continue;
+ }
+
+ if (label_len & DNS_RR_PTR)
+ break; // invalid label length
+
+ uint8_t actual_len = rdlength > label_len ? label_len : rdlength;
+ if (!first_label)
+ rdata_str.append(".");
+ first_label = false;
+ rdata_str.append((const char*)rdata, actual_len);
+ rdata += actual_len;
+ rdlength -= actual_len;
+ }
+}
+
+static void decode_mx(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str, const Packet* p)
+{
+ static const unsigned EXCHANGE_OFFSET = 2;
+ if (rdlength <= EXCHANGE_OFFSET)
+ return; // incomplete MX record
+
+ rdata += EXCHANGE_OFFSET;
+ rdlength -= EXCHANGE_OFFSET;
+ decode_domain_name(rdata, rdlength, rdata_str, p);
+}
+
+static void decode_rrsig(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+{
+ static const std::string rrsig_prefix = "RRSIG" + part_sep;
+ static const unsigned SIGNER_NAME_OFFSET = 18;
+
+ if (rdlength <= SIGNER_NAME_OFFSET)
+ return; // incomplete RRSIG record
+
+ auto type_covered = (uint16_t)rdata[0] << 8 | rdata[1];
+ rdata_str.append(rrsig_prefix);
+ rdata_str.append(std::to_string(type_covered));
+ rdata_str.append(part_sep);
+
+ rdata += SIGNER_NAME_OFFSET;
+ rdlength -= SIGNER_NAME_OFFSET;
+ decode_domain_name(rdata, rdlength, rdata_str);
+}
+
+static void decode_nsec(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str,
+ const std::string& resp_query)
+{
+ static const std::string nsec_prefix = "NSEC" + part_sep;
+
+ rdata_str.append(nsec_prefix);
+ rdata_str.append(resp_query);
+ rdata_str.append(part_sep);
+ decode_domain_name(rdata, rdlength, rdata_str);
+}
+
+static void decode_ds(const uint8_t* rdata, uint16_t rdlength, std::string& rdata_str)
+{
+ static const std::string ds_prefix = "DS" + part_sep;
+ static const unsigned DIGEST_OFFSET = 4;
+
+ if (rdlength <= DIGEST_OFFSET)
+ return; // incomplete DS record
+
+ rdata_str.append(ds_prefix);
+ rdata_str.append(std::to_string(rdata[2])); // algorithm
+ rdata_str.append(part_sep);
+ rdata_str.append(std::to_string(rdata[3])); // digest type
+}
+
+void DNSData::decode_rdata(const Packet* p, const uint8_t* rdata, uint16_t rdlength, uint16_t type,
+ std::string& rdata_str) const
+{
+ assert(rdata < p->data + p->dsize);
+
+ if (rdata + rdlength > p->data + p->dsize)
+ rdlength = p->dsize - (rdata - p->data);
+
+ switch (type)
+ {
+ case DNS_RR_TYPE_A:
+ case DNS_RR_TYPE_AAAA:
+ decode_ip(rdata, rdlength, rdata_str, type);
+ break;
+
+ case DNS_RR_TYPE_TXT:
+ decode_txt(rdata, rdlength, rdata_str);
+ break;
+
+ case DNS_RR_TYPE_CNAME:
+ case DNS_RR_TYPE_MD:
+ case DNS_RR_TYPE_MF:
+ case DNS_RR_TYPE_MB:
+ case DNS_RR_TYPE_MG:
+ case DNS_RR_TYPE_MR:
+ case DNS_RR_TYPE_NS:
+ case DNS_RR_TYPE_PTR:
+ case DNS_RR_TYPE_SOA:
+ decode_domain_name(rdata, rdlength, rdata_str, p);
+ break;
+
+ case DNS_RR_TYPE_MX:
+ decode_mx(rdata, rdlength, rdata_str, p);
+ break;
+
+ case DNS_RR_TYPE_RRSIG:
+ decode_rrsig(rdata, rdlength, rdata_str);
+ break;
+
+ case DNS_RR_TYPE_NSEC:
+ decode_nsec(rdata, rdlength, rdata_str, resp_query);
+ break;
+
+ case DNS_RR_TYPE_DS:
+ decode_ds(rdata, rdlength, rdata_str);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void DNSData::get_rr_data(const Packet *p, const std::vector<uint16_t>& tabs,
+ std::string& rrs, std::string* ttls) const
+{
+ static const std::string item_sep = " "; // list item separator
+ static const unsigned RDATA_OFFSET = 10;
+ for (auto tab : tabs)
+ {
+ if (tab + RDATA_OFFSET >= p->dsize)
+ continue;
+
+ auto rdata = p->data + tab;
+
+ uint16_t type = (rdata[0] << 8) | rdata[1];
+ uint32_t ttl = (rdata[4] << 24) | (rdata[5] << 16) | (rdata[6] << 8) | rdata[7];
+
+ uint16_t rdlength = (rdata[8] << 8) | rdata[9];
+
+ std::string rdata_str;
+ decode_rdata(p, rdata + RDATA_OFFSET, rdlength, type, rdata_str);
+
+ if (rdata_str.empty())
+ continue;
+
+ if (!rrs.empty())
+ {
+ rrs.append(item_sep);
+ if (ttls)
+ ttls->append(item_sep);
+ }
+ rrs.append(rdata_str);
+ if (ttls)
+ ttls->append(std::to_string(ttl));
+ }
+}