1 #include "DNS-message.hpp"
3 #include "DNS-iostream.hpp"
6 #include <arpa/nameser.h>
9 using octet
= unsigned char;
11 octet
constexpr lo(uint16_t n
) { return octet(n
& 0xFF); }
12 octet
constexpr hi(uint16_t n
) { return octet((n
>> 8) & 0xFF); }
14 uint16_t constexpr 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 // Allow the caller to pass us buf + len and have us check for it.
242 if (encoded
>= end(pkt
))
246 int nindir
= 0; // count indirections
250 auto const top
= (*encoded
& NS_CMPRSFLGS
);
252 if (top
== NS_CMPRSFLGS
) {
253 // Check the offset and go there.
254 if (encoded
+ 1 >= end(pkt
))
257 auto const offset
= (*encoded
& ~NS_CMPRSFLGS
) << 8 | *(encoded
+ 1);
258 if (offset
>= size(pkt
))
261 encoded
= begin(pkt
) + offset
;
265 auto constexpr max_indirs
= 50; // maximum indirections allowed for a name
267 // If we've seen more indirects than the message length, or over
268 // some limit, then there's a loop.
269 if (nindir
> size(pkt
) || nindir
> max_indirs
)
273 auto offset
= *encoded
;
274 if (encoded
+ offset
+ 1 >= end(pkt
))
280 length
+= (*encoded
== '.' || *encoded
== '\\') ? 2 : 1;
287 // RFC 1035 4.1.4 says other options (01, 10) for top 2
288 // bits are reserved.
293 // If there were any labels at all, then the number of dots is one
294 // less than the number of labels, so subtract one.
296 return length
? length
- 1 : length
;
299 bool expand_name(octet
const* encoded
,
300 DNS::message
const& pkt
,
308 auto const nlen
= name_length(encoded
, pkt
);
310 LOG(WARNING
) << "bad name";
317 // RFC 2181 says this should be ".": the root of the DNS tree.
318 // Since this function strips trailing dots though, it becomes ""s
320 // indirect root label (like 0xc0 0x0c) is 2 bytes long
321 if ((*encoded
& NS_CMPRSFLGS
) == NS_CMPRSFLGS
)
324 enc_len
= 1; // the caller should move one byte to get past this
329 // error-checking done by name_length()
332 if ((*p
& NS_CMPRSFLGS
) == NS_CMPRSFLGS
) {
334 enc_len
= uztosl(p
+ 2 - encoded
);
337 p
= begin(pkt
) + ((*p
& ~NS_CMPRSFLGS
) << 8 | *(p
+ 1));
343 if (*p
== '.' || *p
== '\\')
345 name
+= static_cast<char>(*p
);
353 enc_len
= uztosl(p
+ 1 - encoded
);
355 if (name
.length() && (name
.back() == '.')) {
362 // return the length of the encoded name
364 int name_put(octet
* bfr
, char const* name
)
368 if ((name
[0] == '.') && (name
[1] == '\0'))
373 LOG(WARNING
) << "zero length label";
380 for (p
= name
; *p
&& *p
!= '.'; p
++) {
381 if (*p
== '\\' && *(p
+ 1) != 0)
386 // RFC-1035 Section 2.3.4. Size limits
387 LOG(WARNING
) << "label exceeds 63 octets";
392 for (p
= name
; *p
&& *p
!= '.'; p
++) {
393 if (*p
== '\\' && *(p
+ 1) != 0)
404 // Add the zero-length label at the end.
407 auto const sz
= q
- bfr
;
409 // RFC-1035 Section 2.3.4. Size limits
410 LOG(WARNING
) << "domain name exceeds 255 octets";
420 uint16_t message::id() const
422 auto const hdr_p
= reinterpret_cast<header
const*>(begin());
426 size_t min_message_sz() { return sizeof(header
); }
429 create_question(char const* name
, DNS::RR_type type
, uint16_t cls
, uint16_t id
)
431 // size to allocate may be larger than needed if backslash escapes
432 // are used in domain name
434 auto const sz_alloc
= strlen(name
) + 2 + // clang-format off
437 sizeof(edns0_opt_meta_rr
); // clang-format on
439 DNS::message::container_t
bfr(sz_alloc
);
446 auto const len
= name_put(q
, name
);
447 CHECK_GE(len
, 1) << "malformed domain name " << name
;
450 new (q
) question(type
, cls
);
451 q
+= sizeof(question
);
453 new (q
) edns0_opt_meta_rr(Config::max_udp_sz
);
454 q
+= sizeof(edns0_opt_meta_rr
);
456 // verify constructed size is less than or equal to allocated size
457 auto const sz
= q
- bfr
.data();
458 CHECK_LE(sz
, sz_alloc
);
463 return DNS::message
{std::move(bfr
)};
466 void check_answer(bool& nx_domain
,
467 bool& bogus_or_indeterminate
,
470 uint16_t& extended_rcode
,
473 bool& authentic_data
,
476 DNS::message
const& q
,
477 DNS::message
const& a
,
482 // We grab some stuff from the question we generated. This is not
483 // an un-trusted datum from afar.
485 auto const cls
{[type
= type
, &q
]() {
487 auto const q_hdr_p
= reinterpret_cast<header
const*>(q_p
);
488 CHECK_EQ(q_hdr_p
->qdcount(), uint16_t(1));
489 q_p
+= sizeof(header
);
492 CHECK(expand_name(q_p
, q
, qname
, name_len
));
494 auto const question_p
= reinterpret_cast<question
const*>(q_p
);
495 CHECK(question_p
->qtype() == type
)
496 << question_p
->qtype() << " != " << type
<< '\n';
497 return question_p
->qclass();
500 auto const hdr_p
= reinterpret_cast<header
const*>(begin(a
));
502 rcode
= hdr_p
->rcode();
504 case ns_r_noerror
: break;
505 case ns_r_nxdomain
: nx_domain
= true; break;
506 case ns_r_servfail
: bogus_or_indeterminate
= true; break;
508 bogus_or_indeterminate
= true;
509 LOG(WARNING
) << "name lookup error: " << DNS::rcode_c_str(rcode
) << " for "
510 << name
<< '/' << type
;
514 truncation
= hdr_p
->truncation();
515 authentic_data
= hdr_p
->authentic_data();
516 has_record
= hdr_p
->ancount() != 0;
519 bogus_or_indeterminate
= true;
520 LOG(WARNING
) << "DNS answer truncated for " << name
<< '/' << type
;
524 // check the question part of the reply
526 if (hdr_p
->qdcount() != 1) {
527 bogus_or_indeterminate
= true;
528 LOG(WARNING
) << "question not copied into answer for " << name
<< '/'
533 // p is a pointer that pushes forward in the message as we process
535 auto p
= begin(a
) + sizeof(header
);
537 { // make sure the question name matches
540 if (!expand_name(p
, a
, qname
, enc_len
)) {
541 bogus_or_indeterminate
= true;
542 LOG(WARNING
) << "bad message";
547 bogus_or_indeterminate
= true;
548 LOG(WARNING
) << "bad message";
551 if (!Domain::match(qname
, name
)) {
552 bogus_or_indeterminate
= true;
553 LOG(WARNING
) << "names don't match, " << qname
<< " != " << name
;
558 if ((p
+ sizeof(question
)) >= end(a
)) {
559 bogus_or_indeterminate
= true;
560 LOG(WARNING
) << "bad message";
564 auto question_p
= reinterpret_cast<question
const*>(p
);
565 p
+= sizeof(question
);
567 if (question_p
->qtype() != type
) {
568 bogus_or_indeterminate
= true;
569 LOG(WARNING
) << "qtypes don't match, " << question_p
->qtype()
573 if (question_p
->qclass() != cls
) {
574 bogus_or_indeterminate
= true;
575 LOG(WARNING
) << "qclasses don't match, " << question_p
->qclass()
580 // answers and nameservers
581 for (auto i
= 0; i
< (hdr_p
->ancount() + hdr_p
->nscount()); ++i
) {
584 if (!expand_name(p
, a
, x
, enc_len
) ||
585 ((p
+ enc_len
+ sizeof(rr
)) > end(a
))) {
586 bogus_or_indeterminate
= true;
587 LOG(WARNING
) << "bad message in answer or nameserver section for " << name
592 auto const rr_p
= reinterpret_cast<rr
const*>(p
);
593 p
= rr_p
->next_rr_name();
596 // check additional section for OPT record
597 for (auto i
= 0; i
< hdr_p
->arcount(); ++i
) {
600 if (!expand_name(p
, a
, x
, enc_len
) ||
601 ((p
+ enc_len
+ sizeof(rr
)) > end(a
))) {
602 bogus_or_indeterminate
= true;
603 LOG(WARNING
) << "bad message in additional section for " << name
<< '/'
608 auto const rr_p
= reinterpret_cast<rr
const*>(p
);
610 switch (rr_p
->rr_type()) {
612 auto opt_p
= reinterpret_cast<edns0_opt_meta_rr
const*>(p
);
613 extended_rcode
= (opt_p
->extended_rcode() << 4) + hdr_p
->rcode();
621 // nameserver records often included with associated address info
625 LOG(INFO
) << "unknown additional record, name == " << name
;
626 LOG(INFO
) << "rr_p->type() == " << rr_p
->rr_type();
627 LOG(INFO
) << "rr_p->class() == " << rr_p
->rr_class();
628 LOG(INFO
) << "rr_p->ttl() == " << rr_p
->rr_ttl();
632 p
= rr_p
->next_rr_name();
635 auto size_check
= p
- begin(a
);
636 if (size_check
!= size(a
)) {
637 bogus_or_indeterminate
= true;
638 LOG(WARNING
) << "bad message size for " << name
<< '/' << type
;
643 std::optional
<RR
> get_A(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
645 if (rr_p
->rdlength() != 4) {
646 LOG(WARNING
) << "bogus A record";
650 return RR_A
{rr_p
->rddata(), rr_p
->rdlength()};
653 std::optional
<RR
> get_CNAME(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
657 if (!expand_name(rr_p
->rddata(), pkt
, name
, enc_len
)) {
658 LOG(WARNING
) << "bogus CNAME record";
662 return RR_CNAME
{name
};
665 std::optional
<RR
> get_PTR(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
669 if (!expand_name(rr_p
->rddata(), pkt
, name
, enc_len
)) {
670 LOG(WARNING
) << "bogus PTR record";
677 std::optional
<RR
> get_MX(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
681 if (rr_p
->rdlength() < 3) {
682 LOG(WARNING
) << "bogus MX record";
686 auto p
= rr_p
->rddata();
687 auto const preference
= as_u16(p
[0], p
[1]);
689 if (!expand_name(p
, pkt
, name
, enc_len
)) {
690 LOG(WARNING
) << "bogus MX record";
694 return RR_MX
{name
, preference
};
697 std::optional
<RR
> get_TXT(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
699 if (rr_p
->rdlength() < 1) {
700 LOG(WARNING
) << "bogus TXT record";
705 auto p
= rr_p
->rddata();
707 if ((p
+ 1 + *p
) > rr_p
->next_rr_name()) {
708 LOG(WARNING
) << "bogus string in TXT record";
712 str
.append(reinterpret_cast<char const*>(p
) + 1, *p
);
714 } while (p
< rr_p
->next_rr_name());
718 std::optional
<RR
> get_AAAA(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
720 if (rr_p
->rdlength() != 16) {
721 LOG(WARNING
) << "bogus AAAA record";
725 return RR_AAAA
{rr_p
->rddata(), rr_p
->rdlength()};
728 std::optional
<RR
> get_RRSIG(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
730 // LOG(WARNING) << "#### FIXME! RRSIG";
731 // return RR_RRSIG{rr_p->rddata(), rr_p->rdlength()});
735 std::optional
<RR
> get_TLSA(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
737 if (rr_p
->rdlength() < 4) {
738 LOG(WARNING
) << "bogus TLSA record";
743 auto p
= rr_p
->rddata();
745 uint8_t cert_usage
= *p
++;
746 uint8_t selector
= *p
++;
747 uint8_t matching_type
= *p
++;
749 uint8_t const* assoc_data
= p
;
750 size_t assoc_data_sz
= rr_p
->rdlength() - 3;
752 return RR_TLSA
{cert_usage
, selector
, matching_type
, assoc_data
,
756 std::optional
<RR
> get_rr(rr
const* rr_p
, DNS::message
const& pkt
, bool& err
)
758 auto const typ
= static_cast<DNS::RR_type
>(rr_p
->rr_type());
760 switch (typ
) { // clang-format off
761 case DNS::RR_type::A
: return get_A (rr_p
, pkt
, err
);
762 case DNS::RR_type::CNAME
: return get_CNAME(rr_p
, pkt
, err
);
763 case DNS::RR_type::PTR
: return get_PTR (rr_p
, pkt
, err
);
764 case DNS::RR_type::MX
: return get_MX (rr_p
, pkt
, err
);
765 case DNS::RR_type::TXT
: return get_TXT (rr_p
, pkt
, err
);
766 case DNS::RR_type::AAAA
: return get_AAAA (rr_p
, pkt
, err
);
767 case DNS::RR_type::RRSIG
: return get_RRSIG(rr_p
, pkt
, err
);
768 case DNS::RR_type::TLSA
: return get_TLSA (rr_p
, pkt
, err
);
772 LOG(WARNING
) << "unsupported RR type " << typ
;
776 RR_collection
get_records(message
const& pkt
, bool& bogus_or_indeterminate
)
780 auto const hdr_p
= reinterpret_cast<header
const*>(begin(pkt
));
782 auto p
= begin(pkt
) + sizeof(header
);
785 for (auto i
= 0; i
< hdr_p
->qdcount(); ++i
) {
789 CHECK(expand_name(p
, pkt
, qname
, enc_len
));
791 // auto question_p = reinterpret_cast<question const*>(p);
792 p
+= sizeof(question
);
796 for (auto i
= 0; i
< hdr_p
->ancount(); ++i
) {
799 CHECK(expand_name(p
, pkt
, name
, enc_len
));
801 if ((p
+ sizeof(rr
)) > end(pkt
)) {
802 bogus_or_indeterminate
= true;
803 LOG(WARNING
) << "bad message";
804 return RR_collection
{};
806 auto rr_p
= reinterpret_cast<rr
const*>(p
);
807 if ((p
+ rr_p
->rdlength()) > end(pkt
)) {
808 bogus_or_indeterminate
= true;
809 LOG(WARNING
) << "bad message";
810 return RR_collection
{};
813 auto rr_ret
= get_rr(rr_p
, pkt
, bogus_or_indeterminate
);
815 if (bogus_or_indeterminate
)
816 return RR_collection
{};
819 ret
.emplace_back(*rr_ret
);
821 p
= rr_p
->next_rr_name();