Implement EDNS Client Subnet Option

This commit is contained in:
Ron Wellman 2020-06-19 22:04:41 -04:00 committed by ronwellman
parent e891c310fb
commit e7146c2a6b
11 changed files with 177 additions and 9 deletions

View file

@ -3676,6 +3676,16 @@ type dns_edns_additional: record {
is_query: count; ##< TODO.
};
## An DNS EDNS Client Subnet (ECS) record.
##
## .. zeek:see:: dns_EDNS_ecs
type dns_edns_ecs: record {
family: string; ##< IP Family
source_prefix_len: count; ##< Source Prefix Length.
scope_prefix_len: count; ##< Scope Prefix Length.
address: string; ##< Client Subnet Address.
};
## An additional DNS TSIG record.
##
## .. zeek:see:: dns_TSIG_addl

View file

@ -527,6 +527,10 @@ event dns_SRV_reply(c: connection, msg: dns_msg, ans: dns_answer, target: string
# {
#
# }
# event dns_EDNS_ecs(c: connection, msg: dns_msg, opt: dns_edns_ecs)
# {
#
# }
#
#event dns_TSIG_addl(c: connection, msg: dns_msg, ans: dns_tsig_additional)
# {

View file

@ -107,6 +107,7 @@ zeek::RecordType* dns_msg;
zeek::RecordType* dns_answer;
zeek::RecordType* dns_soa;
zeek::RecordType* dns_edns_additional;
zeek::RecordType* dns_edns_ecs;
zeek::RecordType* dns_tsig_additional;
zeek::RecordType* dns_rrsig_rr;
zeek::RecordType* dns_dnskey_rr;

View file

@ -149,6 +149,8 @@ extern zeek::RecordType* dns_soa;
[[deprecated("Remove in v4.1. Perform your own lookup.")]]
extern zeek::RecordType* dns_edns_additional;
[[deprecated("Remove in v4.1. Perform your own lookup.")]]
extern zeek::RecordType* dns_edns_ecs;
[[deprecated("Remove in v4.1. Perform your own lookup.")]]
extern zeek::RecordType* dns_tsig_additional;
[[deprecated("Remove in v4.1. Perform your own lookup.")]]
extern zeek::RecordType* dns_rrsig_rr;

View file

@ -700,8 +700,6 @@ bool DNS_Interpreter::ParseRR_EDNS(DNS_MsgInfo* msg,
const u_char*& data, int& len, int rdlength,
const u_char* msg_start)
{
// We need a pair-value set mechanism here to dump useful information
// out to the policy side of the house if rdlength > 0.
if ( dns_EDNS_addl && ! msg->skip_event )
analyzer->EnqueueConnEvent(dns_EDNS_addl,
@ -710,13 +708,79 @@ bool DNS_Interpreter::ParseRR_EDNS(DNS_MsgInfo* msg,
msg->BuildEDNS_Val()
);
// Currently EDNS supports the movement of type:data pairs
// in the RR_DATA section. Here's where we should put together
// a corresponding mechanism.
if ( rdlength > 0 )
{ // deal with data
data += rdlength;
len -= rdlength;
// parse EDNS options
while ( len > 0 )
{
uint16_t option_code = ExtractShort(data, len);
int option_len = ExtractShort(data, len);
len -= option_len;
// TODO: Implement additional option codes
switch ( option_code )
{
case TYPE_ECS:
{
struct 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)
{
opt.ecs_family = make_intrusive<StringVal>("v4");
uint32_t addr = 0;
for (uint16_t shift_factor = 3; option_len > 0; option_len--)
{
addr |= data[0] << (shift_factor * 8);
data++;
shift_factor--;
}
addr = htonl(addr);
opt.ecs_addr = make_intrusive<AddrVal>(addr);
}
else if ( ecs_family == L3_IPV6 )
{
opt.ecs_family = make_intrusive<StringVal>("v6");
uint32_t addr[4] = { 0 };
for (uint16_t i = 0, shift_factor = 15; option_len > 0; option_len--)
{
addr[i / 4] |= data[0] << ((shift_factor % 4) * 8);
data++;
i++;
shift_factor--;
}
for (uint8_t i = 0; i < 4; i++)
{
addr[i] = htonl(addr[i]);
}
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)
);
break;
}
default:
{
data += option_len;
break;
}
}
}
return true;
@ -1518,6 +1582,19 @@ zeek::RecordValPtr DNS_MsgInfo::BuildEDNS_Val()
return r;
}
zeek::RecordValPtr DNS_MsgInfo::BuildEDNS_ECS_Val(struct EDNS_ECS* opt)
{
static auto dns_edns_ecs = zeek::id::find_type<zeek::RecordType>("dns_edns_ecs");
auto r = make_intrusive<RecordVal>(dns_edns_ecs);
r->Assign(0, opt->ecs_family);
r->Assign(1, val_mgr->Count(opt->ecs_src_pfx_len));
r->Assign(2, val_mgr->Count(opt->ecs_scp_pfx_len));
r->Assign(3, opt->ecs_addr);
return r;
}
zeek::RecordValPtr DNS_MsgInfo::BuildTSIG_Val(struct TSIG_DATA* tsig)
{
static auto dns_tsig_additional = zeek::id::find_type<zeek::RecordType>("dns_tsig_additional");

View file

@ -81,6 +81,27 @@ typedef enum {
DNS_ADDITIONAL,
} DNS_AnswerType;
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
// DNS EDNS0 Option Codes (OPT)
typedef enum {
TYPE_LLQ = 1, ///< https://www.iana.org/go/draft-sekar-dns-llq-06
TYPE_UL = 2, ///< http://files.dns-sd.org/draft-sekar-dns-ul.txt
TYPE_NSID = 3, ///< RFC5001
TYPE_DAU = 5, ///< RFC6975
TYPE_DHU = 6, ///< RFC6975
TYPE_N3U = 7, ///< RFC6975
TYPE_ECS = 8, ///< RFC7871
TYPE_EXPIRE = 9, ///< RFC7314
TYPE_TCP_KA = 11, ///< RFC7828
TYPE_PAD = 12, ///< RFC7830
TYPE_CHAIN = 13, ///< RFC7901
TYPE_KEY_TAG = 14, ///< RFC8145
TYPE_ERROR = 15, ///< https://www.iana.org/go/draft-ietf-dnsop-extended-error-16
TYPE_CLIENT_TAG = 16, ///< https://www.iana.org/go/draft-bellis-dnsop-edns-tags
TYPE_SERVER_TAG = 17, ///< https://www.iana.org/go/draft-bellis-dnsop-edns-tags
TYPE_DEVICE_ID = 26946 ///< https://docs.umbrella.com/developer/networkdevices-api/identifying-dns-traffic2
} EDNS_OPT_Type;
typedef enum {
reserved0 = 0,
RSA_MD5 = 1, ///< [RFC2537] NOT RECOMMENDED
@ -128,6 +149,13 @@ struct EDNS_ADDITIONAL { // size
unsigned short rdata_len; // 16
};
struct EDNS_ECS {
IntrusivePtr<StringVal> ecs_family; ///< EDNS client subnet address family
uint16_t ecs_src_pfx_len; ///< EDNS client subnet source prefix length
uint16_t ecs_scp_pfx_len; ///< EDNS client subnet scope prefix length
IntrusivePtr<AddrVal> ecs_addr; ///< EDNS client subnet address
};
struct TSIG_DATA {
zeek::String* alg_name;
unsigned long time_s;
@ -182,6 +210,7 @@ public:
zeek::RecordValPtr BuildHdrVal();
zeek::RecordValPtr BuildAnswerVal();
zeek::RecordValPtr BuildEDNS_Val();
zeek::RecordValPtr BuildEDNS_ECS_Val(struct EDNS_ECS*);
zeek::RecordValPtr BuildTSIG_Val(struct TSIG_DATA*);
zeek::RecordValPtr BuildRRSIG_Val(struct RRSIG_DATA*);
zeek::RecordValPtr BuildDNSKEY_Val(struct DNSKEY_DATA*);
@ -271,6 +300,9 @@ protected:
bool ParseRR_EDNS(DNS_MsgInfo* msg,
const u_char*& data, int& len, int rdlength,
const u_char* msg_start);
bool ParseRR_EDNS_ECS(DNS_MsgInfo* msg,
const u_char*& data, int& len, int rdlength,
const u_char* msg_start);
bool ParseRR_A(DNS_MsgInfo* msg,
const u_char*& data, int& len, int rdlength);
bool ParseRR_AAAA(DNS_MsgInfo* msg,

View file

@ -505,6 +505,29 @@ event dns_unknown_reply%(c: connection, msg: dns_msg, ans: dns_answer%);
## dns_skip_all_addl dns_skip_all_auth dns_skip_auth
event dns_EDNS_addl%(c: connection, msg: dns_msg, ans: dns_edns_additional%);
## Generated for DNS replies of type *EDNS*. For replies with multiple options,
## an individual event is raised for each.
##
## See `Wikipedia <http://en.wikipedia.org/wiki/Domain_Name_System>`__ for more
## information about the DNS protocol. Zeek analyzes both UDP and TCP DNS
## sessions.
##
## 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.
##
## opt: The parsed EDNS option.
##
## .. zeek:see:: dns_AAAA_reply dns_A_reply dns_CNAME_reply dns_HINFO_reply dns_MX_reply
## dns_NS_reply dns_PTR_reply dns_SOA_reply dns_SRV_reply dns_TSIG_addl
## dns_TXT_reply dns_SPF_reply dns_WKS_reply dns_end dns_mapping_altered
## dns_mapping_lost_name dns_mapping_new_name dns_mapping_unverified
## dns_mapping_valid dns_message dns_query_reply dns_rejected dns_request
## dns_max_queries dns_session_timeout dns_skip_addl
## dns_skip_all_addl dns_skip_all_auth dns_skip_auth
event dns_EDNS_ecs%(c: connection, msg: dns_msg, opt: dns_edns_ecs%);
## Generated for DNS replies of type *TSIG*. For replies with multiple answers,
## an individual event of the corresponding type is raised for each.
##

View file

@ -42,6 +42,7 @@ void zeek_legacy_netvar_init()
::dns_answer = zeek::id::find_type("dns_answer")->AsRecordType();
::dns_soa = zeek::id::find_type("dns_soa")->AsRecordType();
::dns_edns_additional = zeek::id::find_type("dns_edns_additional")->AsRecordType();
::dns_edns_ecs = zeek::id::find_type("dns_edns_ecs")->AsRecordType();
::dns_tsig_additional = zeek::id::find_type("dns_tsig_additional")->AsRecordType();
::dns_rrsig_rr = zeek::id::find_type("dns_rrsig_rr")->AsRecordType();
::dns_dnskey_rr = zeek::id::find_type("dns_dnskey_rr")->AsRecordType();

View file

@ -0,0 +1,10 @@
[family=v4, source_prefix_len=24, scope_prefix_len=0, address=213.61.29.0]
[family=v4, source_prefix_len=24, scope_prefix_len=0, address=213.61.29.0]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]
[family=v6, source_prefix_len=56, scope_prefix_len=0, address=2001:470:1f0b:1600::]

Binary file not shown.

View file

@ -0,0 +1,8 @@
# @TEST-EXEC: zeek -C -r $TRACES/dns-edns-ecs.pcap %INPUT > output
# @TEST-EXEC: btest-diff output
@load policy/protocols/dns/auth-addl
event dns_EDNS_ecs(c: connection, msg: dns_msg, opt: dns_edns_ecs) {
print opt;
}