/* * Copyright (c) 2024, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include namespace DNS::Messages { String Options::to_string() const { StringBuilder builder; builder.appendff("QR: {}, Opcode: {}, AA: {}, TC: {}, RD: {}, RA: {}, AD: {}, CD: {}, RCODE: {}", is_question() ? "Q" : "R", Messages::to_string(op_code()), is_authoritative_answer(), is_truncated(), recursion_desired(), recursion_available(), authenticated_data(), checking_disabled(), Messages::to_string(response_code())); return MUST(builder.to_string()); } StringView to_string(Options::ResponseCode code) { switch (code) { case Options::ResponseCode::NoError: return "NoError"sv; case Options::ResponseCode::FormatError: return "FormatError"sv; case Options::ResponseCode::ServerFailure: return "ServerFailure"sv; case Options::ResponseCode::NameError: return "NameError"sv; case Options::ResponseCode::NotImplemented: return "NotImplemented"sv; case Options::ResponseCode::Refused: return "Refused"sv; default: return "UNKNOWN"sv; } } ErrorOr Message::from_raw(AK::Stream& stream) { CountingStream counting_stream { MaybeOwned(stream) }; auto context = ParseContext { counting_stream, make>() }; return from_raw(context); } ErrorOr Message::from_raw(ParseContext& ctx) { // RFC 1035, 4.1. (Messages) Format. // | Header | // | Question | the question for the name server // | Answer | RRs answering the question // | Authority | RRs pointing toward an authority // | Additional | RRs holding additional information // // The header section is always present. The header includes fields that // specify which of the remaining sections are present, and also specify // whether the message is a query or a response, a standard query or some // other opcode, etc. Header header; Bytes header_bytes { &header, sizeof(Header) }; TRY(ctx.stream.read_until_filled(header_bytes)); Message message {}; message.header = header; for (size_t i = 0; i < header.question_count; ++i) { auto question = TRY(Question::from_raw(ctx)); message.questions.append(move(question)); } for (size_t i = 0; i < header.answer_count; ++i) { auto answer = TRY(ResourceRecord::from_raw(ctx)); message.answers.append(move(answer)); } for (size_t i = 0; i < header.authority_count; ++i) { auto authority = TRY(ResourceRecord::from_raw(ctx)); message.authorities.append(move(authority)); } for (size_t i = 0; i < header.additional_count; ++i) { auto additional = TRY(ResourceRecord::from_raw(ctx)); message.additional_records.append(move(additional)); } return message; } ErrorOr Message::to_raw(ByteBuffer& out) const { // NOTE: This is minimally implemented to allow for sending queries, // server-side responses are not implemented yet. VERIFY(header.answer_count == 0); VERIFY(header.authority_count == 0); auto start_size = out.size(); auto header_bytes = TRY(out.get_bytes_for_writing(sizeof(Header))); memcpy(header_bytes.data(), &header, sizeof(Header)); for (size_t i = 0; i < header.question_count; i++) TRY(questions[i].to_raw(out)); for (size_t i = 0; i < header.additional_count; i++) TRY(additional_records[i].to_raw(out)); return out.size() - start_size; } ErrorOr Message::format_for_log() const { StringBuilder builder; builder.appendff("ID: {}\n", header.id); builder.appendff("Flags: {} ({:x})\n", header.options.to_string(), header.options.raw); builder.appendff("qdcount: {}, ancount: {}, nscount: {}, arcount: {}\n", header.question_count, header.answer_count, header.authority_count, header.additional_count); if (header.question_count > 0) { builder.appendff("Questions:\n"); for (auto& q : questions) builder.appendff(" {} {} {}\n", q.name.to_string(), to_string(q.class_), to_string(q.type)); } if (header.answer_count > 0) { builder.appendff("Answers:\n"); for (auto& a : answers) { builder.appendff(" {} {} {}\n", a.name.to_string(), to_string(a.class_), to_string(a.type)); a.record.visit( [&](auto const& record) { builder.appendff(" {}\n", MUST(record.to_string())); }, [&](ByteBuffer const& raw) { builder.appendff(" {:hex-dump}\n", raw.bytes()); }); } } if (header.authority_count > 0) { builder.appendff("Authorities:\n"); for (auto& a : authorities) { builder.appendff(" {} {} {}\n", a.name.to_string(), to_string(a.class_), to_string(a.type)); a.record.visit( [&](auto const& record) { builder.appendff(" {}\n", MUST(record.to_string())); }, [&](ByteBuffer const& raw) { builder.appendff(" {:hex-dump}\n", raw.bytes()); }); } } if (header.additional_count > 0) { builder.appendff("Additional:\n"); for (auto& a : additional_records) { builder.appendff(" {} {} {}\n", a.name.to_string(), to_string(a.type), to_string(a.class_)); a.record.visit( [&](auto const& record) { builder.appendff(" {}\n", MUST(record.to_string())); }, [&](ByteBuffer const& raw) { builder.appendff(" {:hex-dump}\n", raw.bytes()); }); } } return builder.to_string(); } ErrorOr Question::from_raw(ParseContext& ctx) { // RFC 1035, 4.1.2. Question section format. // + + // | QNAME | a domain name represented as a sequence of labels // + + // | QTYPE | a two octet code which specifies the type of the query // | QCLASS | a two octet code that specifies the class of the query auto name = TRY(DomainName::from_raw(ctx)); auto type = static_cast(static_cast(TRY(ctx.stream.read_value>()))); auto class_ = static_cast(static_cast(TRY(ctx.stream.read_value>()))); return Question { move(name), type, class_ }; } ErrorOr Question::to_raw(ByteBuffer& out) const { TRY(name.to_raw(out)); auto type_bytes = TRY(out.get_bytes_for_writing(2)); auto net_type = static_cast>(to_underlying(type)); memcpy(type_bytes.data(), &net_type, 2); auto class_bytes = TRY(out.get_bytes_for_writing(2)); auto net_class = static_cast>(to_underlying(class_)); memcpy(class_bytes.data(), &net_class, 2); return {}; } StringView to_string(ResourceType type) { switch (type) { case ResourceType::Reserved: return "Reserved"sv; case ResourceType::A: return "A"sv; case ResourceType::NS: return "NS"sv; case ResourceType::MD: return "MD"sv; case ResourceType::MF: return "MF"sv; case ResourceType::CNAME: return "CNAME"sv; case ResourceType::SOA: return "SOA"sv; case ResourceType::MB: return "MB"sv; case ResourceType::MG: return "MG"sv; case ResourceType::MR: return "MR"sv; case ResourceType::NULL_: return "NULL_"sv; case ResourceType::WKS: return "WKS"sv; case ResourceType::PTR: return "PTR"sv; case ResourceType::HINFO: return "HINFO"sv; case ResourceType::MINFO: return "MINFO"sv; case ResourceType::MX: return "MX"sv; case ResourceType::TXT: return "TXT"sv; case ResourceType::RP: return "RP"sv; case ResourceType::AFSDB: return "AFSDB"sv; case ResourceType::X25: return "X25"sv; case ResourceType::ISDN: return "ISDN"sv; case ResourceType::RT: return "RT"sv; case ResourceType::NSAP: return "NSAP"sv; case ResourceType::NSAP_PTR: return "NSAP_PTR"sv; case ResourceType::SIG: return "SIG"sv; case ResourceType::KEY: return "KEY"sv; case ResourceType::PX: return "PX"sv; case ResourceType::GPOS: return "GPOS"sv; case ResourceType::AAAA: return "AAAA"sv; case ResourceType::LOC: return "LOC"sv; case ResourceType::NXT: return "NXT"sv; case ResourceType::EID: return "EID"sv; case ResourceType::NIMLOC: return "NIMLOC"sv; case ResourceType::SRV: return "SRV"sv; case ResourceType::ATMA: return "ATMA"sv; case ResourceType::NAPTR: return "NAPTR"sv; case ResourceType::KX: return "KX"sv; case ResourceType::CERT: return "CERT"sv; case ResourceType::A6: return "A6"sv; case ResourceType::DNAME: return "DNAME"sv; case ResourceType::SINK: return "SINK"sv; case ResourceType::OPT: return "OPT"sv; case ResourceType::APL: return "APL"sv; case ResourceType::DS: return "DS"sv; case ResourceType::SSHFP: return "SSHFP"sv; case ResourceType::IPSECKEY: return "IPSECKEY"sv; case ResourceType::RRSIG: return "RRSIG"sv; case ResourceType::NSEC: return "NSEC"sv; case ResourceType::DNSKEY: return "DNSKEY"sv; case ResourceType::DHCID: return "DHCID"sv; case ResourceType::NSEC3: return "NSEC3"sv; case ResourceType::NSEC3PARAM: return "NSEC3PARAM"sv; case ResourceType::TLSA: return "TLSA"sv; case ResourceType::SMIMEA: return "SMIMEA"sv; case ResourceType::HIP: return "HIP"sv; case ResourceType::NINFO: return "NINFO"sv; case ResourceType::RKEY: return "RKEY"sv; case ResourceType::TALINK: return "TALINK"sv; case ResourceType::CDS: return "CDS"sv; case ResourceType::CDNSKEY: return "CDNSKEY"sv; case ResourceType::OPENPGPKEY: return "OPENPGPKEY"sv; case ResourceType::CSYNC: return "CSYNC"sv; case ResourceType::ZONEMD: return "ZONEMD"sv; case ResourceType::SVCB: return "SVCB"sv; case ResourceType::HTTPS: return "HTTPS"sv; case ResourceType::SPF: return "SPF"sv; case ResourceType::UINFO: return "UINFO"sv; case ResourceType::UID: return "UID"sv; case ResourceType::GID: return "GID"sv; case ResourceType::UNSPEC: return "UNSPEC"sv; case ResourceType::NID: return "NID"sv; case ResourceType::L32: return "L32"sv; case ResourceType::L64: return "L64"sv; case ResourceType::LP: return "LP"sv; case ResourceType::EUI48: return "EUI48"sv; case ResourceType::EUI64: return "EUI64"sv; case ResourceType::NXNAME: return "NXNAME"sv; case ResourceType::TKEY: return "TKEY"sv; case ResourceType::TSIG: return "TSIG"sv; case ResourceType::IXFR: return "IXFR"sv; case ResourceType::AXFR: return "AXFR"sv; case ResourceType::MAILB: return "MAILB"sv; case ResourceType::MAILA: return "MAILA"sv; case ResourceType::ANY: return "ANY"sv; case ResourceType::URI: return "URI"sv; case ResourceType::CAA: return "CAA"sv; case ResourceType::AVC: return "AVC"sv; case ResourceType::DOA: return "DOA"sv; case ResourceType::AMTRELAY: return "AMTRELAY"sv; case ResourceType::RESINFO: return "RESINFO"sv; case ResourceType::WALLET: return "WALLET"sv; case ResourceType::CLA: return "CLA"sv; case ResourceType::IPN: return "IPN"sv; case ResourceType::TA: return "TA"sv; case ResourceType::DLV: return "DLV"sv; default: return "UNKNOWN"sv; } } Optional resource_type_from_string(StringView name) { if (name == "Reserved"sv) return ResourceType::Reserved; if (name == "A"sv) return ResourceType::A; if (name == "NS"sv) return ResourceType::NS; if (name == "MD"sv) return ResourceType::MD; if (name == "MF"sv) return ResourceType::MF; if (name == "CNAME"sv) return ResourceType::CNAME; if (name == "SOA"sv) return ResourceType::SOA; if (name == "MB"sv) return ResourceType::MB; if (name == "MG"sv) return ResourceType::MG; if (name == "MR"sv) return ResourceType::MR; if (name == "NULL_"sv) return ResourceType::NULL_; if (name == "WKS"sv) return ResourceType::WKS; if (name == "PTR"sv) return ResourceType::PTR; if (name == "HINFO"sv) return ResourceType::HINFO; if (name == "MINFO"sv) return ResourceType::MINFO; if (name == "MX"sv) return ResourceType::MX; if (name == "TXT"sv) return ResourceType::TXT; if (name == "RP"sv) return ResourceType::RP; if (name == "AFSDB"sv) return ResourceType::AFSDB; if (name == "X25"sv) return ResourceType::X25; if (name == "ISDN"sv) return ResourceType::ISDN; if (name == "RT"sv) return ResourceType::RT; if (name == "NSAP"sv) return ResourceType::NSAP; if (name == "NSAP_PTR"sv) return ResourceType::NSAP_PTR; if (name == "SIG"sv) return ResourceType::SIG; if (name == "KEY"sv) return ResourceType::KEY; if (name == "PX"sv) return ResourceType::PX; if (name == "GPOS"sv) return ResourceType::GPOS; if (name == "AAAA"sv) return ResourceType::AAAA; if (name == "LOC"sv) return ResourceType::LOC; if (name == "NXT"sv) return ResourceType::NXT; if (name == "EID"sv) return ResourceType::EID; if (name == "NIMLOC"sv) return ResourceType::NIMLOC; if (name == "SRV"sv) return ResourceType::SRV; if (name == "ATMA"sv) return ResourceType::ATMA; if (name == "NAPTR"sv) return ResourceType::NAPTR; if (name == "KX"sv) return ResourceType::KX; if (name == "CERT"sv) return ResourceType::CERT; if (name == "A6"sv) return ResourceType::A6; if (name == "DNAME"sv) return ResourceType::DNAME; if (name == "SINK"sv) return ResourceType::SINK; if (name == "OPT"sv) return ResourceType::OPT; if (name == "APL"sv) return ResourceType::APL; if (name == "DS"sv) return ResourceType::DS; if (name == "SSHFP"sv) return ResourceType::SSHFP; if (name == "IPSECKEY"sv) return ResourceType::IPSECKEY; if (name == "RRSIG"sv) return ResourceType::RRSIG; if (name == "NSEC"sv) return ResourceType::NSEC; if (name == "DNSKEY"sv) return ResourceType::DNSKEY; if (name == "DHCID"sv) return ResourceType::DHCID; if (name == "NSEC3"sv) return ResourceType::NSEC3; if (name == "NSEC3PARAM"sv) return ResourceType::NSEC3PARAM; if (name == "TLSA"sv) return ResourceType::TLSA; if (name == "SMIMEA"sv) return ResourceType::SMIMEA; if (name == "HIP"sv) return ResourceType::HIP; if (name == "NINFO"sv) return ResourceType::NINFO; if (name == "RKEY"sv) return ResourceType::RKEY; if (name == "TALINK"sv) return ResourceType::TALINK; if (name == "CDS"sv) return ResourceType::CDS; if (name == "CDNSKEY"sv) return ResourceType::CDNSKEY; if (name == "OPENPGPKEY"sv) return ResourceType::OPENPGPKEY; if (name == "CSYNC"sv) return ResourceType::CSYNC; if (name == "ZONEMD"sv) return ResourceType::ZONEMD; if (name == "SVCB"sv) return ResourceType::SVCB; if (name == "HTTPS"sv) return ResourceType::HTTPS; if (name == "SPF"sv) return ResourceType::SPF; if (name == "UINFO"sv) return ResourceType::UINFO; if (name == "UID"sv) return ResourceType::UID; if (name == "GID"sv) return ResourceType::GID; if (name == "UNSPEC"sv) return ResourceType::UNSPEC; if (name == "NID"sv) return ResourceType::NID; if (name == "L32"sv) return ResourceType::L32; if (name == "L64"sv) return ResourceType::L64; if (name == "LP"sv) return ResourceType::LP; if (name == "EUI48"sv) return ResourceType::EUI48; if (name == "EUI64"sv) return ResourceType::EUI64; if (name == "NXNAME"sv) return ResourceType::NXNAME; if (name == "TKEY"sv) return ResourceType::TKEY; if (name == "TSIG"sv) return ResourceType::TSIG; if (name == "IXFR"sv) return ResourceType::IXFR; if (name == "AXFR"sv) return ResourceType::AXFR; if (name == "MAILB"sv) return ResourceType::MAILB; if (name == "MAILA"sv) return ResourceType::MAILA; if (name == "ANY"sv) return ResourceType::ANY; if (name == "URI"sv) return ResourceType::URI; if (name == "CAA"sv) return ResourceType::CAA; if (name == "AVC"sv) return ResourceType::AVC; if (name == "DOA"sv) return ResourceType::DOA; if (name == "AMTRELAY"sv) return ResourceType::AMTRELAY; if (name == "RESINFO"sv) return ResourceType::RESINFO; if (name == "WALLET"sv) return ResourceType::WALLET; if (name == "CLA"sv) return ResourceType::CLA; if (name == "IPN"sv) return ResourceType::IPN; if (name == "TA"sv) return ResourceType::TA; if (name == "DLV"sv) return ResourceType::DLV; return {}; } StringView to_string(Class class_) { switch (class_) { case Class::IN: return "IN"sv; case Class::CH: return "CH"sv; case Class::HS: return "HS"sv; default: return "UNKNOWN"sv; } } StringView to_string(OpCode code) { if ((to_underlying(code) & to_underlying(OpCode::Reserved)) != 0) return "Reserved"sv; switch (code) { case OpCode::Query: return "Query"sv; case OpCode::IQuery: return "IQuery"sv; case OpCode::Status: return "Status"sv; case OpCode::Notify: return "Notify"sv; case OpCode::Update: return "Update"sv; case OpCode::DSO: return "DSO"sv; default: return "UNKNOWN"sv; } } DomainName DomainName::from_string(StringView name) { DomainName domain_name; name.for_each_split_view('.', SplitBehavior::Nothing, [&](StringView piece) { domain_name.labels.append(piece); }); return domain_name; } ErrorOr DomainName::from_raw(ParseContext& ctx) { // RFC 1035, 4.1.2. Question section format. // QNAME a domain name represented as a sequence of labels, where // each label consists of a length octet followed by that // number of octets. The domain name terminates with the // zero length octet for the null label of the root. Note // that this field may be an odd number of octets; no // padding is used. DomainName name; auto input_offset_marker = ctx.stream.read_bytes(); while (true) { auto length = TRY(ctx.stream.read_value()); if (length == 0) break; constexpr static u8 OffsetMarkerMask = 0b11000000; if ((length & OffsetMarkerMask) == OffsetMarkerMask) { // This is a pointer to a prior domain name. u16 const offset = static_cast(length & ~OffsetMarkerMask) << 8 | TRY(ctx.stream.read_value()); if (auto it = ctx.pointers->find_largest_not_above_iterator(offset); !it.is_end()) { auto labels = it->labels; for (auto& entry : labels) name.labels.append(entry); break; } dbgln("Invalid domain name pointer in label, no prior domain name found around offset {}", offset); return Error::from_string_literal("Invalid domain name pointer in label"); } ByteBuffer content; TRY(ctx.stream.read_until_filled(TRY(content.get_bytes_for_writing(length)))); name.labels.append(ByteString::copy(content)); } ctx.pointers->insert(input_offset_marker, name); return name; } ErrorOr DomainName::to_raw(ByteBuffer& out) const { for (auto& label : labels) { VERIFY(label.length() <= 63); auto size_bytes = TRY(out.get_bytes_for_writing(1)); u8 size = static_cast(label.length()); memcpy(size_bytes.data(), &size, 1); auto content_bytes = TRY(out.get_bytes_for_writing(label.length())); memcpy(content_bytes.data(), label.characters(), label.length()); } TRY(out.try_append(0)); return {}; } String DomainName::to_string() const { StringBuilder builder; for (size_t i = 0; i < labels.size(); ++i) { builder.append(labels[i]); builder.append('.'); } return MUST(builder.to_string()); } class RecordingStream final : public Stream { public: explicit RecordingStream(Stream& stream) : m_stream(stream) { } ByteBuffer take_recorded_data() && { return move(m_recorded_data); } virtual ErrorOr read_some(Bytes bytes) override { auto result = TRY(m_stream->read_some(bytes)); m_recorded_data.append(result.data(), result.size()); return result; } virtual ErrorOr discard(size_t discarded_bytes) override { auto space = TRY(m_recorded_data.get_bytes_for_writing(discarded_bytes)); TRY(m_stream->read_until_filled(space)); return {}; } virtual ErrorOr write_some(ReadonlyBytes bytes) override { return m_stream->write_some(bytes); } virtual bool is_eof() const override { return m_stream->is_eof(); } virtual bool is_open() const override { return m_stream->is_open(); } virtual void close() override { m_stream->close(); } private: MaybeOwned m_stream; ByteBuffer m_recorded_data; }; ErrorOr ResourceRecord::from_raw(ParseContext& ctx) { // RFC 1035, 4.1.3. Resource record format. // + + // | NAME | a domain name to which this resource record pertains // + + // | TYPE | two octets containing one of the RR type codes // | CLASS | two octets containing one of the RR class codes // | TTL | a 32-bit unsigned integer that specifies the time interval // | | that the resource record may be cached // | RDLENGTH | an unsigned 16-bit integer that specifies the length in // | | octets of the RDATA field // | RDATA | a variable length string of octets that describes the resource ByteBuffer rdata; ByteBuffer rr_raw_data; DomainName name; ResourceType type; Class class_; u32 ttl; { RecordingStream rr_stream { ctx.stream }; CountingStream rr_counting_stream { MaybeOwned(rr_stream) }; ParseContext rr_ctx { rr_counting_stream, move(ctx.pointers) }; ScopeGuard guard([&] { ctx.pointers = move(rr_ctx.pointers); }); name = TRY(DomainName::from_raw(rr_ctx)); type = static_cast(static_cast(TRY(rr_ctx.stream.read_value>()))); if (type == ResourceType::OPT) { auto record = ResourceRecord { move(name), type, Class::IN, 0, TRY(Records::OPT::from_raw(rr_ctx)), {}, }; record.raw = move(rr_stream).take_recorded_data(); return record; } class_ = static_cast(static_cast(TRY(rr_ctx.stream.read_value>()))); ttl = static_cast(TRY(rr_ctx.stream.read_value>())); auto rd_length = static_cast(TRY(rr_ctx.stream.read_value>())); TRY(rr_ctx.stream.read_until_filled(TRY(rdata.get_bytes_for_writing(rd_length)))); rr_raw_data = move(rr_stream).take_recorded_data(); } FixedMemoryStream stream { rdata.bytes() }; CountingStream rdata_stream { MaybeOwned(stream) }; ParseContext rdata_ctx { rdata_stream, move(ctx.pointers) }; ScopeGuard guard([&] { ctx.pointers = move(rdata_ctx.pointers); }); #define PARSE_AS_RR(TYPE) \ do { \ auto rr = TRY(Records::TYPE::from_raw(rdata_ctx)); \ if (!rdata_stream.is_eof()) { \ dbgln("Extra data ({}) left in stream: {:hex-dump}", rdata.size() - rdata_stream.read_bytes(), rdata.bytes().slice(rdata_stream.read_bytes())); \ return Error::from_string_literal("Extra data in " #TYPE " record content"); \ } \ return ResourceRecord { move(name), type, class_, ttl, rr, move(rr_raw_data) }; \ } while (0) switch (type) { case ResourceType::A: PARSE_AS_RR(A); case ResourceType::AAAA: PARSE_AS_RR(AAAA); case ResourceType::TXT: PARSE_AS_RR(TXT); case ResourceType::CNAME: PARSE_AS_RR(CNAME); case ResourceType::NS: PARSE_AS_RR(NS); case ResourceType::SOA: PARSE_AS_RR(SOA); case ResourceType::MX: PARSE_AS_RR(MX); case ResourceType::PTR: PARSE_AS_RR(PTR); case ResourceType::SRV: PARSE_AS_RR(SRV); case ResourceType::DNSKEY: PARSE_AS_RR(DNSKEY); case ResourceType::CDNSKEY: PARSE_AS_RR(CDNSKEY); case ResourceType::DS: PARSE_AS_RR(DS); case ResourceType::CDS: PARSE_AS_RR(CDS); case ResourceType::RRSIG: PARSE_AS_RR(RRSIG); // case ResourceType::NSEC: // PARSE_AS_RR(NSEC); // case ResourceType::NSEC3: // PARSE_AS_RR(NSEC3); // case ResourceType::NSEC3PARAM: // PARSE_AS_RR(NSEC3PARAM); // case ResourceType::TLSA: // PARSE_AS_RR(TLSA); case ResourceType::HINFO: PARSE_AS_RR(HINFO); default: return ResourceRecord { move(name), type, class_, ttl, move(rdata), move(rr_raw_data) }; } #undef PARSE_AS_RR } ErrorOr ResourceRecord::to_raw(ByteBuffer& buffer) const { TRY(name.to_raw(buffer)); auto type_bytes = TRY(buffer.get_bytes_for_writing(2)); auto net_type = static_cast>(to_underlying(type)); memcpy(type_bytes.data(), &net_type, 2); if (type != ResourceType::OPT) { auto class_bytes = TRY(buffer.get_bytes_for_writing(2)); auto net_class = static_cast>(to_underlying(class_)); memcpy(class_bytes.data(), &net_class, 2); auto ttl_bytes = TRY(buffer.get_bytes_for_writing(4)); auto net_ttl = static_cast>(ttl); memcpy(ttl_bytes.data(), &net_ttl, 4); } ByteBuffer rdata; TRY(record.visit( [&](auto const& record) { return record.to_raw(rdata); }, [&](ByteBuffer const& raw) { return rdata.try_append(raw); })); if (type != ResourceType::OPT) { auto rdata_length_bytes = TRY(buffer.get_bytes_for_writing(2)); auto net_rdata_length = static_cast>(rdata.size()); memcpy(rdata_length_bytes.data(), &net_rdata_length, 2); } TRY(buffer.try_append(rdata)); return {}; } ErrorOr ResourceRecord::to_string() const { StringBuilder builder; record.visit( [&](auto const& record) { builder.appendff("{}", MUST(record.to_string())); }, [&](ByteBuffer const& raw) { builder.appendff("{:hex-dump}", raw.bytes()); }); return builder.to_string(); } ErrorOr Records::A::from_raw(ParseContext& ctx) { // RFC 1035, 3.4.1. A RDATA format. // | ADDRESS | a 32 bit Internet address. u32 const address = TRY(ctx.stream.read_value>()); return Records::A { IPv4Address { address } }; } ErrorOr Records::AAAA::from_raw(ParseContext& ctx) { // RFC 3596, 2.2. AAAA RDATA format. // | ADDRESS | a 128 bit Internet address. u128 const address = TRY(ctx.stream.read_value>()); return Records::AAAA { IPv6Address { bit_cast>(address) } }; } ErrorOr Records::TXT::from_raw(ParseContext& ctx) { // RFC 1035, 3.3.14. TXT RDATA format. // | TXT-DATA | a which is used for human readability. auto length = TRY(ctx.stream.read_value()); ByteBuffer content; TRY(ctx.stream.read_until_filled(TRY(content.get_bytes_for_writing(length)))); return Records::TXT { ByteString::copy(content) }; } ErrorOr Records::CNAME::from_raw(ParseContext& ctx) { // RFC 1035, 3.3.1. CNAME RDATA format. // | CNAME | a which specifies the canonical or primary name for the owner. auto name = TRY(DomainName::from_raw(ctx)); return Records::CNAME { move(name) }; } ErrorOr Records::NS::from_raw(ParseContext& ctx) { // RFC 1035, 3.3.11. NS RDATA format. // | NSDNAME | a which specifies a host which should be authoritative for the specified class and domain. auto name = TRY(DomainName::from_raw(ctx)); return Records::NS { move(name) }; } ErrorOr Records::SOA::from_raw(ParseContext& ctx) { // RFC 1035, 3.3.13. SOA RDATA format. // | MNAME | which specifies the name of the host where the master file for the zone is maintained. // | RNAME | which specifies the mailbox of the person responsible for this zone. // | SERIAL | a 32-bit unsigned integer that specifies the version number of the original copy of the zone. // | REFRESH | a 32-bit unsigned integer that specifies the time interval before the zone should be refreshed. // | RETRY | a 32-bit unsigned integer that specifies the time interval that should elapse before a failed refresh should be retried. // | EXPIRE | a 32-bit unsigned integer that specifies the time value that specifies the upper limit on the time interval that can elapse before the zone is no longer authoritative. // | MINIMUM | a 32-bit unsigned integer that specifies the minimum TTL field that should be exported with any RR from this zone. auto mname = TRY(DomainName::from_raw(ctx)); auto rname = TRY(DomainName::from_raw(ctx)); auto serial = static_cast(TRY(ctx.stream.read_value>())); auto refresh = static_cast(TRY(ctx.stream.read_value>())); auto retry = static_cast(TRY(ctx.stream.read_value>())); auto expire = static_cast(TRY(ctx.stream.read_value>())); auto minimum = static_cast(TRY(ctx.stream.read_value>())); return Records::SOA { move(mname), move(rname), serial, refresh, retry, expire, minimum }; } ErrorOr Records::MX::from_raw(ParseContext& ctx) { // RFC 1035, 3.3.9. MX RDATA format. // | PREFERENCE | a 16 bit integer which specifies the preference given to this RR among others at the same owner. // | EXCHANGE | a which specifies a host willing to act as a mail exchange for the owner name. auto preference = static_cast(TRY(ctx.stream.read_value>())); auto exchange = TRY(DomainName::from_raw(ctx)); return Records::MX { preference, move(exchange) }; } ErrorOr Records::PTR::from_raw(ParseContext& ctx) { // RFC 1035, 3.3.12. PTR RDATA format. // | PTRDNAME | a which points to some location in the domain name space. auto name = TRY(DomainName::from_raw(ctx)); return Records::PTR { move(name) }; } ErrorOr Records::SRV::from_raw(ParseContext& ctx) { // RFC 2782, 2. Service location and priority. // | PRIORITY | a 16 bit integer that specifies the priority of this target host. // | WEIGHT | a 16 bit integer that specifies a weight for this target host. // | PORT | a 16 bit integer that specifies the port on this target host. // | TARGET | a which specifies the target host. auto priority = static_cast(TRY(ctx.stream.read_value>())); auto weight = static_cast(TRY(ctx.stream.read_value>())); auto port = static_cast(TRY(ctx.stream.read_value>())); auto target = TRY(DomainName::from_raw(ctx)); return Records::SRV { priority, weight, port, move(target) }; } ErrorOr Records::DNSKEY::from_raw(ParseContext& ctx) { // RFC 4034, 2.1. The DNSKEY Resource Record. // | FLAGS | a 16-bit value that flags the key. // | PROTOCOL | an 8-bit value that specifies the protocol for this key. // | ALGORITHM| an 8-bit value that identifies the public key's cryptographic algorithm. // | PUBLICKEY| the public key material. auto flags = static_cast(TRY(ctx.stream.read_value>())); auto protocol = TRY(ctx.stream.read_value()); auto algorithm = static_cast(static_cast(TRY(ctx.stream.read_value()))); auto public_key = TRY(ctx.stream.read_until_eof()); return Records::DNSKEY { flags, protocol, algorithm, move(public_key) }; } ErrorOr Records::DS::from_raw(ParseContext& ctx) { // RFC 4034, 5.1. The DS Resource Record. // | KEYTAG | a 16-bit value that identifies the DNSKEY RR. // | ALGORITHM | an 8-bit value that identifies the DS's hash algorithm. // | DIGEST TYPE | an 8-bit value that identifies the DS's digest algorithm. // | DIGEST | Digest of the DNSKEY RDATA (Flags | Protocol | Algorithm | Pubkey). auto key_tag = static_cast(TRY(ctx.stream.read_value>())); auto algorithm = static_cast(static_cast(TRY(ctx.stream.read_value()))); auto digest_type = static_cast(static_cast(TRY(ctx.stream.read_value()))); size_t digest_size; switch (digest_type) { case DNSSEC::DigestType::SHA1: digest_size = 20; break; case DNSSEC::DigestType::SHA256: case DNSSEC::DigestType::GOST3411: digest_size = 32; break; case DNSSEC::DigestType::SHA384: digest_size = 48; break; case DNSSEC::DigestType::SHA512: digest_size = 64; break; case DNSSEC::DigestType::SHA224: digest_size = 28; break; case DNSSEC::DigestType::Unknown: default: return Error::from_string_literal("Unknown digest type in DS record"); } ByteBuffer digest; TRY(ctx.stream.read_until_filled(TRY(digest.get_bytes_for_writing(digest_size)))); return Records::DS { key_tag, algorithm, digest_type, move(digest) }; } ErrorOr Records::SIG::from_raw(ParseContext& ctx) { // RFC 4034, 2.2. The SIG Resource Record. // | TYPE-COVERED | a 16-bit value that specifies the type of the RRset that is covered by this SIG. // | ALGORITHM | an 8-bit value that specifies the algorithm used to create the signature. // | LABELS | an 8-bit value that specifies the number of labels in the original SIG RR owner name. // | ORIGINAL TTL | a 32-bit value that specifies the TTL of the covered RRset as it appears in the authoritative zone. // | SIGNATURE EXPIRATION | a 32-bit value that specifies the expiration date of the signature. // | SIGNATURE INCEPTION | a 32-bit value that specifies the inception date of the signature. // | KEY TAG | a 16-bit value that contains the key tag value of the DNSKEY RR that was used to create the signature. // | SIGNER'S NAME| a which specifies the domain name of the signer generating the SIG RR. // | SIGNATURE | a that authenticates the RRs. auto type_covered = static_cast(static_cast(TRY(ctx.stream.read_value>()))); auto algorithm = static_cast(static_cast(TRY(ctx.stream.read_value()))); auto labels = TRY(ctx.stream.read_value()); auto original_ttl = static_cast(TRY(ctx.stream.read_value>())); auto signature_expiration = static_cast(TRY(ctx.stream.read_value>())); auto signature_inception = static_cast(TRY(ctx.stream.read_value>())); auto key_tag = static_cast(TRY(ctx.stream.read_value>())); auto signer_name = TRY(DomainName::from_raw(ctx)); auto signature = TRY(ctx.stream.read_until_eof()); return Records::SIG { type_covered, algorithm, labels, original_ttl, UnixDateTime::from_seconds_since_epoch(signature_expiration), UnixDateTime::from_seconds_since_epoch(signature_inception), key_tag, move(signer_name), move(signature) }; } ErrorOr Records::SIG::to_string() const { // Single line: // SIG Type covered: , Algorithm: , Labels: , Original TTL: , Signature expiration: , Signature inception: , Key tag: , Signer's name: , Signature: StringBuilder builder; builder.append("SIG "sv); builder.appendff("Type covered: {}, ", Messages::to_string(type_covered)); builder.appendff("Algorithm: {}, ", DNSSEC::to_string(algorithm)); builder.appendff("Labels: {}, ", label_count); builder.appendff("Original TTL: {}, ", original_ttl); builder.appendff("Signature expiration: {}, ", Core::DateTime::from_timestamp(expiration.truncated_seconds_since_epoch())); builder.appendff("Signature inception: {}, ", Core::DateTime::from_timestamp(inception.truncated_seconds_since_epoch())); builder.appendff("Key tag: {}, ", key_tag); builder.appendff("Signer's name: '{}', ", signers_name.to_string()); builder.appendff("Signature: {}", TRY(encode_base64(signature))); return builder.to_string(); } ErrorOr Records::HINFO::from_raw(ParseContext& ctx) { // RFC 1035, 3.3.2. HINFO RDATA format. // | CPU | a which specifies the CPU type. // | OS | a which specifies the operating system type. auto cpu_length = TRY(ctx.stream.read_value()); ByteBuffer cpu; TRY(ctx.stream.read_until_filled(TRY(cpu.get_bytes_for_writing(cpu_length)))); auto os_length = TRY(ctx.stream.read_value()); ByteBuffer os; TRY(ctx.stream.read_until_filled(TRY(os.get_bytes_for_writing(os_length)))); return Records::HINFO { ByteString::copy(cpu), ByteString::copy(os) }; } ErrorOr Records::OPT::from_raw(ParseContext& ctx) { // RFC 6891, 6.1. The OPT pseudo-RR. // This RR does *not* use the standard RDATA format, `ctx` starts right after 'TYPE'. // | NAME | empty (root domain) // | TYPE | OPT (41) // - we are here - // | UDP SIZE | 16-bit max UDP payload size // | RCODE_AND_FLAGS | 32-bit flags and response code // | RDLENGTH | 16-bit length of the RDATA field // | RDATA | variable length, pairs of OPTION-CODE and OPTION-DATA { length(16), data(length) } auto udp_size = static_cast(TRY(ctx.stream.read_value>())); auto rcode_and_flags = static_cast(TRY(ctx.stream.read_value>())); auto rd_length = static_cast(TRY(ctx.stream.read_value>())); Vector options; while (rd_length > 0 && !ctx.stream.is_eof()) { auto option_code = static_cast(TRY(ctx.stream.read_value>())); auto option_length = static_cast(TRY(ctx.stream.read_value>())); ByteBuffer option_data; TRY(ctx.stream.read_until_filled(TRY(option_data.get_bytes_for_writing(option_length)))); rd_length -= 4 + option_length; options.append(OPT::Option { option_code, move(option_data) }); } if (rd_length != 0) return Error::from_string_literal("Invalid OPT record"); return Records::OPT { udp_size, rcode_and_flags, move(options) }; } ErrorOr Records::OPT::to_raw(ByteBuffer& buffer) const { auto udp_size_bytes = TRY(buffer.get_bytes_for_writing(sizeof(udp_payload_size))); auto net_udp_size = static_cast>(udp_payload_size); memcpy(udp_size_bytes.data(), &net_udp_size, 2); auto rcode_and_flags_bytes = TRY(buffer.get_bytes_for_writing(sizeof(extended_rcode_and_flags))); auto net_rcode_and_flags = static_cast>(extended_rcode_and_flags); memcpy(rcode_and_flags_bytes.data(), &net_rcode_and_flags, 4); auto rd_length_bytes = TRY(buffer.get_bytes_for_writing(2)); u16 rd_length = 0; for (auto const& option : options) { rd_length += 4 + option.data.size(); } auto net_rd_length = static_cast>(rd_length); memcpy(rd_length_bytes.data(), &net_rd_length, 2); for (auto& option : options) { auto option_code_bytes = TRY(buffer.get_bytes_for_writing(sizeof(option.code))); auto net_option_code = static_cast>(option.code); memcpy(option_code_bytes.data(), &net_option_code, 2); auto option_length_bytes = TRY(buffer.get_bytes_for_writing(2)); auto net_option_length = static_cast>(option.data.size()); memcpy(option_length_bytes.data(), &net_option_length, 2); TRY(buffer.try_append(option.data)); } return {}; } }