diff --git a/CHANGES b/CHANGES index f99b1b4065..8b55c90438 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,8 @@ +3.2.0-dev.979 | 2020-07-24 09:03:06 -0700 + + * Implement EDNS Client Subnet Option (Ron Wellman) + 3.2.0-dev.974 | 2020-07-23 13:02:13 -0700 * origin/topic/jsiwek/gh-1076-fix-random: diff --git a/VERSION b/VERSION index 3f24308dd6..5489f38b3d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.0-dev.974 +3.2.0-dev.979 diff --git a/doc b/doc index 48d9a82760..bbd144f84d 160000 --- a/doc +++ b/doc @@ -1 +1 @@ -Subproject commit 48d9a82760a1c06ba40fce4c27677d01f6c263f9 +Subproject commit bbd144f84d41a687dc5dbf447db6626063132dd2 diff --git a/scripts/base/init-bare.zeek b/scripts/base/init-bare.zeek index 8278694dcd..d99dbcc346 100644 --- a/scripts/base/init-bare.zeek +++ b/scripts/base/init-bare.zeek @@ -3690,6 +3690,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 diff --git a/scripts/base/protocols/dns/main.zeek b/scripts/base/protocols/dns/main.zeek index 7b7f6c2176..0a0da1aa82 100644 --- a/scripts/base/protocols/dns/main.zeek +++ b/scripts/base/protocols/dns/main.zeek @@ -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) # { diff --git a/src/NetVar.cc b/src/NetVar.cc index 44d5950d27..7628f1d9eb 100644 --- a/src/NetVar.cc +++ b/src/NetVar.cc @@ -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; diff --git a/src/NetVar.h b/src/NetVar.h index 3192007a4a..9c1017d96e 100644 --- a/src/NetVar.h +++ b/src/NetVar.h @@ -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; diff --git a/src/analyzer/protocol/dns/DNS.cc b/src/analyzer/protocol/dns/DNS.cc index 09458db5f9..7e2f49ab0b 100644 --- a/src/analyzer/protocol/dns/DNS.cc +++ b/src/analyzer/protocol/dns/DNS.cc @@ -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,88 @@ 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); + // check for invalid option length + if ( (option_len > len) || (0 == option_len) ) { + break; + } + len -= option_len; + + // TODO: Implement additional option codes + switch ( option_code ) + { + case TYPE_ECS: + { + // must be 4 bytes + variable number of octets for address + if ( option_len <= 4 ) { + break; + } + + 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 = zeek::make_intrusive("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 = zeek::make_intrusive(addr); + } + else if ( ecs_family == L3_IPV6 ) + { + opt.ecs_family = zeek::make_intrusive("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 = zeek::make_intrusive(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 +1591,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("dns_edns_ecs"); + auto r = zeek::make_intrusive(dns_edns_ecs); + + r->Assign(0, opt->ecs_family); + r->Assign(1, zeek::val_mgr->Count(opt->ecs_src_pfx_len)); + r->Assign(2, zeek::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("dns_tsig_additional"); diff --git a/src/analyzer/protocol/dns/DNS.h b/src/analyzer/protocol/dns/DNS.h index 73a882a985..2e795de4ad 100644 --- a/src/analyzer/protocol/dns/DNS.h +++ b/src/analyzer/protocol/dns/DNS.h @@ -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 { + zeek::StringValPtr 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 + zeek::IntrusivePtr 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, diff --git a/src/analyzer/protocol/dns/events.bif b/src/analyzer/protocol/dns/events.bif index d0a5411344..31e9a11625 100644 --- a/src/analyzer/protocol/dns/events.bif +++ b/src/analyzer/protocol/dns/events.bif @@ -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 `__ 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. ## diff --git a/src/legacy-netvar-init.cc b/src/legacy-netvar-init.cc index f5f7ee52f0..de4dc83972 100644 --- a/src/legacy-netvar-init.cc +++ b/src/legacy-netvar-init.cc @@ -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(); diff --git a/testing/btest/Baseline/scripts.base.protocols.dns.dns-edns-ecs/output b/testing/btest/Baseline/scripts.base.protocols.dns.dns-edns-ecs/output new file mode 100644 index 0000000000..7e47b5a201 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dns.dns-edns-ecs/output @@ -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::] diff --git a/testing/btest/Traces/dns-edns-ecs.pcap b/testing/btest/Traces/dns-edns-ecs.pcap new file mode 100644 index 0000000000..0357459a8d Binary files /dev/null and b/testing/btest/Traces/dns-edns-ecs.pcap differ diff --git a/testing/btest/scripts/base/protocols/dns/dns-edns-ecs.zeek b/testing/btest/scripts/base/protocols/dns/dns-edns-ecs.zeek new file mode 100644 index 0000000000..09a694f15f --- /dev/null +++ b/testing/btest/scripts/base/protocols/dns/dns-edns-ecs.zeek @@ -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; +} \ No newline at end of file