1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177 |
- /*
- * Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <AK/CountingStream.h>
- #include <AK/MemoryStream.h>
- #include <AK/Stream.h>
- #include <AK/UFixedBigInt.h>
- #include <LibCore/DateTime.h>
- #include <LibDNS/Message.h>
- 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> Message::from_raw(AK::Stream& stream)
- {
- CountingStream counting_stream { MaybeOwned(stream) };
- auto context = ParseContext { counting_stream, make<RedBlackTree<u16, DomainName>>() };
- return from_raw(context);
- }
- ErrorOr<Message> 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<size_t> 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<String> 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> 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<ResourceType>(static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>())));
- auto class_ = static_cast<Class>(static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>())));
- return Question { move(name), type, class_ };
- }
- ErrorOr<void> 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<NetworkOrdered<u16>>(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<NetworkOrdered<u16>>(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<ResourceType> 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> 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<u8>());
- 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<u16>(length & ~OffsetMarkerMask) << 8 | TRY(ctx.stream.read_value<u8>());
- 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<void> 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<u8>(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<Bytes> 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<void> 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<size_t> 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<Stream> m_stream;
- ByteBuffer m_recorded_data;
- };
- ErrorOr<ResourceRecord> 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<Stream>(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<ResourceType>(static_cast<u16>(TRY(rr_ctx.stream.read_value<NetworkOrdered<u16>>())));
- 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<Class>(static_cast<u16>(TRY(rr_ctx.stream.read_value<NetworkOrdered<u16>>())));
- ttl = static_cast<u32>(TRY(rr_ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto rd_length = static_cast<u16>(TRY(rr_ctx.stream.read_value<NetworkOrdered<u16>>()));
- 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>(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<void> 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<NetworkOrdered<u16>>(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<NetworkOrdered<u16>>(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<NetworkOrdered<u32>>(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<NetworkOrdered<u16>>(rdata.size());
- memcpy(rdata_length_bytes.data(), &net_rdata_length, 2);
- }
- TRY(buffer.try_append(rdata));
- return {};
- }
- ErrorOr<String> 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> 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<LittleEndian<u32>>());
- return Records::A { IPv4Address { address } };
- }
- ErrorOr<Records::AAAA> 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<LittleEndian<u128>>());
- return Records::AAAA { IPv6Address { bit_cast<Array<u8, 16>>(address) } };
- }
- ErrorOr<Records::TXT> Records::TXT::from_raw(ParseContext& ctx)
- {
- // RFC 1035, 3.3.14. TXT RDATA format.
- // | TXT-DATA | a <character-string> which is used for human readability.
- auto length = TRY(ctx.stream.read_value<u8>());
- ByteBuffer content;
- TRY(ctx.stream.read_until_filled(TRY(content.get_bytes_for_writing(length))));
- return Records::TXT { ByteString::copy(content) };
- }
- ErrorOr<Records::CNAME> Records::CNAME::from_raw(ParseContext& ctx)
- {
- // RFC 1035, 3.3.1. CNAME RDATA format.
- // | CNAME | a <domain-name> 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> Records::NS::from_raw(ParseContext& ctx)
- {
- // RFC 1035, 3.3.11. NS RDATA format.
- // | NSDNAME | a <domain-name> 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> Records::SOA::from_raw(ParseContext& ctx)
- {
- // RFC 1035, 3.3.13. SOA RDATA format.
- // | MNAME | <domain-name> which specifies the name of the host where the master file for the zone is maintained.
- // | RNAME | <domain-name> 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<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto refresh = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto retry = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto expire = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto minimum = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- return Records::SOA { move(mname), move(rname), serial, refresh, retry, expire, minimum };
- }
- ErrorOr<Records::MX> 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 <domain-name> which specifies a host willing to act as a mail exchange for the owner name.
- auto preference = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto exchange = TRY(DomainName::from_raw(ctx));
- return Records::MX { preference, move(exchange) };
- }
- ErrorOr<Records::PTR> Records::PTR::from_raw(ParseContext& ctx)
- {
- // RFC 1035, 3.3.12. PTR RDATA format.
- // | PTRDNAME | a <domain-name> 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> 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 <domain-name> which specifies the target host.
- auto priority = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto weight = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto port = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto target = TRY(DomainName::from_raw(ctx));
- return Records::SRV { priority, weight, port, move(target) };
- }
- ErrorOr<Records::DNSKEY> 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<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto protocol = TRY(ctx.stream.read_value<u8>());
- auto algorithm = static_cast<DNSSEC::Algorithm>(static_cast<u8>(TRY(ctx.stream.read_value<u8>())));
- auto public_key = TRY(ctx.stream.read_until_eof());
- return Records::DNSKEY { flags, protocol, algorithm, move(public_key) };
- }
- ErrorOr<Records::DS> 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<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto algorithm = static_cast<DNSSEC::Algorithm>(static_cast<u8>(TRY(ctx.stream.read_value<u8>())));
- auto digest_type = static_cast<DNSSEC::DigestType>(static_cast<u8>(TRY(ctx.stream.read_value<u8>())));
- 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> 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 <domain-name> which specifies the domain name of the signer generating the SIG RR.
- // | SIGNATURE | a <signature> that authenticates the RRs.
- auto type_covered = static_cast<ResourceType>(static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>())));
- auto algorithm = static_cast<DNSSEC::Algorithm>(static_cast<u8>(TRY(ctx.stream.read_value<u8>())));
- auto labels = TRY(ctx.stream.read_value<u8>());
- auto original_ttl = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto signature_expiration = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto signature_inception = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto key_tag = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- 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<String> Records::SIG::to_string() const
- {
- // Single line:
- // SIG Type covered: <type>, Algorithm: <algorithm>, Labels: <labels>, Original TTL: <ttl>, Signature expiration: <expiration>, Signature inception: <inception>, Key tag: <key tag>, Signer's name: <signer>, Signature: <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> Records::HINFO::from_raw(ParseContext& ctx)
- {
- // RFC 1035, 3.3.2. HINFO RDATA format.
- // | CPU | a <character-string> which specifies the CPU type.
- // | OS | a <character-string> which specifies the operating system type.
- auto cpu_length = TRY(ctx.stream.read_value<u8>());
- ByteBuffer cpu;
- TRY(ctx.stream.read_until_filled(TRY(cpu.get_bytes_for_writing(cpu_length))));
- auto os_length = TRY(ctx.stream.read_value<u8>());
- 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> 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<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto rcode_and_flags = static_cast<u32>(TRY(ctx.stream.read_value<NetworkOrdered<u32>>()));
- auto rd_length = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- Vector<OPT::Option> options;
- while (rd_length > 0 && !ctx.stream.is_eof()) {
- auto option_code = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- auto option_length = static_cast<u16>(TRY(ctx.stream.read_value<NetworkOrdered<u16>>()));
- 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<void> 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<NetworkOrdered<u16>>(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<NetworkOrdered<u32>>(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<NetworkOrdered<u16>>(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<NetworkOrdered<u16>>(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<NetworkOrdered<u16>>(option.data.size());
- memcpy(option_length_bytes.data(), &net_option_length, 2);
- TRY(buffer.try_append(option.data));
- }
- return {};
- }
- }
|