Merge remote-tracking branch 'origin/topic/awelzel/dns-naming-authority-pointer'

* origin/topic/awelzel/dns-naming-authority-pointer:
  DNS: Implement NAPTR RR support
  DNS: Move extract_char_string() helper around
This commit is contained in:
Arne Welzel 2025-06-24 17:44:06 +02:00
commit fbeb3adfe6
13 changed files with 157 additions and 25 deletions

View file

@ -1,3 +1,12 @@
8.0.0-dev.506 | 2025-06-24 17:44:06 +0200
* DNS: Implement NAPTR RR support (Arne Welzel, Corelight)
My phone is sending NAPTR queries and we reported an unknown RR type 35
in weird.log for the response, so figured I'd just add it.
* DNS: Move extract_char_string() helper around (Arne Welzel, Corelight)
8.0.0-dev.503 | 2025-06-24 17:14:28 +0200
* cluster/zeromq: Short-circuit DoPublishLogWrite() when not initialized (Arne Welzel, Corelight)

3
NEWS
View file

@ -114,6 +114,9 @@ New Functionality
redef Intel::manage_seen_event_groups = F;
- The DNS analyzer was extended to support NAPTR RRs (RFC 2915, RFC 3403).
A corresponding ``dns_NAPTR_reply`` event was added.
Changed Functionality
---------------------

View file

@ -1 +1 @@
8.0.0-dev.503
8.0.0-dev.506

View file

@ -3048,6 +3048,20 @@ type dns_svcb_rr: record {
target_name: string; ##< Target name, the hostname of the service endpoint.
};
## A NAPTR record.
##
## See also RFC 2915 - The Naming Authority Pointer (NAPTR) DNS Resource Record.
##
## .. zeek:see:: dns_NAPTR_reply
type dns_naptr_rr: record {
order: count; ##< Order in which to process NAPTR records.
preference: count; ##< Preference specifying processing order for *equal* :zeek:field:`dns_naptr_rr$order` fields.
flags: string; ##< Flags to control rewriting. E.g. "u", "a", "s" or "p".
service: string; ##< The services available down this rewrite path.
regexp: string; ##< Substitution expression to be applied to the original query.
replacement: string; ##< The next name to query, where the type is depending on the :zeek:field:`dns_naptr_rr$flags` field.
};
# DNS answer types.
#
# .. zeek:see:: dns_answer

View file

@ -537,6 +537,27 @@ event dns_SRV_reply(c: connection, msg: dns_msg, ans: dns_answer, target: string
hook DNS::do_reply(c, msg, ans, target);
}
event dns_NAPTR_reply(c: connection, msg: dns_msg, ans: dns_answer, naptr: dns_naptr_rr) &priority=5
{
# Just encode all the fields for NAPTR RR in the reply string.
local tmp = "";
if ( |naptr$regexp| > 0 )
tmp += naptr$regexp;
if ( |naptr$replacement| > 0 )
{
if ( |tmp| > 0 )
tmp += " ";
tmp += naptr$replacement;
}
local r = fmt("NAPTR %s %s %s %s %s", naptr$order, naptr$preference, naptr$flags, naptr$service, tmp);
hook DNS::do_reply(c, msg, ans, r);
}
# TODO: figure out how to handle these
#event dns_EDNS(c: connection, msg: dns_msg, ans: dns_answer)
# {

View file

@ -11,10 +11,37 @@
#include "zeek/Event.h"
#include "zeek/NetVar.h"
#include "zeek/RunState.h"
#include "zeek/Val.h"
#include "zeek/ZeekString.h"
#include "zeek/analyzer/protocol/dns/events.bif.h"
#include "zeek/session/Manager.h"
namespace {
zeek::StringValPtr extract_char_string(zeek::analyzer::Analyzer* analyzer, const u_char*& data, int& len, int& rdlen) {
if ( rdlen <= 0 )
return nullptr;
uint8_t str_size = data[0];
--rdlen;
--len;
++data;
if ( str_size > rdlen ) {
analyzer->Weird("DNS_TXT_char_str_past_rdlen");
return nullptr;
}
auto rval = zeek::make_intrusive<zeek::StringVal>(str_size, reinterpret_cast<const char*>(data));
rdlen -= str_size;
len -= str_size;
data += str_size;
return rval;
}
} // namespace
namespace zeek::analyzer::dns {
namespace detail {
@ -273,6 +300,8 @@ bool DNS_Interpreter::ParseAnswer(detail::DNS_MsgInfo* msg, const u_char*& data,
break;
case detail::TYPE_NAPTR: status = ParseRR_NAPTR(msg, data, len, rdlength, msg_start); break;
case detail::TYPE_EDNS: status = ParseRR_EDNS(msg, data, len, rdlength, msg_start); break;
case detail::TYPE_TKEY: status = ParseRR_TKEY(msg, data, len, rdlength, msg_start); break;
@ -602,6 +631,50 @@ bool DNS_Interpreter::ParseRR_SRV(detail::DNS_MsgInfo* msg, const u_char*& data,
return true;
}
bool DNS_Interpreter::ParseRR_NAPTR(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
const u_char* msg_start) {
zeek_uint_t order = ExtractShort(data, len);
zeek_uint_t preference = ExtractShort(data, len);
rdlength -= 4;
if ( len <= 0 || rdlength <= 0 ) {
analyzer->AnalyzerViolation("DNS_NAPTR_too_short");
return false;
}
// These all check rdlength and return nullptr if there's not enough data available.
auto flags = extract_char_string(analyzer, data, len, rdlength);
auto service = extract_char_string(analyzer, data, len, rdlength);
auto regexp = extract_char_string(analyzer, data, len, rdlength);
// The replacement string is a name. Compression shouldn't be used, but doesn't seem
// we have a helper that would allow to control this.
u_char replacement[513];
int replacement_len = sizeof(replacement) - 1;
u_char* replacement_end = ExtractName(data, len, replacement, replacement_len, msg_start, false);
if ( ! flags || ! service || ! regexp || ! replacement_end ) {
analyzer->AnalyzerViolation("DNS_NAPTR_RR_too_short");
return false;
}
if ( dns_NAPTR_reply && ! msg->skip_event ) {
static auto dns_naptr_rr = id::find_type<RecordType>("dns_naptr_rr");
auto r = make_intrusive<RecordVal>(dns_naptr_rr);
r->Assign(0, order);
r->Assign(1, preference);
r->Assign(2, std::move(flags));
r->Assign(3, std::move(service));
r->Assign(4, std::move(regexp));
r->Assign(5, zeek::make_intrusive<StringVal>(new String(replacement, replacement_end - replacement, true)));
analyzer->EnqueueConnEvent(dns_NAPTR_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(), r);
}
return true;
}
bool DNS_Interpreter::ParseRR_EDNS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
const u_char* msg_start) {
if ( dns_EDNS_addl && ! msg->skip_event )
@ -1413,30 +1486,6 @@ bool DNS_Interpreter::ParseRR_WKS(detail::DNS_MsgInfo* msg, const u_char*& data,
return true;
}
static StringValPtr extract_char_string(analyzer::Analyzer* analyzer, const u_char*& data, int& len, int& rdlen) {
if ( rdlen <= 0 )
return nullptr;
uint8_t str_size = data[0];
--rdlen;
--len;
++data;
if ( str_size > rdlen ) {
analyzer->Weird("DNS_TXT_char_str_past_rdlen");
return nullptr;
}
auto rval = make_intrusive<StringVal>(str_size, reinterpret_cast<const char*>(data));
rdlen -= str_size;
len -= str_size;
data += str_size;
return rval;
}
bool DNS_Interpreter::ParseRR_HINFO(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
if ( ! dns_HINFO_reply || msg->skip_event ) {
data += rdlength;

View file

@ -364,6 +364,7 @@ protected:
bool ParseRR_MX(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_NBS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_SRV(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_NAPTR(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_EDNS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength, const u_char* msg_start);
bool ParseRR_EDNS_ECS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
const u_char* msg_start);

View file

@ -467,6 +467,23 @@ event dns_CAA_reply%(c: connection, msg: dns_msg, ans: dns_answer, flags: count,
## dns_skip_addl dns_skip_all_addl dns_skip_all_auth dns_skip_auth
event dns_SRV_reply%(c: connection, msg: dns_msg, ans: dns_answer, target: string, priority: count, weight: count, p: count%);
## Generated for DNS replies of type *NAPTR*. For replies with multiple answers,
## an individual event of the corresponding type is raised for each.
##
## c: The connection, which may be UDP or TCP depending on the type of the
## transport-layer session being analyzed.
##
## msg: The parsed DNS message header.
##
## ans: The type-independent part of the parsed answer record.
##
## naptr: The parsed RDATA of NAPTR type record.
##
## .. zeek:see:: dns_AAAA_reply dns_A_reply dns_CNAME_reply dns_EDNS_addl
## dns_HINFO_reply dns_MX_reply dns_NS_reply dns_PTR_reply dns_SOA_reply
## dns_TSIG_addl dns_TXT_reply dns_SPF_reply dns_WKS_reply dns_SRV_reply dns_end
event dns_NAPTR_reply%(c: connection, msg: dns_msg, ans: dns_answer, naptr: dns_naptr_rr%);
## Generated on DNS reply resource records when the type of record is not one
## that Zeek knows how to parse and generate another more specific event.
##

View file

@ -0,0 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
NAPTR, [id=20970, opcode=0, rcode=0, QR=T, AA=F, TC=F, RD=T, RA=T, Z=0, AD=F, CD=F, num_queries=1, num_answers=1, num_auth=0, num_addl=0], [answer_type=1, query=fp-de-carrier-vodafone.rcs.telephony.goog, qtype=35, qclass=1, TTL=2.0 mins 48.0 secs], [order=100, preference=100, flags=s, service=SIPS+D2T, regexp=, replacement=_sips._tcp.fp-de-carrier-vodafone.rcs.telephony.goog]

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
uid query qclass_name qtype_name answers
CHhAvVGS1DHFjwGM9 fp-de-carrier-vodafone.rcs.telephony.goog C_INTERNET NAPTR NAPTR 100 100 s SIPS+D2T _sips._tcp.fp-de-carrier-vodafone.rcs.telephony.goog

View file

@ -0,0 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
NAPTR, [id=20970, opcode=0, rcode=0, QR=T, AA=F, TC=F, RD=T, RA=T, Z=0, AD=F, CD=F, num_queries=1, num_answers=1, num_auth=0, num_addl=0], [answer_type=1, query=fp-de-carrier-vodafone.rcs.telephony.goog, qtype=35, qclass=1, TTL=2.0 mins 48.0 secs], [order=100, preference=100, flags=s, service=SIPS+D2T, regexp=, replacement=_sips._tcp.fp-de-carrier-vodafone.rcs.telephony.goog]

Binary file not shown.

View file

@ -0,0 +1,11 @@
# @TEST-EXEC: zeek -b -r $TRACES/dns/naptr.pcap %INPUT >out
# @TEST-EXEC: btest-diff out
# @TEST-EXEC: zeek-cut -m uid query qclass_name qtype_name answers < dns.log > dns.log.cut
# @TEST-EXEC: btest-diff dns.log.cut
@load base/protocols/dns
event dns_NAPTR_reply(c: connection, msg: dns_msg, ans: dns_answer, naptr: dns_naptr_rr)
{
print "NAPTR", msg, ans, naptr;
}