1 #include "DNS-message.hpp"
3 #include "DNS-iostream.hpp"
6 #include <arpa/nameser.h>
9 using octet
= DNS::message::octet
;
11 octet
constexpr lo(uint16_t n
) { return octet(n
& 0xFF); }
12 octet
constexpr hi(uint16_t n
) { return octet((n
>> 8) & 0xFF); }
14 constexpr uint16_t as_u16(octet hi
, octet lo
)
16 return (uint16_t(hi
) << 8) + lo
;
21 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
22 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
24 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
25 |QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE |
26 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
28 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
30 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
32 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
34 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
42 octet flags_0_
{1}; // recursion desired
46 octet qdcount_lo_
{1}; // 1 question
55 octet arcount_lo_
{1}; // 1 additional for the OPT
58 explicit header(uint16_t id
)
62 static_assert(sizeof(header
) == 12);
65 uint16_t id() const { return as_u16(id_hi_
, id_lo_
); }
67 uint16_t qdcount() const { return as_u16(qdcount_hi_
, qdcount_lo_
); }
68 uint16_t ancount() const { return as_u16(ancount_hi_
, ancount_lo_
); }
69 uint16_t nscount() const { return as_u16(nscount_hi_
, nscount_lo_
); }
70 uint16_t arcount() const { return as_u16(arcount_hi_
, arcount_lo_
); }
73 bool truncation() const { return (flags_0_
& 0x02) != 0; }
75 bool checking_disabled() const { return (flags_1_
& 0x10) != 0; }
76 bool authentic_data() const { return (flags_1_
& 0x20) != 0; }
77 bool recursion_available() const { return (flags_1_
& 0x80) != 0; }
80 uint16_t rcode() const { return flags_1_
& 0xf; }
91 explicit question(DNS::RR_type qtype
, uint16_t qclass
)
92 : qtype_hi_(hi(static_cast<uint16_t>(qtype
)))
93 , qtype_lo_(lo(static_cast<uint16_t>(qtype
)))
94 , qclass_hi_(hi(qclass
))
95 , qclass_lo_(lo(qclass
))
97 static_assert(sizeof(question
) == 4);
100 DNS::RR_type
qtype() const
102 return static_cast<DNS::RR_type
>(as_u16(qtype_hi_
, qtype_lo_
));
104 uint16_t qclass() const { return as_u16(qclass_hi_
, qclass_lo_
); }
109 <https://tools.ietf.org/html/rfc6891#section-6.1.2>
111 +------------+--------------+------------------------------+
112 | Field Name | Field Type | Description |
113 +------------+--------------+------------------------------+
114 | NAME | domain name | MUST be 0 (root domain) |
115 | TYPE | u_int16_t | OPT (41) |
116 | CLASS | u_int16_t | requestor's UDP payload size |
117 | TTL | u_int32_t | extended RCODE and flags |
118 | RDLEN | u_int16_t | length of all RDATA |
119 | RDATA | octet stream | {attribute,value} pairs |
120 +------------+--------------+------------------------------+
122 <https://tools.ietf.org/html/rfc3225>
124 3. Protocol Changes, in place of TTL
127 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
128 0: | EXTENDED-RCODE | VERSION |
129 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
131 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
135 class edns0_opt_meta_rr
{
136 octet root_domain_name_
{0}; // must be zero
139 octet type_lo_
{ns_t_opt
};
141 octet class_hi_
; // UDP payload size
144 octet extended_rcode_
{0};
147 octet z_hi_
{0x80}; // "DNSSEC OK" (DO) bit
154 explicit edns0_opt_meta_rr(uint16_t max_udp_sz
)
155 : class_hi_(hi(max_udp_sz
))
156 , class_lo_(lo(max_udp_sz
))
158 static_assert(sizeof(edns0_opt_meta_rr
) == 11);
161 uint16_t extended_rcode() const { return extended_rcode_
; }
166 <https://tools.ietf.org/html/rfc1035>
168 4.1.3. Resource record format
170 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
171 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
176 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
178 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
180 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
183 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
185 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
188 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
208 rr() { static_assert(sizeof(rr
) == 10); }
210 uint16_t rr_type() const { return as_u16(type_hi_
, type_lo_
); }
211 uint16_t rr_class() const { return as_u16(class_hi_
, class_lo_
); }
212 uint32_t rr_ttl() const
214 return (uint32_t(ttl_0_
) << 24) + (uint32_t(ttl_1_
) << 16) +
215 (uint32_t(ttl_2_
) << 8) + (uint32_t(ttl_3_
));
218 uint16_t rdlength() const { return as_u16(rdlength_hi_
, rdlength_lo_
); }
222 return reinterpret_cast<char const*>(this) + sizeof(rr
);
224 auto rddata() const { return reinterpret_cast<octet
const*>(cdata()); }
225 auto next_rr_name() const { return rddata() + rdlength(); }
228 // name processing code mostly adapted from c-ares
230 auto uztosl(size_t uznum
)
232 CHECK_LE(uznum
, size_t(std::numeric_limits
<long>::max()));
233 return static_cast<long>(uznum
);
236 // return the length of the expansion of an encoded domain name, or -1
237 // if the encoding is invalid
239 int name_length(octet
const* encoded
, DNS::message
const& pkt
)
241 auto const sp
= static_cast<std::span
<DNS::message::octet
const>>(pkt
);
242 auto const sp_end
= sp
.data() + sp
.size();
244 // Allow the caller to pass us buf + len and have us check for it.
245 if (encoded
>= sp_end
)
249 int nindir
= 0; // count indirections
253 auto const top
= (*encoded
& NS_CMPRSFLGS
);
255 if (top
== NS_CMPRSFLGS
) {
256 // Check the offset and go there.
257 if (encoded
+ 1 >= sp_end
)
260 unsigned const offset
= (*encoded
& ~NS_CMPRSFLGS
) << 8 | *(encoded
+ 1);
261 if (offset
>= sp
.size())
264 encoded
= sp
.data() + offset
;
268 auto constexpr max_indirs
= 50; // maximum indirections allowed for a name
270 // If we've seen more indirects than the message length, or over
271 // some limit, then there's a loop.
272 if (nindir
> std::streamsize(sp
.size()) || nindir
> max_indirs
)
276 auto offset
= *encoded
;
277 if (encoded
+ offset
+ 1 >= sp_end
)
283 length
+= (*encoded
== '.' || *encoded
== '\\') ? 2 : 1;
290 // RFC 1035 4.1.4 says other options (01, 10) for top 2
291 // bits are reserved.
296 // If there were any labels at all, then the number of dots is one
297 // less than the number of labels, so subtract one.
299 return length
? length
- 1 : length
;
302 bool expand_name(octet
const* encoded
,
303 DNS::message
const& pkt
,
307 auto const sp
= static_cast<std::span
<DNS::message::octet
const>>(pkt
);
313 auto const nlen
= name_length(encoded
, pkt
);
315 LOG(WARNING
) << "bad name";
322 // RFC 2181 says this should be ".": the root of the DNS tree.
323 // Since this function strips trailing dots though, it becomes ""s
325 // indirect root label (like 0xc0 0x0c) is 2 bytes long
326 if ((*encoded
& NS_CMPRSFLGS
) == NS_CMPRSFLGS
)
329 enc_len
= 1; // the caller should move one byte to get past this
334 // error-checking done by name_length()
337 if ((*p
& NS_CMPRSFLGS
) == NS_CMPRSFLGS
) {
339 enc_len
= uztosl(p
+ 2 - encoded
);
342 p
= sp
.data() + ((*p
& ~NS_CMPRSFLGS
) << 8 | *(p
+ 1));
348 if (*p
== '.' || *p
== '\\')
350 name
+= static_cast<char>(*p
);
358 enc_len
= uztosl(p
+ 1 - encoded
);
360 if (name
.length() && (name
.back() == '.')) {
367 // return the length of the encoded name
369 int name_put(octet
* buf
, char const* name
)
373 if ((name
[0] == '.') && (name
[1] == '\0'))
378 LOG(WARNING
) << "zero length label";
385 for (p
= name
; *p
&& *p
!= '.'; p
++) {
386 if (*p
== '\\' && *(p
+ 1) != 0)
391 // RFC-1035 Section 2.3.4. Size limits
392 LOG(WARNING
) << "label exceeds 63 octets";
397 for (p
= name
; *p
&& *p
!= '.'; p
++) {
398 if (*p
== '\\' && *(p
+ 1) != 0)
409 // Add the zero-length label at the end.
412 auto const sz
= q
- buf
;
414 // RFC-1035 Section 2.3.4. Size limits
415 LOG(WARNING
) << "domain name exceeds 255 octets";
425 uint16_t message::id() const
427 auto const hdr_p
= reinterpret_cast<header
const*>(buf_
.data());
431 size_t message::min_sz() { return sizeof(header
); }
434 create_question(char const* name
, DNS::RR_type type
, uint16_t cls
, uint16_t id
)
436 // size to allocate may be larger than needed if backslash escapes
437 // are used in domain name
439 auto const sz_alloc
= strlen(name
) + 2 + // clang-format off
442 sizeof(edns0_opt_meta_rr
); // clang-format on
444 DNS::message::container_t
buf(sz_alloc
);
451 auto const len
= name_put(q
, name
);
452 CHECK_GE(len
, 1) << "malformed domain name " << name
;
455 new (q
) question(type
, cls
);
456 q
+= sizeof(question
);
458 new (q
) edns0_opt_meta_rr(Config::max_udp_sz
);
459 q
+= sizeof(edns0_opt_meta_rr
);
461 // verify constructed size is less than or equal to allocated size
462 auto const sz
= q
- buf
.data();
463 CHECK_LE(sz
, sz_alloc
);
468 return DNS::message
{std::move(buf
)};
471 void check_answer(bool& nx_domain
,
472 bool& bogus_or_indeterminate
,
475 uint16_t& extended_rcode
,
478 bool& authentic_data
,
481 DNS::message
const& q
,
482 DNS::message
const& a
,
487 // We grab some stuff from the question we generated. This is not
488 // an un-trusted datum from afar.
490 auto const cls
{[type
= type
, &q
]() {
491 auto const q_sp
= static_cast<std::span
<DNS::message::octet
const>>(q
);
492 auto q_p
= q_sp
.data();
493 auto const q_hdr_p
= reinterpret_cast<header
const*>(q_p
);
494 CHECK_EQ(q_hdr_p
->qdcount(), uint16_t(1));
495 q_p
+= sizeof(header
);
498 CHECK(expand_name(q_p
, q
, qname
, name_len
));
500 auto const question_p
= reinterpret_cast<question
const*>(q_p
);
501 CHECK(question_p
->qtype() == type
)
502 << question_p
->qtype() << " != " << type
<< '\n';
503 return question_p
->qclass();
506 auto const a_sp
= static_cast<std::span
<DNS::message::octet
const>>(a
);
507 auto const a_sp_end
= a_sp
.data() + a_sp
.size();
509 auto const hdr_p
= reinterpret_cast<header
const*>(a_sp
.data());
511 rcode
= hdr_p
->rcode();
513 case ns_r_noerror
: break;
514 case ns_r_nxdomain
: nx_domain
= true; break;
515 case ns_r_servfail
: bogus_or_indeterminate
= true; break;
517 bogus_or_indeterminate
= true;
518 LOG(WARNING
) << "name lookup error: " << DNS::rcode_c_str(rcode
) << " for "
519 << name
<< '/' << type
;
523 truncation
= hdr_p
->truncation();
524 authentic_data
= hdr_p
->authentic_data();
525 has_record
= hdr_p
->ancount() != 0;
528 bogus_or_indeterminate
= true;
529 LOG(WARNING
) << "DNS answer truncated for " << name
<< '/' << type
;
533 // check the question part of the reply
535 if (hdr_p
->qdcount() != 1) {
536 bogus_or_indeterminate
= true;
537 LOG(WARNING
) << "question not copied into answer for " << name
<< '/'
542 // p is a pointer that pushes forward in the message as we process
544 auto p
= a_sp
.data() + sizeof(header
);
546 { // make sure the question name matches
549 if (!expand_name(p
, a
, qname
, enc_len
)) {
550 bogus_or_indeterminate
= true;
551 LOG(WARNING
) << "bad message";
556 bogus_or_indeterminate
= true;
557 LOG(WARNING
) << "bad message";
560 if (!iequal(qname
, name
)) {
561 bogus_or_indeterminate
= true;
562 LOG(WARNING
) << "names don't match, " << qname
<< " != " << name
;
567 if ((p
+ sizeof(question
)) >= a_sp_end
) {
568 bogus_or_indeterminate
= true;
569 LOG(WARNING
) << "bad message";
573 auto question_p
= reinterpret_cast<question
const*>(p
);
574 p
+= sizeof(question
);
576 if (question_p
->qtype() != type
) {
577 bogus_or_indeterminate
= true;
578 LOG(WARNING
) << "qtypes don't match, " << question_p
->qtype()
582 if (question_p
->qclass() != cls
) {
583 bogus_or_indeterminate
= true;
584 LOG(WARNING
) << "qclasses don't match, " << question_p
->qclass()
589 // answers and nameservers
590 for (auto i
= 0; i
< (hdr_p
->ancount() + hdr_p
->nscount()); ++i
) {
593 if (!expand_name(p
, a
, x
, enc_len
) ||
594 ((p
+ enc_len
+ sizeof(rr
)) > a_sp_end
)) {
595 bogus_or_indeterminate
= true;
596 LOG(WARNING
) << "bad message in answer or nameserver section for " << name
601 auto const rr_p
= reinterpret_cast<rr
const*>(p
);
602 p
= rr_p
->next_rr_name();
605 // check additional section for OPT record
606 for (auto i
= 0; i
< hdr_p
->arcount(); ++i
) {
609 if (!expand_name(p
, a
, x
, enc_len
) ||
610 ((p
+ enc_len
+ sizeof(rr
)) > a_sp_end
)) {
611 bogus_or_indeterminate
= true;
612 LOG(WARNING
) << "bad message in additional section for " << name
<< '/'
617 auto const rr_p
= reinterpret_cast<rr
const*>(p
);
619 switch (rr_p
->rr_type()) {
621 auto opt_p
= reinterpret_cast<edns0_opt_meta_rr
const*>(p
);
622 extended_rcode
= (opt_p
->extended_rcode() << 4) + hdr_p
->rcode();
630 // nameserver records often included with associated address info
634 // tlsa records can now be in the additional section, as
635 // they are returned from dns.mullvad.net.
639 LOG(INFO
) << "unknown additional record, name == " << name
;
640 LOG(INFO
) << "rr_p->type() == " << rr_p
->rr_type() << " ("
641 << RR_type_c_str(rr_p
->rr_type()) << ")";
642 LOG(INFO
) << "rr_p->class() == " << rr_p
->rr_class();
643 LOG(INFO
) << "rr_p->ttl() == " << rr_p
->rr_ttl();
647 p
= rr_p
->next_rr_name();
650 unsigned long size_check
= p
- a_sp
.data();
651 if (size_check
!= a_sp
.size()) {
652 bogus_or_indeterminate
= true;
653 LOG(WARNING
) << "bad message size for " << name
<< '/' << type
;
658 std::optional
<RR
> get_A(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
660 if (rr_p
->rdlength() != 4) {
661 LOG(WARNING
) << "bogus A record";
665 return RR_A
{rr_p
->rddata(), rr_p
->rdlength()};
668 std::optional
<RR
> get_CNAME(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
672 if (!expand_name(rr_p
->rddata(), pkt
, name
, enc_len
)) {
673 LOG(WARNING
) << "bogus CNAME record";
677 return RR_CNAME
{name
};
680 std::optional
<RR
> get_PTR(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
684 if (!expand_name(rr_p
->rddata(), pkt
, name
, enc_len
)) {
685 LOG(WARNING
) << "bogus PTR record";
692 std::optional
<RR
> get_MX(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
696 if (rr_p
->rdlength() < 3) {
697 LOG(WARNING
) << "bogus MX record";
701 auto p
= rr_p
->rddata();
702 auto const preference
= as_u16(p
[0], p
[1]);
704 if (!expand_name(p
, pkt
, name
, enc_len
)) {
705 LOG(WARNING
) << "bogus MX record";
709 return RR_MX
{name
, preference
};
712 std::optional
<RR
> get_TXT(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
714 if (rr_p
->rdlength() < 1) {
715 LOG(WARNING
) << "bogus TXT record";
720 auto p
= rr_p
->rddata();
722 if ((p
+ 1 + *p
) > rr_p
->next_rr_name()) {
723 LOG(WARNING
) << "bogus string in TXT record";
727 str
.append(reinterpret_cast<char const*>(p
) + 1, *p
);
729 } while (p
< rr_p
->next_rr_name());
733 std::optional
<RR
> get_AAAA(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
735 if (rr_p
->rdlength() != 16) {
736 LOG(WARNING
) << "bogus AAAA record";
740 return RR_AAAA
{rr_p
->rddata(), rr_p
->rdlength()};
743 std::optional
<RR
> get_RRSIG(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
745 // LOG(WARNING) << "#### FIXME! RRSIG";
746 // return RR_RRSIG{rr_p->rddata(), rr_p->rdlength()});
750 std::optional
<RR
> get_TLSA(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
752 if (rr_p
->rdlength() < 4) {
753 LOG(WARNING
) << "bogus TLSA record";
758 auto p
= rr_p
->rddata();
760 uint8_t cert_usage
= *p
++;
761 uint8_t selector
= *p
++;
762 uint8_t matching_type
= *p
++;
764 std::span
<DNS::message::octet
const> assoc_data
{
765 p
, static_cast<size_t>(rr_p
->rdlength()) - 3};
767 return RR_TLSA
{cert_usage
, selector
, matching_type
, assoc_data
};
770 std::optional
<RR
> get_rr(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
772 auto const typ
= static_cast<DNS::RR_type
>(rr_p
->rr_type());
774 switch (typ
) { // clang-format off
775 case DNS::RR_type::A
: return get_A (rr_p
, pkt
, err
);
776 case DNS::RR_type::CNAME
: return get_CNAME(rr_p
, pkt
, err
);
777 case DNS::RR_type::PTR
: return get_PTR (rr_p
, pkt
, err
);
778 case DNS::RR_type::MX
: return get_MX (rr_p
, pkt
, err
);
779 case DNS::RR_type::TXT
: return get_TXT (rr_p
, pkt
, err
);
780 case DNS::RR_type::AAAA
: return get_AAAA (rr_p
, pkt
, err
);
781 case DNS::RR_type::RRSIG
: return get_RRSIG(rr_p
, pkt
, err
);
782 case DNS::RR_type::TLSA
: return get_TLSA (rr_p
, pkt
, err
);
786 LOG(WARNING
) << "unsupported RR type " << typ
;
790 RR_collection
get_records(message
const& pkt
, bool& bogus_or_indeterminate
)
792 auto const sp
= static_cast<std::span
<DNS::message::octet
const>>(pkt
);
793 auto const sp_end
= sp
.data() + sp
.size();
797 auto const hdr_p
= reinterpret_cast<header
const*>(sp
.data());
799 auto p
= sp
.data() + sizeof(header
);
802 for (auto i
= 0; i
< hdr_p
->qdcount(); ++i
) {
806 CHECK(expand_name(p
, pkt
, qname
, enc_len
));
808 // auto question_p = reinterpret_cast<question const*>(p);
809 p
+= sizeof(question
);
813 for (auto i
= 0; i
< hdr_p
->ancount(); ++i
) {
816 CHECK(expand_name(p
, pkt
, name
, enc_len
));
818 if ((p
+ sizeof(rr
)) > sp_end
) {
819 bogus_or_indeterminate
= true;
820 LOG(WARNING
) << "bad message";
821 return RR_collection
{};
823 auto rr_p
= reinterpret_cast<rr
const*>(p
);
824 if ((p
+ rr_p
->rdlength()) > sp_end
) {
825 bogus_or_indeterminate
= true;
826 LOG(WARNING
) << "bad message";
827 return RR_collection
{};
830 auto rr_ret
= get_rr(rr_p
, pkt
, bogus_or_indeterminate
);
832 if (bogus_or_indeterminate
)
833 return RR_collection
{};
836 ret
.emplace_back(*rr_ret
);
838 p
= rr_p
->next_rr_name();