better with obs forms
[ghsmtp.git] / DNS-message.cpp
blobc3f774ad345d4746b7d21ec93ef19aed98e40742
1 #include "DNS-message.hpp"
3 #include "DNS-iostream.hpp"
4 #include "Domain.hpp"
6 #include <arpa/nameser.h>
8 namespace {
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;
20 1 1 1 1 1 1
21 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
22 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
23 | ID |
24 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
25 |QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE |
26 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
27 | QDCOUNT |
28 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
29 | ANCOUNT |
30 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
31 | NSCOUNT |
32 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
33 | ARCOUNT |
34 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
38 class header {
39 octet id_hi_;
40 octet id_lo_;
42 octet flags_0_{1}; // recursion desired
43 octet flags_1_{0};
45 octet qdcount_hi_{0};
46 octet qdcount_lo_{1}; // 1 question
48 octet ancount_hi_{0};
49 octet ancount_lo_{0};
51 octet nscount_hi_{0};
52 octet nscount_lo_{0};
54 octet arcount_hi_{0};
55 octet arcount_lo_{1}; // 1 additional for the OPT
57 public:
58 explicit header(uint16_t id)
59 : id_hi_(hi(id))
60 , id_lo_(lo(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_); }
72 // clang-format off
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; }
78 // clang-format on
80 uint16_t rcode() const { return flags_1_ & 0xf; }
83 class question {
84 octet qtype_hi_;
85 octet qtype_lo_;
87 octet qclass_hi_;
88 octet qclass_lo_;
90 public:
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
126 +0 (MSB) +1 (LSB)
127 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
128 0: | EXTENDED-RCODE | VERSION |
129 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
130 2: |DO| Z |
131 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
135 class edns0_opt_meta_rr {
136 octet root_domain_name_{0}; // must be zero
138 octet type_hi_{0};
139 octet type_lo_{ns_t_opt};
141 octet class_hi_; // UDP payload size
142 octet class_lo_;
144 octet extended_rcode_{0};
145 octet version_{0};
147 octet z_hi_{0x80}; // "DNSSEC OK" (DO) bit
148 octet z_lo_{0};
150 octet rdlen_hi_{0};
151 octet rdlen_lo_{0};
153 public:
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
169 1 1 1 1 1 1
170 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
171 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
174 / NAME /
176 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
177 | TYPE |
178 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
179 | CLASS |
180 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
181 | TTL |
183 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
184 | RDLENGTH |
185 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
186 / RDATA /
188 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
192 class rr {
193 octet type_hi_;
194 octet type_lo_;
196 octet class_hi_;
197 octet class_lo_;
199 octet ttl_0_;
200 octet ttl_1_;
201 octet ttl_2_;
202 octet ttl_3_;
204 octet rdlength_hi_;
205 octet rdlength_lo_;
207 public:
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_); }
220 auto cdata() const
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))
243 return -1;
245 int length = 0;
246 int nindir = 0; // count indirections
248 while (*encoded) {
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))
255 return -1;
257 auto const offset = (*encoded & ~NS_CMPRSFLGS) << 8 | *(encoded + 1);
258 if (offset >= size(pkt))
259 return -1;
261 encoded = begin(pkt) + offset;
263 ++nindir;
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)
270 return -1;
272 else if (top == 0) {
273 auto offset = *encoded;
274 if (encoded + offset + 1 >= end(pkt))
275 return -1;
277 ++encoded;
279 while (offset--) {
280 length += (*encoded == '.' || *encoded == '\\') ? 2 : 1;
281 encoded++;
284 ++length;
286 else {
287 // RFC 1035 4.1.4 says other options (01, 10) for top 2
288 // bits are reserved.
289 return -1;
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,
301 std::string& name,
302 int& enc_len)
304 name.clear();
306 auto indir = false;
308 auto const nlen = name_length(encoded, pkt);
309 if (nlen < 0) {
310 LOG(WARNING) << "bad name";
311 return false;
314 name.reserve(nlen);
316 if (nlen == 0) {
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)
322 enc_len = 2;
323 else
324 enc_len = 1; // the caller should move one byte to get past this
326 return true;
329 // error-checking done by name_length()
330 auto p = encoded;
331 while (*p) {
332 if ((*p & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
333 if (!indir) {
334 enc_len = uztosl(p + 2 - encoded);
335 indir = true;
337 p = begin(pkt) + ((*p & ~NS_CMPRSFLGS) << 8 | *(p + 1));
339 else {
340 int len = *p;
341 p++;
342 while (len--) {
343 if (*p == '.' || *p == '\\')
344 name += '\\';
345 name += static_cast<char>(*p);
346 p++;
348 name += '.';
352 if (!indir)
353 enc_len = uztosl(p + 1 - encoded);
355 if (name.length() && (name.back() == '.')) {
356 name.pop_back();
359 return true;
362 // return the length of the encoded name
364 int name_put(octet* bfr, char const* name)
366 auto q = bfr;
368 if ((name[0] == '.') && (name[1] == '\0'))
369 name++;
371 while (*name) {
372 if (*name == '.') {
373 LOG(WARNING) << "zero length label";
374 return -1;
377 uint8_t len = 0;
378 char const* p;
380 for (p = name; *p && *p != '.'; p++) {
381 if (*p == '\\' && *(p + 1) != 0)
382 p++;
383 len++;
385 if (len > 63) {
386 // RFC-1035 Section 2.3.4. Size limits
387 LOG(WARNING) << "label exceeds 63 octets";
388 return -1;
391 *q++ = len;
392 for (p = name; *p && *p != '.'; p++) {
393 if (*p == '\\' && *(p + 1) != 0)
394 p++;
395 *q++ = *p;
398 if (!*p)
399 break;
401 name = p + 1;
404 // Add the zero-length label at the end.
405 *q++ = 0;
407 auto const sz = q - bfr;
408 if (sz > 255) {
409 // RFC-1035 Section 2.3.4. Size limits
410 LOG(WARNING) << "domain name exceeds 255 octets";
411 return -1;
414 return sz;
416 } // namespace
418 namespace DNS {
420 uint16_t message::id() const
422 auto const hdr_p = reinterpret_cast<header const*>(begin());
423 return hdr_p->id();
426 size_t min_message_sz() { return sizeof(header); }
428 DNS::message
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
435 sizeof(header) +
436 sizeof(question) +
437 sizeof(edns0_opt_meta_rr); // clang-format on
439 DNS::message::container_t bfr(sz_alloc);
441 auto q = bfr.data();
443 new (q) header(id);
444 q += sizeof(header);
446 auto const len = name_put(q, name);
447 CHECK_GE(len, 1) << "malformed domain name " << name;
448 q += len;
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);
460 bfr.resize(sz);
461 bfr.shrink_to_fit();
463 return DNS::message{std::move(bfr)};
466 void check_answer(bool& nx_domain,
467 bool& bogus_or_indeterminate,
469 uint16_t& rcode,
470 uint16_t& extended_rcode,
472 bool& truncation,
473 bool& authentic_data,
474 bool& has_record,
476 DNS::message const& q,
477 DNS::message const& a,
479 DNS::RR_type type,
480 char const* name)
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]() {
486 auto q_p = begin(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);
490 std::string qname;
491 int name_len;
492 CHECK(expand_name(q_p, q, qname, name_len));
493 q_p += 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();
498 }()};
500 auto const hdr_p = reinterpret_cast<header const*>(begin(a));
502 rcode = hdr_p->rcode();
503 switch (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;
507 default:
508 bogus_or_indeterminate = true;
509 LOG(WARNING) << "name lookup error: " << DNS::rcode_c_str(rcode) << " for "
510 << name << '/' << type;
511 break;
514 truncation = hdr_p->truncation();
515 authentic_data = hdr_p->authentic_data();
516 has_record = hdr_p->ancount() != 0;
518 if (truncation) {
519 bogus_or_indeterminate = true;
520 LOG(WARNING) << "DNS answer truncated for " << name << '/' << type;
521 return;
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 << '/'
529 << type;
530 return;
533 // p is a pointer that pushes forward in the message as we process
534 // each section
535 auto p = begin(a) + sizeof(header);
537 { // make sure the question name matches
538 std::string qname;
539 auto enc_len = 0;
540 if (!expand_name(p, a, qname, enc_len)) {
541 bogus_or_indeterminate = true;
542 LOG(WARNING) << "bad message";
543 return;
545 p += enc_len;
546 if (p >= end(a)) {
547 bogus_or_indeterminate = true;
548 LOG(WARNING) << "bad message";
549 return;
551 if (!Domain::match(qname, name)) {
552 bogus_or_indeterminate = true;
553 LOG(WARNING) << "names don't match, " << qname << " != " << name;
554 return;
558 if ((p + sizeof(question)) >= end(a)) {
559 bogus_or_indeterminate = true;
560 LOG(WARNING) << "bad message";
561 return;
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()
570 << " != " << type;
571 return;
573 if (question_p->qclass() != cls) {
574 bogus_or_indeterminate = true;
575 LOG(WARNING) << "qclasses don't match, " << question_p->qclass()
576 << " != " << cls;
577 return;
580 // answers and nameservers
581 for (auto i = 0; i < (hdr_p->ancount() + hdr_p->nscount()); ++i) {
582 std::string x;
583 auto enc_len = 0;
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
588 << '/' << type;
589 return;
591 p += enc_len;
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) {
598 std::string x;
599 auto enc_len = 0;
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 << '/'
604 << type;
605 return;
607 p += enc_len;
608 auto const rr_p = reinterpret_cast<rr const*>(p);
610 switch (rr_p->rr_type()) {
611 case ns_t_opt: {
612 auto opt_p = reinterpret_cast<edns0_opt_meta_rr const*>(p);
613 extended_rcode = (opt_p->extended_rcode() << 4) + hdr_p->rcode();
614 break;
617 case ns_t_a:
618 case ns_t_aaaa:
619 case ns_t_ns:
620 case ns_t_rrsig:
621 // nameserver records often included with associated address info
622 break;
624 default:
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();
629 break;
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;
639 return;
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";
647 err = true;
648 return {};
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)
655 std::string name;
656 int enc_len;
657 if (!expand_name(rr_p->rddata(), pkt, name, enc_len)) {
658 LOG(WARNING) << "bogus CNAME record";
659 err = true;
660 return {};
662 return RR_CNAME{name};
665 std::optional<RR> get_PTR(rr const* rr_p, DNS::message const& pkt, bool& err)
667 std::string name;
668 int enc_len;
669 if (!expand_name(rr_p->rddata(), pkt, name, enc_len)) {
670 LOG(WARNING) << "bogus PTR record";
671 err = true;
672 return {};
674 return RR_PTR{name};
677 std::optional<RR> get_MX(rr const* rr_p, DNS::message const& pkt, bool& err)
679 std::string name;
680 int enc_len;
681 if (rr_p->rdlength() < 3) {
682 LOG(WARNING) << "bogus MX record";
683 err = true;
684 return {};
686 auto p = rr_p->rddata();
687 auto const preference = as_u16(p[0], p[1]);
688 p += 2;
689 if (!expand_name(p, pkt, name, enc_len)) {
690 LOG(WARNING) << "bogus MX record";
691 err = true;
692 return {};
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";
701 err = true;
702 return {};
704 std::string str;
705 auto p = rr_p->rddata();
706 do {
707 if ((p + 1 + *p) > rr_p->next_rr_name()) {
708 LOG(WARNING) << "bogus string in TXT record";
709 err = true;
710 return {};
712 str.append(reinterpret_cast<char const*>(p) + 1, *p);
713 p = p + *p + 1;
714 } while (p < rr_p->next_rr_name());
715 return RR_TXT{str};
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";
722 err = true;
723 return {};
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()});
732 return {};
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";
739 err = true;
740 return {};
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,
753 assoc_data_sz};
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);
769 default: break;
770 } // clang-format on
772 LOG(WARNING) << "unsupported RR type " << typ;
773 return {};
776 RR_collection get_records(message const& pkt, bool& bogus_or_indeterminate)
778 RR_collection ret;
780 auto const hdr_p = reinterpret_cast<header const*>(begin(pkt));
782 auto p = begin(pkt) + sizeof(header);
784 // skip queries
785 for (auto i = 0; i < hdr_p->qdcount(); ++i) {
786 std::string qname;
787 auto enc_len = 0;
789 CHECK(expand_name(p, pkt, qname, enc_len));
790 p += enc_len;
791 // auto question_p = reinterpret_cast<question const*>(p);
792 p += sizeof(question);
795 // get answers
796 for (auto i = 0; i < hdr_p->ancount(); ++i) {
797 std::string name;
798 auto enc_len = 0;
799 CHECK(expand_name(p, pkt, name, enc_len));
800 p += 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{};
818 if (rr_ret)
819 ret.emplace_back(*rr_ret);
821 p = rr_p->next_rr_name();
824 return ret;
827 } // namespace DNS