mirror of
https://github.com/zeek/zeek.git
synced 2025-10-01 22:28:20 +00:00
2090 lines
69 KiB
C++
2090 lines
69 KiB
C++
// See the file "COPYING" in the main distribution directory for copyright.
|
|
|
|
#include "zeek/analyzer/protocol/dns/DNS.h"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <cctype>
|
|
|
|
#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 {
|
|
|
|
// Used for checking whether the connection being parsed comes from NetBIOS,
|
|
// since it's similar to DNS but does some things differently.
|
|
constexpr int NETBIOS_PORT = 137;
|
|
|
|
DNS_Interpreter::DNS_Interpreter(analyzer::Analyzer* arg_analyzer) {
|
|
analyzer = arg_analyzer;
|
|
first_message = true;
|
|
is_netbios =
|
|
ntohs(analyzer->Conn()->OrigPort()) == NETBIOS_PORT || ntohs(analyzer->Conn()->RespPort()) == NETBIOS_PORT;
|
|
}
|
|
|
|
void DNS_Interpreter::ParseMessage(const u_char* data, int len, int is_query) {
|
|
// Every packet for every opcode starts with same size header.
|
|
int hdr_len = sizeof(detail::DNS_RawMsgHdr);
|
|
|
|
if ( len < hdr_len ) {
|
|
analyzer->Weird("DNS_truncated_len_lt_hdr_len");
|
|
return;
|
|
}
|
|
|
|
// The flags section may be different between the different opcodes, but the
|
|
// opcode is always in the same location. Parse out just that part of it here
|
|
// even though it will probably be reparsed later.
|
|
auto* hdr = (detail::DNS_RawMsgHdr*)data;
|
|
unsigned short flags = ntohs(hdr->flags);
|
|
int opcode = (flags & 0x7800) >> 11;
|
|
|
|
// NetBIOS registration and release messages look like regular DNS requests, so parse them as such
|
|
if ( opcode != DNS_OP_QUERY && ! is_netbios ) {
|
|
analyzer->Weird("DNS_unknown_opcode", util::fmt("%d", opcode));
|
|
analyzer->Conn()->CheckHistory(zeek::session::detail::HIST_UNKNOWN_PKT, 'X');
|
|
return;
|
|
}
|
|
|
|
detail::DNS_MsgInfo msg(hdr, is_query);
|
|
|
|
if ( first_message && msg.QR && is_query == 1 ) {
|
|
is_query = msg.is_query = 0;
|
|
|
|
if ( ! analyzer->Conn()->RespAddr().IsMulticast() )
|
|
analyzer->Conn()->FlipRoles();
|
|
}
|
|
|
|
first_message = false;
|
|
|
|
if ( dns_message )
|
|
analyzer->EnqueueConnEvent(dns_message, analyzer->ConnVal(), val_mgr->Bool(is_query), msg.BuildHdrVal(),
|
|
val_mgr->Count(len));
|
|
|
|
// There is a great deal of non-DNS traffic that runs on port 53.
|
|
// This should weed out most of it.
|
|
if ( zeek::detail::dns_max_queries > 0 && msg.qdcount > zeek::detail::dns_max_queries ) {
|
|
analyzer->AnalyzerViolation("DNS_Conn_count_too_large");
|
|
analyzer->Weird("DNS_Conn_count_too_large");
|
|
EndMessage(&msg);
|
|
return;
|
|
}
|
|
|
|
const u_char* msg_start = data; // needed for interpreting compression
|
|
|
|
data += hdr_len;
|
|
len -= hdr_len;
|
|
|
|
if ( ! ParseQuestions(&msg, data, len, msg_start) ) {
|
|
EndMessage(&msg);
|
|
return;
|
|
}
|
|
|
|
if ( ! ParseAnswers(&msg, msg.ancount, detail::DNS_ANSWER, data, len, msg_start) ) {
|
|
EndMessage(&msg);
|
|
return;
|
|
}
|
|
|
|
analyzer->AnalyzerConfirmation();
|
|
|
|
int skip_auth = zeek::detail::dns_skip_all_auth;
|
|
int skip_addl = zeek::detail::dns_skip_all_addl;
|
|
if ( msg.ancount > 0 ) { // We did an answer, so can potentially skip auth/addl.
|
|
static auto dns_skip_auth = id::find_val<TableVal>("dns_skip_auth");
|
|
static auto dns_skip_addl = id::find_val<TableVal>("dns_skip_addl");
|
|
auto server = make_intrusive<AddrVal>(analyzer->Conn()->RespAddr());
|
|
|
|
skip_auth = skip_auth || msg.nscount == 0 || dns_skip_auth->FindOrDefault(server);
|
|
skip_addl = skip_addl || msg.arcount == 0 || dns_skip_addl->FindOrDefault(server);
|
|
}
|
|
|
|
if ( skip_auth && skip_addl ) {
|
|
// No point doing further work parsing the message.
|
|
EndMessage(&msg);
|
|
return;
|
|
}
|
|
|
|
msg.skip_event = skip_auth;
|
|
if ( ! ParseAnswers(&msg, msg.nscount, detail::DNS_AUTHORITY, data, len, msg_start) ) {
|
|
EndMessage(&msg);
|
|
return;
|
|
}
|
|
|
|
if ( skip_addl ) {
|
|
// No point doing further work parsing the message.
|
|
EndMessage(&msg);
|
|
return;
|
|
}
|
|
|
|
msg.skip_event = skip_addl;
|
|
if ( ! ParseAnswers(&msg, msg.arcount, detail::DNS_ADDITIONAL, data, len, msg_start) ) {
|
|
EndMessage(&msg);
|
|
return;
|
|
}
|
|
|
|
EndMessage(&msg);
|
|
}
|
|
|
|
void DNS_Interpreter::EndMessage(detail::DNS_MsgInfo* msg) {
|
|
if ( dns_end )
|
|
analyzer->EnqueueConnEvent(dns_end, analyzer->ConnVal(), msg->BuildHdrVal());
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseQuestions(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, const u_char* msg_start) {
|
|
int n = msg->qdcount;
|
|
|
|
while ( n > 0 && ParseQuestion(msg, data, len, msg_start) )
|
|
--n;
|
|
return n == 0;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseAnswers(detail::DNS_MsgInfo* msg, int n, detail::DNS_AnswerType atype, const u_char*& data,
|
|
int& len, const u_char* msg_start) {
|
|
msg->answer_type = atype;
|
|
|
|
while ( n > 0 && ParseAnswer(msg, data, len, msg_start) )
|
|
--n;
|
|
|
|
return n == 0;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseQuestion(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, const u_char* msg_start) {
|
|
u_char name[513];
|
|
int name_len = sizeof(name) - 1;
|
|
|
|
u_char* name_end = ExtractName(data, len, name, name_len, msg_start, false);
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
if ( len < int(sizeof(short)) * 2 ) {
|
|
analyzer->Weird("DNS_truncated_quest_too_short");
|
|
return false;
|
|
}
|
|
|
|
EventHandlerPtr dns_event = nullptr;
|
|
|
|
if ( msg->QR == 0 )
|
|
dns_event = dns_request;
|
|
|
|
else if ( msg->QR == 1 && msg->ancount == 0 && msg->nscount == 0 && msg->arcount == 0 )
|
|
// Service rejected in some fashion, and it won't be reported
|
|
// via a returned RR because there aren't any.
|
|
dns_event = dns_rejected;
|
|
else
|
|
dns_event = dns_query_reply;
|
|
|
|
if ( dns_event && ! msg->skip_event ) {
|
|
String* original_name = new String(name, name_end - name, true);
|
|
|
|
// Downcase the Name to normalize it
|
|
for ( u_char* np = name; np < name_end; ++np )
|
|
if ( isupper(*np) )
|
|
*np = tolower(*np);
|
|
|
|
String* question_name = new String(name, name_end - name, true);
|
|
|
|
SendReplyOrRejectEvent(msg, dns_event, data, len, question_name, original_name);
|
|
}
|
|
else {
|
|
// Consume the unused type/class.
|
|
(void)ExtractShort(data, len);
|
|
(void)ExtractShort(data, len);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseAnswer(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, const u_char* msg_start) {
|
|
u_char name[513];
|
|
int name_len = sizeof(name) - 1;
|
|
|
|
u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
|
|
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
if ( len < int(sizeof(short)) * 2 ) {
|
|
analyzer->Weird("DNS_truncated_ans_too_short");
|
|
return false;
|
|
}
|
|
|
|
// Note that the exact meaning of some of these fields will be
|
|
// re-interpreted by other, more adventurous RR types.
|
|
|
|
msg->query_name = make_intrusive<StringVal>(new String(name, name_end - name, true));
|
|
msg->atype = detail::RR_Type(ExtractShort(data, len));
|
|
msg->aclass = ExtractShort(data, len);
|
|
msg->ttl = ExtractLong(data, len);
|
|
|
|
int rdlength = ExtractShort(data, len);
|
|
if ( rdlength > len ) {
|
|
analyzer->Weird("DNS_truncated_RR_rdlength_lt_len");
|
|
return false;
|
|
}
|
|
else if ( rdlength == 0 && len > 0 ) {
|
|
analyzer->Weird("DNS_zero_rdlength");
|
|
return false;
|
|
}
|
|
|
|
bool status;
|
|
switch ( msg->atype ) {
|
|
case detail::TYPE_A: status = ParseRR_A(msg, data, len, rdlength); break;
|
|
|
|
case detail::TYPE_A6:
|
|
case detail::TYPE_AAAA: status = ParseRR_AAAA(msg, data, len, rdlength); break;
|
|
|
|
case detail::TYPE_NS:
|
|
case detail::TYPE_CNAME:
|
|
case detail::TYPE_PTR: status = ParseRR_Name(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_SOA: status = ParseRR_SOA(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_WKS: status = ParseRR_WKS(msg, data, len, rdlength); break;
|
|
|
|
case detail::TYPE_HINFO: status = ParseRR_HINFO(msg, data, len, rdlength); break;
|
|
|
|
case detail::TYPE_MX: status = ParseRR_MX(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_TXT: status = ParseRR_TXT(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_SPF: status = ParseRR_SPF(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_CAA: status = ParseRR_CAA(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_NBS: status = ParseRR_NBS(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_SRV:
|
|
if ( ntohs(analyzer->Conn()->RespPort()) == NETBIOS_PORT ) {
|
|
// This is an NBSTAT (NetBIOS NODE STATUS) record.
|
|
// The SRV RFC reused the value that was already being
|
|
// used for this.
|
|
// We aren't parsing this yet.
|
|
status = true;
|
|
}
|
|
else
|
|
status = ParseRR_SRV(msg, data, len, rdlength, msg_start);
|
|
|
|
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;
|
|
|
|
case detail::TYPE_TSIG: status = ParseRR_TSIG(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_RRSIG: status = ParseRR_RRSIG(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_DNSKEY: status = ParseRR_DNSKEY(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_NSEC: status = ParseRR_NSEC(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_NSEC3: status = ParseRR_NSEC3(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_NSEC3PARAM: status = ParseRR_NSEC3PARAM(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_DS: status = ParseRR_DS(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_BINDS: status = ParseRR_BINDS(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_SSHFP: status = ParseRR_SSHFP(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_LOC: status = ParseRR_LOC(msg, data, len, rdlength, msg_start); break;
|
|
|
|
case detail::TYPE_SVCB: status = ParseRR_SVCB(msg, data, len, rdlength, msg_start, TYPE_SVCB); break;
|
|
|
|
case detail::TYPE_HTTPS: status = ParseRR_SVCB(msg, data, len, rdlength, msg_start, TYPE_HTTPS); break;
|
|
|
|
default:
|
|
|
|
if ( dns_unknown_reply && ! msg->skip_event )
|
|
analyzer->EnqueueConnEvent(dns_unknown_reply, analyzer->ConnVal(), msg->BuildHdrVal(),
|
|
msg->BuildAnswerVal());
|
|
|
|
analyzer->Weird("DNS_RR_unknown_type", util::fmt("%d", msg->atype));
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
status = true;
|
|
break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
u_char* DNS_Interpreter::ExtractName(const u_char*& data, int& len, u_char* name, int name_len, const u_char* msg_start,
|
|
bool downcase) {
|
|
u_char* name_start = name;
|
|
|
|
while ( ExtractLabel(data, len, name, name_len, msg_start) )
|
|
;
|
|
|
|
int n = name - name_start;
|
|
|
|
if ( n >= 255 )
|
|
analyzer->Weird("DNS_NAME_too_long");
|
|
|
|
if ( n >= 2 && name[-1] == '.' ) {
|
|
// Remove trailing dot.
|
|
--name;
|
|
name[0] = 0;
|
|
}
|
|
|
|
// Convert labels to lower case for consistency.
|
|
if ( downcase )
|
|
for ( u_char* np = name_start; np < name; ++np )
|
|
if ( isupper(*np) )
|
|
*np = tolower(*np);
|
|
|
|
return name;
|
|
}
|
|
|
|
bool DNS_Interpreter::ExtractLabel(const u_char*& data, int& len, u_char*& name, int& name_len,
|
|
const u_char* msg_start) {
|
|
if ( len <= 0 )
|
|
return false;
|
|
|
|
const u_char* orig_data = data;
|
|
int label_len = data[0];
|
|
|
|
++data;
|
|
--len;
|
|
|
|
if ( len <= 0 )
|
|
return false;
|
|
|
|
if ( label_len == 0 )
|
|
// Found terminating label.
|
|
return false;
|
|
|
|
if ( (label_len & 0xc0) == 0xc0 ) {
|
|
unsigned short offset = (label_len & ~0xc0) << 8;
|
|
|
|
offset |= *data;
|
|
|
|
++data;
|
|
--len;
|
|
|
|
if ( offset >= orig_data - msg_start ) {
|
|
// (You'd think that actually the offset should be
|
|
// at least 6 bytes below our current position:
|
|
// 2 bytes for a non-trivial label, plus 4 bytes for
|
|
// its class and type, which presumably are between
|
|
// our current location and the instance of the label.
|
|
// But actually this turns out not to be the case -
|
|
// sometimes compression points to compression.)
|
|
|
|
analyzer->Weird("DNS_label_forward_compress_offset");
|
|
return false;
|
|
}
|
|
|
|
// Recursively resolve name.
|
|
const u_char* recurse_data = msg_start + offset;
|
|
int recurse_max_len = orig_data - recurse_data;
|
|
|
|
u_char* name_end = ExtractName(recurse_data, recurse_max_len, name, name_len, msg_start);
|
|
|
|
name_len -= name_end - name;
|
|
name = name_end;
|
|
|
|
return false;
|
|
}
|
|
|
|
if ( label_len > len ) {
|
|
analyzer->Weird("DNS_label_len_gt_pkt");
|
|
data += len; // consume the rest of the packet
|
|
len = 0;
|
|
return false;
|
|
}
|
|
|
|
if ( label_len > 63 &&
|
|
// NetBIOS name service look ups can use longer labels.
|
|
ntohs(analyzer->Conn()->RespPort()) != NETBIOS_PORT ) {
|
|
analyzer->Weird("DNS_label_too_long");
|
|
return false;
|
|
}
|
|
|
|
if ( label_len >= name_len ) {
|
|
analyzer->Weird("DNS_label_len_gt_name_len");
|
|
return false;
|
|
}
|
|
|
|
memcpy(name, data, label_len);
|
|
name[label_len] = '.';
|
|
|
|
name += label_len + 1;
|
|
name_len -= label_len + 1;
|
|
|
|
data += label_len;
|
|
len -= label_len;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint16_t DNS_Interpreter::ExtractShort(const u_char*& data, int& len) {
|
|
if ( len < 2 )
|
|
return 0;
|
|
|
|
uint16_t val;
|
|
|
|
val = data[0] << 8;
|
|
|
|
++data;
|
|
--len;
|
|
|
|
val |= data[0];
|
|
|
|
++data;
|
|
--len;
|
|
|
|
return val;
|
|
}
|
|
|
|
uint32_t DNS_Interpreter::ExtractLong(const u_char*& data, int& len) {
|
|
if ( len < 4 )
|
|
return 0;
|
|
|
|
uint32_t val;
|
|
|
|
val = data[0] << 24;
|
|
val |= data[1] << 16;
|
|
val |= data[2] << 8;
|
|
val |= data[3];
|
|
|
|
data += sizeof(val);
|
|
len -= sizeof(val);
|
|
|
|
return val;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_Name(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
const u_char* data_start = data;
|
|
|
|
u_char name[513];
|
|
int name_len = sizeof(name) - 1;
|
|
|
|
u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
if ( data - data_start != rdlength ) {
|
|
analyzer->Weird("DNS_RR_length_mismatch");
|
|
}
|
|
|
|
EventHandlerPtr reply_event;
|
|
switch ( msg->atype ) {
|
|
case detail::TYPE_NS: reply_event = dns_NS_reply; break;
|
|
|
|
case detail::TYPE_CNAME:
|
|
case detail::TYPE_AAAA:
|
|
case detail::TYPE_A6: reply_event = dns_CNAME_reply; break;
|
|
|
|
case detail::TYPE_PTR: reply_event = dns_PTR_reply; break;
|
|
|
|
default: analyzer->Conn()->Internal("DNS_RR_bad_name"); reply_event = nullptr;
|
|
}
|
|
|
|
if ( reply_event && ! msg->skip_event )
|
|
analyzer->EnqueueConnEvent(reply_event, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
make_intrusive<StringVal>(new String(name, name_end - name, true)));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_SOA(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
const u_char* data_start = data;
|
|
|
|
u_char mname[513];
|
|
int mname_len = sizeof(mname) - 1;
|
|
|
|
u_char* mname_end = ExtractName(data, len, mname, mname_len, msg_start);
|
|
if ( ! mname_end )
|
|
return false;
|
|
|
|
u_char rname[513];
|
|
int rname_len = sizeof(rname) - 1;
|
|
|
|
u_char* rname_end = ExtractName(data, len, rname, rname_len, msg_start);
|
|
if ( ! rname_end )
|
|
return false;
|
|
|
|
if ( len < 20 )
|
|
return false;
|
|
|
|
uint32_t serial = ExtractLong(data, len);
|
|
uint32_t refresh = ExtractLong(data, len);
|
|
uint32_t retry = ExtractLong(data, len);
|
|
uint32_t expire = ExtractLong(data, len);
|
|
uint32_t minimum = ExtractLong(data, len);
|
|
|
|
if ( data - data_start != rdlength )
|
|
analyzer->Weird("DNS_RR_length_mismatch");
|
|
|
|
if ( dns_SOA_reply && ! msg->skip_event ) {
|
|
static auto dns_soa = id::find_type<RecordType>("dns_soa");
|
|
auto r = make_intrusive<RecordVal>(dns_soa);
|
|
r->Assign(0, new String(mname, mname_end - mname, true));
|
|
r->Assign(1, new String(rname, rname_end - rname, true));
|
|
r->Assign(2, serial);
|
|
r->AssignInterval(3, double(refresh));
|
|
r->AssignInterval(4, double(retry));
|
|
r->AssignInterval(5, double(expire));
|
|
r->AssignInterval(6, double(minimum));
|
|
|
|
analyzer->EnqueueConnEvent(dns_SOA_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
std::move(r));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_MX(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
const u_char* data_start = data;
|
|
|
|
int preference = ExtractShort(data, len);
|
|
|
|
u_char name[513];
|
|
int name_len = sizeof(name) - 1;
|
|
|
|
u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
if ( data - data_start != rdlength )
|
|
analyzer->Weird("DNS_RR_length_mismatch");
|
|
|
|
if ( dns_MX_reply && ! msg->skip_event )
|
|
analyzer->EnqueueConnEvent(dns_MX_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
make_intrusive<StringVal>(new String(name, name_end - name, true)),
|
|
val_mgr->Count(preference));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_NBS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_SRV(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
const u_char* data_start = data;
|
|
|
|
unsigned int priority = ExtractShort(data, len);
|
|
unsigned int weight = ExtractShort(data, len);
|
|
unsigned int port = ExtractShort(data, len);
|
|
|
|
u_char name[513];
|
|
int name_len = sizeof(name) - 1;
|
|
|
|
u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
if ( data - data_start != rdlength )
|
|
analyzer->Weird("DNS_RR_length_mismatch");
|
|
|
|
if ( dns_SRV_reply && ! msg->skip_event )
|
|
analyzer->EnqueueConnEvent(dns_SRV_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
make_intrusive<StringVal>(new String(name, name_end - name, true)),
|
|
val_mgr->Count(priority), val_mgr->Count(weight), val_mgr->Count(port));
|
|
|
|
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 )
|
|
analyzer->EnqueueConnEvent(dns_EDNS_addl, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildEDNS_Val());
|
|
|
|
// parse EDNS options. length has to be at least 4 to parse out the option
|
|
// code and length.
|
|
while ( len >= 4 ) {
|
|
uint16_t option_code = ExtractShort(data, len);
|
|
int option_len = ExtractShort(data, len);
|
|
// check for invalid option length
|
|
if ( (option_len > len) ) {
|
|
break;
|
|
}
|
|
len -= option_len;
|
|
|
|
// TODO: Implement additional option codes
|
|
switch ( option_code ) {
|
|
case detail::TYPE_ECS: {
|
|
// must be 4 bytes + variable number of octets for address
|
|
if ( option_len <= 4 ) {
|
|
analyzer->Weird("EDNS_ECS_invalid_option_len");
|
|
data += option_len;
|
|
break;
|
|
}
|
|
|
|
detail::EDNS_ECS opt{};
|
|
uint16_t ecs_family = ExtractShort(data, option_len);
|
|
uint16_t source_scope = ExtractShort(data, option_len);
|
|
opt.ecs_src_pfx_len = (source_scope >> 8) & 0xff;
|
|
opt.ecs_scp_pfx_len = source_scope & 0xff;
|
|
|
|
// ADDRESS, variable number of octets, contains either an IPv4 or
|
|
// IPv6 address, depending on FAMILY, which MUST be truncated to the
|
|
// number of bits indicated by the SOURCE PREFIX-LENGTH field,
|
|
// padding with 0 bits to pad to the end of the last octet needed.
|
|
if ( ecs_family == L3_IPV4 ) {
|
|
if ( opt.ecs_src_pfx_len > 32 ) {
|
|
analyzer->Weird("EDNS_ECS_invalid_addr_v4_prefix",
|
|
util::fmt("%" PRIu16 " bits", opt.ecs_src_pfx_len));
|
|
data += option_len;
|
|
break;
|
|
}
|
|
|
|
if ( opt.ecs_src_pfx_len > option_len * 8 ) {
|
|
analyzer->Weird("EDNS_ECS_invalid_addr_v4", util::fmt("need %" PRIu16 " bits, have %d bits",
|
|
opt.ecs_src_pfx_len, option_len * 8));
|
|
data += option_len;
|
|
break;
|
|
}
|
|
|
|
opt.ecs_family = make_intrusive<StringVal>("v4");
|
|
uint32_t addr = 0;
|
|
uint16_t shift_factor = 3;
|
|
int bits_left = opt.ecs_src_pfx_len;
|
|
|
|
while ( bits_left > 0 ) {
|
|
addr |= data[0] << (shift_factor * 8);
|
|
data++;
|
|
shift_factor--;
|
|
option_len--;
|
|
bits_left -= 8;
|
|
}
|
|
|
|
addr = htonl(addr);
|
|
opt.ecs_addr = make_intrusive<AddrVal>(addr);
|
|
}
|
|
else if ( ecs_family == L3_IPV6 ) {
|
|
if ( opt.ecs_src_pfx_len > 128 ) {
|
|
analyzer->Weird("EDNS_ECS_invalid_addr_v6_prefix",
|
|
util::fmt("%" PRIu16 " bits", opt.ecs_src_pfx_len));
|
|
data += option_len;
|
|
break;
|
|
}
|
|
|
|
if ( opt.ecs_src_pfx_len > option_len * 8 ) {
|
|
analyzer->Weird("EDNS_ECS_invalid_addr_v6", util::fmt("need %" PRIu16 " bits, have %d bits",
|
|
opt.ecs_src_pfx_len, option_len * 8));
|
|
data += option_len;
|
|
break;
|
|
}
|
|
|
|
opt.ecs_family = make_intrusive<StringVal>("v6");
|
|
uint32_t addr[4] = {0};
|
|
uint16_t shift_factor = 15;
|
|
int bits_left = opt.ecs_src_pfx_len;
|
|
int i = 0;
|
|
|
|
while ( bits_left > 0 ) {
|
|
addr[i / 4] |= data[0] << ((shift_factor % 4) * 8);
|
|
data++;
|
|
i++;
|
|
shift_factor--;
|
|
option_len--;
|
|
bits_left -= 8;
|
|
}
|
|
|
|
for ( uint32_t& a : addr ) {
|
|
a = htonl(a);
|
|
}
|
|
opt.ecs_addr = make_intrusive<AddrVal>(addr);
|
|
}
|
|
else {
|
|
// non ipv4/ipv6 family address
|
|
data += option_len;
|
|
break;
|
|
}
|
|
|
|
analyzer->EnqueueConnEvent(dns_EDNS_ecs, analyzer->ConnVal(), msg->BuildHdrVal(),
|
|
msg->BuildEDNS_ECS_Val(&opt));
|
|
data += option_len;
|
|
break;
|
|
} // END EDNS ECS
|
|
|
|
case TYPE_TCP_KA: {
|
|
EDNS_TCP_KEEPALIVE edns_tcp_keepalive{true, 0};
|
|
if ( option_len == 0 || option_len == 2 ) {
|
|
// 0 bytes is permitted by RFC 7828, showing that the timeout value is
|
|
// omitted.
|
|
if ( option_len == 2 ) {
|
|
edns_tcp_keepalive.keepalive_timeout = ExtractShort(data, option_len);
|
|
edns_tcp_keepalive.keepalive_timeout_omitted = false;
|
|
}
|
|
|
|
if ( analyzer->Conn()->ConnTransport() == TRANSPORT_UDP ) {
|
|
/*
|
|
* Based on RFC 7828 (3.2.1/3.2.2), clients and servers MUST NOT
|
|
* negotiate TCP Keepalive timeout in DNS-over-UDP.
|
|
*/
|
|
analyzer->Weird("EDNS_TCP_Keepalive_In_UDP");
|
|
}
|
|
|
|
analyzer->EnqueueConnEvent(dns_EDNS_tcp_keepalive, analyzer->ConnVal(), msg->BuildHdrVal(),
|
|
msg->BuildEDNS_TCP_KA_Val(&edns_tcp_keepalive));
|
|
}
|
|
else {
|
|
// error. MUST BE 0 or 2 bytes. skip
|
|
data += option_len;
|
|
}
|
|
break;
|
|
} // END EDNS TCP KEEPALIVE
|
|
|
|
case TYPE_COOKIE: {
|
|
EDNS_COOKIE cookie{};
|
|
if ( option_len != 8 && ! (option_len >= 16 && option_len <= 40) ) {
|
|
/*
|
|
* option length for DNS Cookie must be 8 bytes (with client cookie only)
|
|
* OR
|
|
* between 16 bytes to 40 bytes (with an 8 bytes client and an 8 to 32 bytes
|
|
* server cookie)
|
|
*/
|
|
data += option_len;
|
|
break;
|
|
}
|
|
|
|
int client_cookie_len = 8;
|
|
int server_cookie_len = option_len - client_cookie_len;
|
|
|
|
cookie.client_cookie = ExtractStream(data, client_cookie_len, client_cookie_len);
|
|
cookie.server_cookie = nullptr;
|
|
|
|
if ( server_cookie_len >= 8 ) {
|
|
cookie.server_cookie = ExtractStream(data, server_cookie_len, server_cookie_len);
|
|
}
|
|
|
|
analyzer->EnqueueConnEvent(dns_EDNS_cookie, analyzer->ConnVal(), msg->BuildHdrVal(),
|
|
msg->BuildEDNS_COOKIE_Val(&cookie));
|
|
|
|
break;
|
|
} // END EDNS COOKIE
|
|
|
|
default: {
|
|
data += option_len;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( len > 0 ) {
|
|
analyzer->Weird("EDNS_truncated_option");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DNS_Interpreter::ExtractOctets(const u_char*& data, int& len, String** p) {
|
|
uint16_t dlen = ExtractShort(data, len);
|
|
dlen = std::min(len, static_cast<int>(dlen));
|
|
|
|
if ( p )
|
|
*p = new String(data, dlen, false);
|
|
|
|
data += dlen;
|
|
len -= dlen;
|
|
}
|
|
|
|
String* DNS_Interpreter::ExtractStream(const u_char*& data, int& len, int l) {
|
|
l = std::max(l, 0);
|
|
int dlen = std::min(len, l); // Len in bytes of the algorithm use
|
|
auto rval = new String(data, dlen, false);
|
|
|
|
data += dlen;
|
|
len -= dlen;
|
|
return rval;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_TSIG(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
const u_char* data_start = data;
|
|
u_char alg_name[1024];
|
|
int alg_name_len = sizeof(alg_name) - 1;
|
|
|
|
u_char* alg_name_end = ExtractName(data, len, alg_name, alg_name_len, msg_start);
|
|
|
|
if ( ! alg_name_end )
|
|
return false;
|
|
|
|
uint32_t sign_time_sec = ExtractLong(data, len);
|
|
unsigned int sign_time_msec = ExtractShort(data, len);
|
|
unsigned int fudge = ExtractShort(data, len);
|
|
String* request_MAC;
|
|
ExtractOctets(data, len, dns_TSIG_addl ? &request_MAC : nullptr);
|
|
unsigned int orig_id = ExtractShort(data, len);
|
|
unsigned int rr_error = ExtractShort(data, len);
|
|
ExtractOctets(data, len, nullptr); // Other Data
|
|
|
|
if ( dns_TSIG_addl ) {
|
|
detail::TSIG_DATA tsig;
|
|
tsig.alg_name = new String(alg_name, alg_name_end - alg_name, true);
|
|
tsig.sig = request_MAC;
|
|
tsig.time_s = sign_time_sec;
|
|
tsig.time_ms = sign_time_msec;
|
|
tsig.fudge = fudge;
|
|
tsig.orig_id = orig_id;
|
|
tsig.rr_error = rr_error;
|
|
|
|
analyzer->EnqueueConnEvent(dns_TSIG_addl, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildTSIG_Val(&tsig));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_TKEY(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_TKEY || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 16 )
|
|
return false;
|
|
|
|
const u_char* data_start = data;
|
|
u_char alg_name[513];
|
|
int alg_name_len = sizeof(alg_name) - 1;
|
|
|
|
u_char* alg_name_end = ExtractName(data, len, alg_name, alg_name_len, msg_start);
|
|
|
|
if ( ! alg_name_end )
|
|
return false;
|
|
|
|
uint32_t inception = ExtractLong(data, len);
|
|
uint32_t expiration = ExtractLong(data, len);
|
|
uint16_t mode = ExtractShort(data, len);
|
|
uint16_t error = ExtractShort(data, len);
|
|
String* key_data;
|
|
ExtractOctets(data, len, dns_TKEY ? &key_data : nullptr);
|
|
ExtractOctets(data, len, nullptr); // Other data
|
|
|
|
if ( dns_TKEY ) {
|
|
detail::TKEY_DATA tkey;
|
|
tkey.alg_name = new String(alg_name, int(alg_name_end - alg_name), true);
|
|
tkey.inception = inception;
|
|
tkey.expiration = expiration;
|
|
tkey.mode = mode;
|
|
tkey.error = error;
|
|
tkey.key = key_data;
|
|
analyzer->EnqueueConnEvent(dns_TKEY, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildTKEY_Val(&tkey));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_RRSIG(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_RRSIG || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 18 )
|
|
return false;
|
|
|
|
unsigned int type_covered = ExtractShort(data, len);
|
|
// split the two bytes for algo and labels extraction
|
|
uint32_t algo_lab = ExtractShort(data, len);
|
|
unsigned int algo = (algo_lab >> 8) & 0xff;
|
|
unsigned int lab = algo_lab & 0xff;
|
|
|
|
uint32_t orig_ttl = ExtractLong(data, len);
|
|
uint32_t sign_exp = ExtractLong(data, len);
|
|
uint32_t sign_incp = ExtractLong(data, len);
|
|
unsigned int key_tag = ExtractShort(data, len);
|
|
|
|
// implement signer's name with the msg_start offset
|
|
const u_char* data_start = data;
|
|
u_char name[513];
|
|
int name_len = sizeof(name) - 1;
|
|
|
|
u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
int sig_len = rdlength - ((data - data_start) + 18);
|
|
detail::DNSSEC_Algo dsa = detail::DNSSEC_Algo(algo);
|
|
String* sign = ExtractStream(data, len, sig_len);
|
|
|
|
switch ( dsa ) {
|
|
case detail::RSA_MD5: analyzer->Weird("DNSSEC_RRSIG_NotRecommended_ZoneSignAlgo", util::fmt("%d", algo)); break;
|
|
case detail::Diffie_Hellman:
|
|
case detail::DSA_SHA1:
|
|
case detail::Elliptic_Curve:
|
|
case detail::RSA_SHA1:
|
|
case detail::DSA_NSEC3_SHA1:
|
|
case detail::RSA_SHA1_NSEC3_SHA1:
|
|
case detail::RSA_SHA256:
|
|
case detail::RSA_SHA512:
|
|
case detail::GOST_R_34_10_2001:
|
|
case detail::ECDSA_curveP256withSHA256:
|
|
case detail::ECDSA_curveP384withSHA384:
|
|
case detail::Ed25519:
|
|
case detail::Ed448: break;
|
|
case detail::Indirect: analyzer->Weird("DNSSEC_RRSIG_Indirect_ZoneSignAlgo", util::fmt("%d", algo)); break;
|
|
case detail::PrivateDNS: analyzer->Weird("DNSSEC_RRSIG_PrivateDNS_ZoneSignAlgo", util::fmt("%d", algo)); break;
|
|
case detail::PrivateOID: analyzer->Weird("DNSSEC_RRSIG_PrivateOID_ZoneSignAlgo", util::fmt("%d", algo)); break;
|
|
default: analyzer->Weird("DNSSEC_RRSIG_unknown_ZoneSignAlgo", util::fmt("%d", algo)); break;
|
|
}
|
|
|
|
if ( dns_RRSIG ) {
|
|
detail::RRSIG_DATA rrsig;
|
|
rrsig.type_covered = type_covered;
|
|
rrsig.algorithm = algo;
|
|
rrsig.labels = lab;
|
|
rrsig.orig_ttl = orig_ttl;
|
|
rrsig.sig_exp = sign_exp;
|
|
rrsig.sig_incep = sign_incp;
|
|
rrsig.key_tag = key_tag;
|
|
rrsig.signer_name = new String(name, name_end - name, true);
|
|
rrsig.signature = sign;
|
|
|
|
analyzer->EnqueueConnEvent(dns_RRSIG, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildRRSIG_Val(&rrsig));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_DNSKEY(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_DNSKEY || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 4 )
|
|
return false;
|
|
|
|
auto dflags = ExtractShort(data, len);
|
|
// split the two bytes for protocol and algorithm extraction
|
|
auto proto_algo = ExtractShort(data, len);
|
|
unsigned int dprotocol = (proto_algo >> 8) & 0xff;
|
|
unsigned int dalgorithm = proto_algo & 0xff;
|
|
detail::DNSSEC_Algo dsa = detail::DNSSEC_Algo(dalgorithm);
|
|
// Evaluating the size of remaining bytes for Public Key
|
|
String* key = ExtractStream(data, len, rdlength - 4);
|
|
|
|
// flags bit 7: zone key
|
|
// flags bit 8: revoked
|
|
// flags bit 15: Secure Entry Point, key signing key
|
|
if ( (dflags & 0xfe7e) != 0 )
|
|
analyzer->Weird("DNSSEC_DNSKEY_Invalid_Flag", util::fmt("%d", dflags));
|
|
|
|
// flags bit 7, 8, and 15 all set
|
|
if ( (dflags & 0x0181) == 0x0181 )
|
|
analyzer->Weird("DNSSEC_DNSKEY_Revoked_KSK", util::fmt("%d", dflags));
|
|
|
|
if ( dprotocol != 3 )
|
|
analyzer->Weird("DNSSEC_DNSKEY_Invalid_Protocol", util::fmt("%d", dprotocol));
|
|
|
|
switch ( dsa ) {
|
|
case detail::RSA_MD5:
|
|
analyzer->Weird("DNSSEC_DNSKEY_NotRecommended_ZoneSignAlgo", util::fmt("%d", dalgorithm));
|
|
break;
|
|
case detail::Diffie_Hellman:
|
|
case detail::DSA_SHA1:
|
|
case detail::Elliptic_Curve:
|
|
case detail::RSA_SHA1:
|
|
case detail::DSA_NSEC3_SHA1:
|
|
case detail::RSA_SHA1_NSEC3_SHA1:
|
|
case detail::RSA_SHA256:
|
|
case detail::RSA_SHA512:
|
|
case detail::GOST_R_34_10_2001:
|
|
case detail::ECDSA_curveP256withSHA256:
|
|
case detail::ECDSA_curveP384withSHA384:
|
|
case detail::Ed25519:
|
|
case detail::Ed448: break;
|
|
case detail::Indirect:
|
|
analyzer->Weird("DNSSEC_DNSKEY_Indirect_ZoneSignAlgo", util::fmt("%d", dalgorithm));
|
|
break;
|
|
case detail::PrivateDNS:
|
|
analyzer->Weird("DNSSEC_DNSKEY_PrivateDNS_ZoneSignAlgo", util::fmt("%d", dalgorithm));
|
|
break;
|
|
case detail::PrivateOID:
|
|
analyzer->Weird("DNSSEC_DNSKEY_PrivateOID_ZoneSignAlgo", util::fmt("%d", dalgorithm));
|
|
break;
|
|
default: analyzer->Weird("DNSSEC_DNSKEY_unknown_ZoneSignAlgo", util::fmt("%d", dalgorithm)); break;
|
|
}
|
|
|
|
if ( dns_DNSKEY ) {
|
|
detail::DNSKEY_DATA dnskey;
|
|
dnskey.dflags = dflags;
|
|
dnskey.dalgorithm = dalgorithm;
|
|
dnskey.dprotocol = dprotocol;
|
|
dnskey.public_key = key;
|
|
|
|
analyzer->EnqueueConnEvent(dns_DNSKEY, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildDNSKEY_Val(&dnskey));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_NSEC(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_NSEC || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
const u_char* data_start = data;
|
|
u_char name[513];
|
|
int name_len = sizeof(name) - 1;
|
|
|
|
u_char* name_end = ExtractName(data, len, name, name_len, msg_start);
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
int typebitmaps_len = rdlength - (data - data_start);
|
|
|
|
auto char_strings = make_intrusive<VectorVal>(id::string_vec);
|
|
|
|
while ( typebitmaps_len > 0 && len > 0 ) {
|
|
uint32_t block_bmlen = ExtractShort(data, len);
|
|
unsigned int win_blck = (block_bmlen >> 8) & 0xff;
|
|
unsigned int bmlen = block_bmlen & 0xff;
|
|
|
|
if ( bmlen == 0 ) {
|
|
analyzer->Weird("DNSSEC_NSEC_bitmapLen0", util::fmt("%d", win_blck));
|
|
break;
|
|
}
|
|
|
|
String* bitmap = ExtractStream(data, len, bmlen);
|
|
char_strings->Assign(char_strings->Size(), make_intrusive<StringVal>(bitmap));
|
|
typebitmaps_len = typebitmaps_len - (2 + bmlen);
|
|
}
|
|
|
|
if ( dns_NSEC )
|
|
analyzer->EnqueueConnEvent(dns_NSEC, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
make_intrusive<StringVal>(new String(name, name_end - name, true)),
|
|
std::move(char_strings));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_NSEC3(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_NSEC3 || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 6 )
|
|
return false;
|
|
|
|
const u_char* data_start = data;
|
|
uint32_t halgo_flags = ExtractShort(data, len);
|
|
unsigned int hash_algo = (halgo_flags >> 8) & 0xff;
|
|
unsigned int nsec_flags = halgo_flags & 0xff;
|
|
unsigned int iter = ExtractShort(data, len);
|
|
|
|
uint8_t salt_len = 0;
|
|
|
|
if ( len > 0 ) {
|
|
salt_len = data[0];
|
|
++data;
|
|
--len;
|
|
}
|
|
|
|
auto salt_val = ExtractStream(data, len, static_cast<int>(salt_len));
|
|
|
|
uint8_t hash_len = 0;
|
|
|
|
if ( len > 0 ) {
|
|
hash_len = data[0];
|
|
++data;
|
|
--len;
|
|
}
|
|
|
|
auto hash_val = ExtractStream(data, len, static_cast<int>(hash_len));
|
|
|
|
int typebitmaps_len = rdlength - (data - data_start);
|
|
|
|
auto char_strings = make_intrusive<VectorVal>(id::string_vec);
|
|
|
|
while ( typebitmaps_len > 0 && len > 0 ) {
|
|
uint32_t block_bmlen = ExtractShort(data, len);
|
|
unsigned int win_blck = (block_bmlen >> 8) & 0xff;
|
|
unsigned int bmlen = block_bmlen & 0xff;
|
|
|
|
if ( bmlen == 0 ) {
|
|
analyzer->Weird("DNSSEC_NSEC3_bitmapLen0", util::fmt("%d", win_blck));
|
|
break;
|
|
}
|
|
|
|
String* bitmap = ExtractStream(data, len, bmlen);
|
|
char_strings->Assign(char_strings->Size(), make_intrusive<StringVal>(bitmap));
|
|
typebitmaps_len = typebitmaps_len - (2 + bmlen);
|
|
}
|
|
|
|
if ( dns_NSEC3 ) {
|
|
detail::NSEC3_DATA nsec3;
|
|
nsec3.nsec_flags = nsec_flags;
|
|
nsec3.nsec_hash_algo = hash_algo;
|
|
nsec3.nsec_iter = iter;
|
|
nsec3.nsec_salt_len = salt_len;
|
|
nsec3.nsec_salt = salt_val;
|
|
nsec3.nsec_hlen = hash_len;
|
|
nsec3.nsec_hash = hash_val;
|
|
nsec3.bitmaps = std::move(char_strings);
|
|
|
|
analyzer->EnqueueConnEvent(dns_NSEC3, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildNSEC3_Val(&nsec3));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_NSEC3PARAM(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_NSEC3PARAM || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 5 )
|
|
return false;
|
|
|
|
uint32_t halgo_flags = ExtractShort(data, len);
|
|
unsigned int hash_algo = (halgo_flags >> 8) & 0xff;
|
|
unsigned int nsec_flags = halgo_flags & 0xff;
|
|
unsigned int iter = ExtractShort(data, len);
|
|
|
|
uint8_t salt_len = 0;
|
|
|
|
if ( len > 0 ) {
|
|
salt_len = data[0];
|
|
++data;
|
|
--len;
|
|
}
|
|
|
|
auto salt_value = ExtractStream(data, len, static_cast<int>(salt_len));
|
|
|
|
if ( dns_NSEC3PARAM ) {
|
|
detail::NSEC3PARAM_DATA nsec3param;
|
|
nsec3param.nsec_flags = nsec_flags;
|
|
nsec3param.nsec_hash_algo = hash_algo;
|
|
nsec3param.nsec_iter = iter;
|
|
nsec3param.nsec_salt_len = salt_len;
|
|
nsec3param.nsec_salt = salt_value;
|
|
|
|
analyzer->EnqueueConnEvent(dns_NSEC3PARAM, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildNSEC3PARAM_Val(&nsec3param));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_DS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_DS || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 4 )
|
|
return false;
|
|
|
|
unsigned int ds_key_tag = ExtractShort(data, len);
|
|
// split the two bytes for algorithm and digest type extraction
|
|
uint32_t ds_algo_dtype = ExtractShort(data, len);
|
|
unsigned int ds_algo = (ds_algo_dtype >> 8) & 0xff;
|
|
unsigned int ds_dtype = ds_algo_dtype & 0xff;
|
|
detail::DNSSEC_Digest ds_digest_type = detail::DNSSEC_Digest(ds_dtype);
|
|
String* ds_digest = ExtractStream(data, len, rdlength - 4);
|
|
|
|
switch ( ds_digest_type ) {
|
|
case detail::SHA1:
|
|
case detail::SHA256:
|
|
case detail::GOST_R_34_11_94:
|
|
case detail::SHA384: break;
|
|
case detail::reserved: analyzer->Weird("DNSSEC_DS_ReservedDigestType", util::fmt("%d", ds_dtype)); break;
|
|
default: analyzer->Weird("DNSSEC_DS_unknown_DigestType", util::fmt("%d", ds_dtype)); break;
|
|
}
|
|
|
|
if ( dns_DS ) {
|
|
detail::DS_DATA ds;
|
|
ds.key_tag = ds_key_tag;
|
|
ds.algorithm = ds_algo;
|
|
ds.digest_type = ds_dtype;
|
|
ds.digest_val = ds_digest;
|
|
|
|
analyzer->EnqueueConnEvent(dns_DS, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildDS_Val(&ds));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_BINDS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_BINDS || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 5 )
|
|
return false;
|
|
|
|
uint32_t algo_keyid_rflag = ExtractLong(data, len);
|
|
|
|
unsigned int algo = (algo_keyid_rflag >> 24) & 0xff;
|
|
unsigned int keyid1 = (algo_keyid_rflag >> 16) & 0xff;
|
|
unsigned int keyid2 = (algo_keyid_rflag >> 8) & 0xff;
|
|
unsigned int rmflag = algo_keyid_rflag & 0xff;
|
|
|
|
unsigned int keyid = (keyid1 << 8) | keyid2;
|
|
|
|
String* completeflag = ExtractStream(data, len, rdlength - 4);
|
|
|
|
// We exposed the complete flag as a string to script land previously,
|
|
// but there should only ever be a single byte, so raise a weird if
|
|
// it is longer than that.
|
|
//
|
|
// https://bind9.readthedocs.io/en/latest/chapter5.html#monitoring-with-private-type-records
|
|
if ( completeflag->Len() > 1 )
|
|
analyzer->Weird("DNS_BINDS_complete_flag_length", util::fmt("%d", completeflag->Len()));
|
|
|
|
if ( dns_BINDS ) {
|
|
detail::BINDS_DATA binds;
|
|
binds.algorithm = algo;
|
|
binds.key_id = keyid;
|
|
binds.removal_flag = rmflag;
|
|
binds.complete_flag = completeflag;
|
|
|
|
analyzer->EnqueueConnEvent(dns_BINDS, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildBINDS_Val(&binds));
|
|
}
|
|
|
|
delete completeflag;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_SSHFP(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_SSHFP || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 2 )
|
|
return false;
|
|
|
|
uint32_t algo_fptype = ExtractShort(data, len);
|
|
unsigned int algo = (algo_fptype >> 8) & 0xff;
|
|
unsigned int fptype = algo_fptype & 0xff;
|
|
|
|
String* fingerprint = ExtractStream(data, len, rdlength - 2);
|
|
|
|
if ( dns_SSHFP ) {
|
|
analyzer->EnqueueConnEvent(dns_SSHFP, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
val_mgr->Count(algo), val_mgr->Count(fptype),
|
|
make_intrusive<StringVal>(fingerprint));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_LOC(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_LOC || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
if ( len < 15 )
|
|
return false;
|
|
|
|
// split the two bytes for version and size extraction
|
|
uint32_t ver_size = ExtractShort(data, len);
|
|
unsigned int version = (ver_size >> 8) & 0xff;
|
|
unsigned int size = ver_size & 0xff;
|
|
|
|
// split the two bytes for horizontal and vertical precision extraction
|
|
uint32_t horiz_vert = ExtractShort(data, len);
|
|
unsigned int horiz_pre = (horiz_vert >> 8) & 0xff;
|
|
unsigned int vert_pre = horiz_vert & 0xff;
|
|
|
|
uint32_t latitude = ExtractLong(data, len);
|
|
uint32_t longitude = ExtractLong(data, len);
|
|
uint32_t altitude = ExtractLong(data, len);
|
|
|
|
if ( version != 0 ) {
|
|
analyzer->Weird("DNS_LOC_version_unrecognized", util::fmt("%d", version));
|
|
}
|
|
|
|
if ( dns_LOC ) {
|
|
detail::LOC_DATA loc;
|
|
loc.version = version;
|
|
loc.size = size;
|
|
loc.horiz_pre = horiz_pre;
|
|
loc.vert_pre = vert_pre;
|
|
loc.latitude = latitude;
|
|
loc.longitude = longitude;
|
|
loc.altitude = altitude;
|
|
|
|
analyzer->EnqueueConnEvent(dns_LOC, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildLOC_Val(&loc));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_A(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
|
|
if ( rdlength != 4 ) {
|
|
analyzer->Weird("DNS_RR_bad_length");
|
|
return false;
|
|
}
|
|
|
|
uint32_t addr = ExtractLong(data, len);
|
|
|
|
if ( dns_A_reply && ! msg->skip_event )
|
|
analyzer->EnqueueConnEvent(dns_A_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
make_intrusive<AddrVal>(htonl(addr)));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_AAAA(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
|
|
uint32_t addr[4];
|
|
|
|
// Intentionally leaving this as a normal loop because it's more descriptive.
|
|
// NOLINTNEXTLINE(modernize-loop-convert)
|
|
for ( size_t i = 0; i < 4; i++ ) {
|
|
addr[i] = htonl(ExtractLong(data, len));
|
|
|
|
if ( len < 0 ) {
|
|
if ( msg->atype == detail::TYPE_AAAA )
|
|
analyzer->Weird("DNS_AAAA_neg_length");
|
|
else
|
|
analyzer->Weird("DNS_A6_neg_length");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
EventHandlerPtr event;
|
|
if ( msg->atype == detail::TYPE_AAAA )
|
|
event = dns_AAAA_reply;
|
|
else
|
|
event = dns_A6_reply;
|
|
|
|
if ( event && ! msg->skip_event )
|
|
analyzer->EnqueueConnEvent(event, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
make_intrusive<AddrVal>(addr));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_WKS(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength) {
|
|
if ( ! dns_WKS_reply || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
// TODO: Pass the ports as parameters to the event
|
|
analyzer->EnqueueConnEvent(dns_WKS_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal());
|
|
|
|
// TODO: Return a status which reflects if the port parameters were successfully parsed
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
auto cpu = extract_char_string(analyzer, data, len, rdlength);
|
|
auto os = extract_char_string(analyzer, data, len, rdlength);
|
|
|
|
analyzer->EnqueueConnEvent(dns_HINFO_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(), cpu,
|
|
os);
|
|
|
|
return rdlength == 0;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_TXT(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_TXT_reply || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
auto char_strings = make_intrusive<VectorVal>(id::string_vec);
|
|
StringValPtr char_string;
|
|
|
|
while ( (char_string = extract_char_string(analyzer, data, len, rdlength)) )
|
|
char_strings->Assign(char_strings->Size(), std::move(char_string));
|
|
|
|
if ( dns_TXT_reply )
|
|
analyzer->EnqueueConnEvent(dns_TXT_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
std::move(char_strings));
|
|
|
|
return rdlength == 0;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_SPF(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_SPF_reply || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
auto char_strings = make_intrusive<VectorVal>(id::string_vec);
|
|
StringValPtr char_string;
|
|
|
|
while ( (char_string = extract_char_string(analyzer, data, len, rdlength)) )
|
|
char_strings->Assign(char_strings->Size(), std::move(char_string));
|
|
|
|
if ( dns_SPF_reply )
|
|
analyzer->EnqueueConnEvent(dns_SPF_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
std::move(char_strings));
|
|
|
|
return rdlength == 0;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_CAA(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start) {
|
|
if ( ! dns_CAA_reply || msg->skip_event ) {
|
|
data += rdlength;
|
|
len -= rdlength;
|
|
return true;
|
|
}
|
|
|
|
unsigned int flags = ExtractShort(data, len);
|
|
unsigned int tagLen = flags & 0xff;
|
|
flags = flags >> 8;
|
|
rdlength -= 2;
|
|
if ( (int)tagLen >= rdlength ) {
|
|
analyzer->Weird("DNS_CAA_char_str_past_rdlen");
|
|
return false;
|
|
}
|
|
String* tag = new String(data, tagLen, true);
|
|
len -= tagLen;
|
|
data += tagLen;
|
|
rdlength -= tagLen;
|
|
String* value = new String(data, rdlength, false);
|
|
|
|
len -= value->Len();
|
|
data += value->Len();
|
|
rdlength -= value->Len();
|
|
|
|
if ( dns_CAA_reply )
|
|
analyzer->EnqueueConnEvent(dns_CAA_reply, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
val_mgr->Count(flags), make_intrusive<StringVal>(tag),
|
|
make_intrusive<StringVal>(value));
|
|
else {
|
|
delete tag;
|
|
delete value;
|
|
}
|
|
|
|
return rdlength == 0;
|
|
}
|
|
|
|
bool DNS_Interpreter::ParseRR_SVCB(detail::DNS_MsgInfo* msg, const u_char*& data, int& len, int rdlength,
|
|
const u_char* msg_start, const RR_Type& svcb_type) {
|
|
const u_char* data_start = data;
|
|
// the smallest SVCB/HTTPS rr is 3 bytes:
|
|
// the first 2 bytes are for the svc priority, and the third byte is root (0x0)
|
|
if ( len < 3 ) {
|
|
analyzer->Weird("DNS_SVBC_wrong_length");
|
|
return false;
|
|
}
|
|
|
|
uint16_t svc_priority = ExtractShort(data, len);
|
|
|
|
u_char target_name[513];
|
|
int name_len = sizeof(target_name) - 1;
|
|
u_char* name_end = ExtractName(data, len, target_name, name_len, msg_start, false);
|
|
if ( ! name_end )
|
|
return false;
|
|
|
|
// target name can be root - in this case the alternative endpoint is
|
|
// qname itself. make sure that we print "." instead of an empty string
|
|
if ( name_end - target_name == 0 ) {
|
|
target_name[0] = '.';
|
|
target_name[1] = '\0';
|
|
name_end = target_name + 1;
|
|
}
|
|
|
|
SVCB_DATA svcb_data = {svc_priority,
|
|
make_intrusive<StringVal>(new String(target_name, name_end - target_name, true))};
|
|
|
|
// TODO: parse svcparams
|
|
// we consume all the remaining raw data (svc params) but do nothing.
|
|
// this should be removed if the svc param parser is ready
|
|
std::ptrdiff_t parsed_bytes = data - data_start;
|
|
if ( parsed_bytes < rdlength ) {
|
|
len -= (rdlength - parsed_bytes);
|
|
data += (rdlength - parsed_bytes);
|
|
}
|
|
|
|
switch ( svcb_type ) {
|
|
case detail::TYPE_SVCB:
|
|
analyzer->EnqueueConnEvent(dns_SVCB, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildSVCB_Val(svcb_data));
|
|
break;
|
|
case detail::TYPE_HTTPS:
|
|
analyzer->EnqueueConnEvent(dns_HTTPS, analyzer->ConnVal(), msg->BuildHdrVal(), msg->BuildAnswerVal(),
|
|
msg->BuildSVCB_Val(svcb_data));
|
|
break;
|
|
default: break; // unreachable. for suppressing compiler warnings.
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DNS_Interpreter::SendReplyOrRejectEvent(detail::DNS_MsgInfo* msg, EventHandlerPtr event, const u_char*& data,
|
|
int& len, String* question_name, String* original_name) {
|
|
detail::RR_Type qtype = detail::RR_Type(ExtractShort(data, len));
|
|
int qclass = ExtractShort(data, len);
|
|
|
|
assert(event);
|
|
|
|
analyzer->EnqueueConnEvent(event, analyzer->ConnVal(), msg->BuildHdrVal(), make_intrusive<StringVal>(question_name),
|
|
val_mgr->Count(qtype), val_mgr->Count(qclass), make_intrusive<StringVal>(original_name));
|
|
}
|
|
|
|
DNS_MsgInfo::DNS_MsgInfo(DNS_RawMsgHdr* hdr, int arg_is_query) {
|
|
// ### Need to fix alignment if hdr is misaligned (not on a short
|
|
// boundary).
|
|
unsigned short flags = ntohs(hdr->flags);
|
|
|
|
QR = (flags & 0x8000) != 0;
|
|
opcode = (flags & 0x7800) >> 11;
|
|
AA = (flags & 0x0400) != 0;
|
|
TC = (flags & 0x0200) != 0;
|
|
RD = (flags & 0x0100) != 0;
|
|
RA = (flags & 0x0080) != 0;
|
|
Z = (flags & 0x0070) >> 4;
|
|
AD = (flags & 0x0020) >> 5;
|
|
CD = (flags & 0x0010) >> 4;
|
|
rcode = (flags & 0x000f);
|
|
|
|
qdcount = ntohs(hdr->qdcount);
|
|
ancount = ntohs(hdr->ancount);
|
|
nscount = ntohs(hdr->nscount);
|
|
arcount = ntohs(hdr->arcount);
|
|
|
|
id = ntohs(hdr->id);
|
|
is_query = arg_is_query;
|
|
|
|
atype = detail::TYPE_ALL;
|
|
aclass = 0;
|
|
ttl = 0;
|
|
|
|
answer_type = DNS_QUESTION;
|
|
skip_event = 0;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildHdrVal() {
|
|
static auto dns_msg = id::find_type<RecordType>("dns_msg");
|
|
auto r = make_intrusive<RecordVal>(dns_msg);
|
|
|
|
r->Assign(0, id);
|
|
r->Assign(1, opcode);
|
|
r->Assign(2, rcode);
|
|
r->Assign(3, static_cast<bool>(QR));
|
|
r->Assign(4, static_cast<bool>(AA));
|
|
r->Assign(5, static_cast<bool>(TC));
|
|
r->Assign(6, static_cast<bool>(RD));
|
|
r->Assign(7, static_cast<bool>(RA));
|
|
r->Assign(8, Z);
|
|
r->Assign(9, static_cast<bool>(AD));
|
|
r->Assign(10, static_cast<bool>(CD));
|
|
r->Assign(11, qdcount);
|
|
r->Assign(12, ancount);
|
|
r->Assign(13, nscount);
|
|
r->Assign(14, arcount);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildAnswerVal() {
|
|
static auto dns_answer = id::find_type<RecordType>("dns_answer");
|
|
auto r = make_intrusive<RecordVal>(dns_answer);
|
|
|
|
r->Assign(0, answer_type);
|
|
r->Assign(1, query_name);
|
|
r->Assign(2, atype);
|
|
r->Assign(3, aclass);
|
|
r->AssignInterval(4, double(ttl));
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildEDNS_Val() {
|
|
// We have to treat the additional record type in EDNS differently
|
|
// than a regular resource record.
|
|
static auto dns_edns_additional = id::find_type<RecordType>("dns_edns_additional");
|
|
auto r = make_intrusive<RecordVal>(dns_edns_additional);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
|
|
// type = 0x29 or 41 = EDNS
|
|
r->Assign(2, atype);
|
|
|
|
// sender's UDP payload size, per RFC 2671 4.3
|
|
r->Assign(3, aclass);
|
|
|
|
// Need to break the TTL field into three components:
|
|
// initial: [------------- ttl (32) ---------------------]
|
|
// after: [ ext rcode (8)][ver # (8)][ Z field (16) ]
|
|
|
|
unsigned int ercode = (ttl >> 24) & 0xff;
|
|
unsigned int version = (ttl >> 16) & 0xff;
|
|
// unsigned int DO = ttl & 0x8000; // "DNSSEC OK" - RFC 3225
|
|
unsigned int z = ttl & 0xffff;
|
|
|
|
unsigned int return_error = (ercode << 4) | rcode;
|
|
|
|
r->Assign(4, return_error);
|
|
r->Assign(5, version);
|
|
r->Assign(6, z);
|
|
r->AssignInterval(7, double(ttl));
|
|
r->Assign(8, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildEDNS_ECS_Val(struct EDNS_ECS* opt) {
|
|
static auto dns_edns_ecs = id::find_type<RecordType>("dns_edns_ecs");
|
|
auto r = make_intrusive<RecordVal>(dns_edns_ecs);
|
|
|
|
r->Assign(0, opt->ecs_family);
|
|
r->Assign(1, opt->ecs_src_pfx_len);
|
|
r->Assign(2, opt->ecs_scp_pfx_len);
|
|
r->Assign(3, opt->ecs_addr);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildEDNS_TCP_KA_Val(struct EDNS_TCP_KEEPALIVE* opt) {
|
|
static auto dns_edns_tcp_keepalive = id::find_type<RecordType>("dns_edns_tcp_keepalive");
|
|
auto r = make_intrusive<RecordVal>(dns_edns_tcp_keepalive);
|
|
|
|
r->Assign(0, opt->keepalive_timeout_omitted);
|
|
r->Assign(1, opt->keepalive_timeout);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildEDNS_COOKIE_Val(struct EDNS_COOKIE* opt) {
|
|
static auto dns_edns_cookie = id::find_type<RecordType>("dns_edns_cookie");
|
|
auto r = make_intrusive<RecordVal>(dns_edns_cookie);
|
|
|
|
r->Assign(0, opt->client_cookie);
|
|
if ( opt->server_cookie != nullptr ) {
|
|
r->Assign(1, opt->server_cookie);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildTKEY_Val(struct TKEY_DATA* tkey) {
|
|
static auto dns_tkey = id::find_type<RecordType>("dns_tkey");
|
|
auto r = make_intrusive<RecordVal>(dns_tkey);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, tkey->alg_name);
|
|
r->AssignTime(3, static_cast<double>(tkey->inception));
|
|
r->AssignTime(4, static_cast<double>(tkey->expiration));
|
|
r->Assign(5, static_cast<uint16_t>(tkey->mode));
|
|
r->Assign(6, static_cast<uint16_t>(tkey->error));
|
|
r->Assign(7, tkey->key);
|
|
r->Assign(8, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildTSIG_Val(struct TSIG_DATA* tsig) {
|
|
static auto dns_tsig_additional = id::find_type<RecordType>("dns_tsig_additional");
|
|
auto r = make_intrusive<RecordVal>(dns_tsig_additional);
|
|
double rtime = tsig->time_s + tsig->time_ms / 1000.0;
|
|
|
|
// r->Assign(0, answer_type);
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, tsig->alg_name);
|
|
r->Assign(3, tsig->sig);
|
|
r->AssignTime(4, rtime);
|
|
r->AssignTime(5, double(tsig->fudge));
|
|
r->Assign(6, tsig->orig_id);
|
|
r->Assign(7, tsig->rr_error);
|
|
r->Assign(8, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildRRSIG_Val(RRSIG_DATA* rrsig) {
|
|
static auto dns_rrsig_rr = id::find_type<RecordType>("dns_rrsig_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_rrsig_rr);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, rrsig->type_covered);
|
|
r->Assign(3, rrsig->algorithm);
|
|
r->Assign(4, rrsig->labels);
|
|
r->AssignInterval(5, double(rrsig->orig_ttl));
|
|
r->AssignTime(6, double(rrsig->sig_exp));
|
|
r->AssignTime(7, double(rrsig->sig_incep));
|
|
r->Assign(8, rrsig->key_tag);
|
|
r->Assign(9, rrsig->signer_name);
|
|
r->Assign(10, rrsig->signature);
|
|
r->Assign(11, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildDNSKEY_Val(DNSKEY_DATA* dnskey) {
|
|
static auto dns_dnskey_rr = id::find_type<RecordType>("dns_dnskey_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_dnskey_rr);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, dnskey->dflags);
|
|
r->Assign(3, dnskey->dprotocol);
|
|
r->Assign(4, dnskey->dalgorithm);
|
|
r->Assign(5, dnskey->public_key);
|
|
r->Assign(6, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildNSEC3_Val(NSEC3_DATA* nsec3) {
|
|
static auto dns_nsec3_rr = id::find_type<RecordType>("dns_nsec3_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_nsec3_rr);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, nsec3->nsec_flags);
|
|
r->Assign(3, nsec3->nsec_hash_algo);
|
|
r->Assign(4, nsec3->nsec_iter);
|
|
r->Assign(5, nsec3->nsec_salt_len);
|
|
r->Assign(6, nsec3->nsec_salt);
|
|
r->Assign(7, nsec3->nsec_hlen);
|
|
r->Assign(8, nsec3->nsec_hash);
|
|
r->Assign(9, std::move(nsec3->bitmaps));
|
|
r->Assign(10, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildNSEC3PARAM_Val(NSEC3PARAM_DATA* nsec3param) {
|
|
static auto dns_nsec3param_rr = id::find_type<RecordType>("dns_nsec3param_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_nsec3param_rr);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, nsec3param->nsec_flags);
|
|
r->Assign(3, nsec3param->nsec_hash_algo);
|
|
r->Assign(4, nsec3param->nsec_iter);
|
|
r->Assign(5, nsec3param->nsec_salt_len);
|
|
r->Assign(6, nsec3param->nsec_salt);
|
|
r->Assign(7, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildDS_Val(DS_DATA* ds) {
|
|
static auto dns_ds_rr = id::find_type<RecordType>("dns_ds_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_ds_rr);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, ds->key_tag);
|
|
r->Assign(3, ds->algorithm);
|
|
r->Assign(4, ds->digest_type);
|
|
r->Assign(5, ds->digest_val);
|
|
r->Assign(6, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildBINDS_Val(BINDS_DATA* binds) {
|
|
static auto dns_binds_rr = id::find_type<RecordType>("dns_binds_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_binds_rr);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, binds->algorithm);
|
|
r->Assign(3, binds->key_id);
|
|
r->Assign(4, binds->removal_flag);
|
|
r->Assign(5, binds->complete_flag->Len() > 0 ? binds->complete_flag->Bytes()[0] : 0);
|
|
r->Assign(6, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildLOC_Val(LOC_DATA* loc) {
|
|
static auto dns_loc_rr = id::find_type<RecordType>("dns_loc_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_loc_rr);
|
|
|
|
r->Assign(0, query_name);
|
|
r->Assign(1, answer_type);
|
|
r->Assign(2, loc->version);
|
|
r->Assign(3, loc->size);
|
|
r->Assign(4, loc->horiz_pre);
|
|
r->Assign(5, loc->vert_pre);
|
|
r->Assign(6, static_cast<uint64_t>(loc->latitude));
|
|
r->Assign(7, static_cast<uint64_t>(loc->longitude));
|
|
r->Assign(8, static_cast<uint64_t>(loc->altitude));
|
|
r->Assign(9, is_query);
|
|
|
|
return r;
|
|
}
|
|
|
|
RecordValPtr DNS_MsgInfo::BuildSVCB_Val(const SVCB_DATA& svcb) {
|
|
static auto dns_svcb_rr = id::find_type<RecordType>("dns_svcb_rr");
|
|
auto r = make_intrusive<RecordVal>(dns_svcb_rr);
|
|
|
|
r->Assign(0, svcb.svc_priority);
|
|
r->Assign(1, svcb.target_name);
|
|
|
|
// TODO: assign values to svcparams
|
|
return r;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
Contents_DNS::Contents_DNS(Connection* conn, bool orig, detail::DNS_Interpreter* arg_interp)
|
|
: analyzer::tcp::TCP_SupportAnalyzer("CONTENTS_DNS", conn, orig) {
|
|
interp = arg_interp;
|
|
|
|
msg_buf = nullptr;
|
|
buf_n = buf_len = msg_size = 0;
|
|
state = detail::DNS_LEN_HI;
|
|
}
|
|
|
|
Contents_DNS::~Contents_DNS() { free(msg_buf); }
|
|
|
|
void Contents_DNS::Flush() {
|
|
if ( buf_n > 0 ) { // Deliver partial message.
|
|
// '2' here means whether it's a query is unknown.
|
|
interp->ParseMessage(msg_buf, buf_n, 2);
|
|
msg_size = 0;
|
|
}
|
|
}
|
|
|
|
void Contents_DNS::DeliverStream(int len, const u_char* data, bool orig) {
|
|
while ( len > 0 )
|
|
ProcessChunk(len, data, orig);
|
|
}
|
|
|
|
void Contents_DNS::ProcessChunk(int& len, const u_char*& data, bool orig) {
|
|
if ( state == detail::DNS_LEN_HI ) {
|
|
msg_size = (*data) << 8;
|
|
state = detail::DNS_LEN_LO;
|
|
|
|
++data;
|
|
--len;
|
|
|
|
if ( len == 0 )
|
|
return;
|
|
}
|
|
|
|
if ( state == detail::DNS_LEN_LO ) {
|
|
msg_size += *data;
|
|
state = detail::DNS_MESSAGE_BUFFER;
|
|
|
|
buf_n = 0;
|
|
|
|
if ( msg_buf ) {
|
|
if ( buf_len < msg_size ) {
|
|
buf_len = msg_size;
|
|
msg_buf = (u_char*)util::safe_realloc((void*)msg_buf, buf_len);
|
|
}
|
|
}
|
|
else {
|
|
buf_len = msg_size;
|
|
msg_buf = (u_char*)util::safe_malloc(buf_len);
|
|
}
|
|
|
|
++data;
|
|
--len;
|
|
|
|
if ( len == 0 )
|
|
return;
|
|
}
|
|
|
|
if ( state != detail::DNS_MESSAGE_BUFFER )
|
|
Conn()->Internal("state inconsistency in Contents_DNS::DeliverStream");
|
|
|
|
int n;
|
|
for ( n = 0; buf_n < msg_size && n < len; ++n )
|
|
msg_buf[buf_n++] = data[n];
|
|
|
|
data += n;
|
|
len -= n;
|
|
|
|
if ( buf_n < msg_size )
|
|
// Haven't filled up the message buffer yet, no more to do.
|
|
return;
|
|
|
|
ForwardPacket(msg_size, msg_buf, orig, -1, nullptr, 0);
|
|
|
|
buf_n = 0;
|
|
state = detail::DNS_LEN_HI;
|
|
}
|
|
|
|
DNS_Analyzer::DNS_Analyzer(Connection* conn) : analyzer::tcp::TCP_ApplicationAnalyzer("DNS", conn) {
|
|
interp = new detail::DNS_Interpreter(this);
|
|
contents_dns_orig = contents_dns_resp = nullptr;
|
|
|
|
if ( Conn()->ConnTransport() == TRANSPORT_TCP ) {
|
|
contents_dns_orig = new Contents_DNS(conn, true, interp);
|
|
contents_dns_resp = new Contents_DNS(conn, false, interp);
|
|
AddSupportAnalyzer(contents_dns_orig);
|
|
AddSupportAnalyzer(contents_dns_resp);
|
|
}
|
|
else {
|
|
ADD_ANALYZER_TIMER(&DNS_Analyzer::ExpireTimer, run_state::network_time + zeek::detail::dns_session_timeout,
|
|
true, zeek::detail::TIMER_DNS_EXPIRE);
|
|
}
|
|
}
|
|
|
|
DNS_Analyzer::~DNS_Analyzer() { delete interp; }
|
|
|
|
void DNS_Analyzer::Init() {}
|
|
|
|
void DNS_Analyzer::Done() {
|
|
analyzer::tcp::TCP_ApplicationAnalyzer::Done();
|
|
|
|
if ( Conn()->ConnTransport() == TRANSPORT_UDP )
|
|
Event(udp_session_done);
|
|
else
|
|
interp->Timeout();
|
|
}
|
|
|
|
void DNS_Analyzer::DeliverPacket(int len, const u_char* data, bool orig, uint64_t seq, const IP_Hdr* ip, int caplen) {
|
|
analyzer::tcp::TCP_ApplicationAnalyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
|
|
interp->ParseMessage(data, len, orig ? 1 : 0);
|
|
}
|
|
|
|
void DNS_Analyzer::ConnectionClosed(analyzer::tcp::TCP_Endpoint* endpoint, analyzer::tcp::TCP_Endpoint* peer,
|
|
bool gen_event) {
|
|
analyzer::tcp::TCP_ApplicationAnalyzer::ConnectionClosed(endpoint, peer, gen_event);
|
|
|
|
assert(contents_dns_orig && contents_dns_resp);
|
|
contents_dns_orig->Flush();
|
|
contents_dns_resp->Flush();
|
|
}
|
|
|
|
void DNS_Analyzer::ExpireTimer(double t) {
|
|
// The - 1.0 in the following is to allow 1 second for the
|
|
// common case of a single request followed by a single reply,
|
|
// so we don't needlessly set the timer twice in that case.
|
|
if ( t - Conn()->LastTime() >= zeek::detail::dns_session_timeout - 1.0 || run_state::terminating ) {
|
|
Event(connection_timeout);
|
|
session_mgr->Remove(Conn());
|
|
}
|
|
else
|
|
ADD_ANALYZER_TIMER(&DNS_Analyzer::ExpireTimer, t + zeek::detail::dns_session_timeout, true,
|
|
zeek::detail::TIMER_DNS_EXPIRE);
|
|
}
|
|
|
|
} // namespace zeek::analyzer::dns
|