DNS: Implement NAPTR RR support

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.
This commit is contained in:
Arne Welzel 2025-06-21 13:32:03 +02:00
parent 25b5cabab7
commit 4f1fc296b6
11 changed files with 121 additions and 0 deletions

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

@ -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,6 +11,7 @@
#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"
@ -299,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;
@ -628,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 )

View file

@ -360,6 +360,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;
}