mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 00:50:22 +00:00
1178 lines
42 KiB
C++
1178 lines
42 KiB
C++
|
/*
|
||
|
* 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 {};
|
||
|
}
|
||
|
|
||
|
}
|