mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
LibDNS+LibWeb+Ladybird+RequestServer: Let there be DNS over TLS
This commit adds our own DNS resolver, with the aim of implementing DoT (and eventually DoH, maybe even DNSSEC etc.)
This commit is contained in:
parent
7f72c28e78
commit
7e20f4726f
Notes:
github-actions[bot]
2024-11-20 20:44:27 +00:00
Author: https://github.com/alimpfard Commit: https://github.com/LadybirdBrowser/ladybird/commit/7e20f4726fe Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2111 Reviewed-by: https://github.com/ADKaster ✅
13 changed files with 2586 additions and 2 deletions
6
Libraries/LibDNS/CMakeLists.txt
Normal file
6
Libraries/LibDNS/CMakeLists.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
set(SOURCES
|
||||
Message.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibDNS dns)
|
||||
target_link_libraries(LibDNS PRIVATE LibCore)
|
1177
Libraries/LibDNS/Message.cpp
Normal file
1177
Libraries/LibDNS/Message.cpp
Normal file
File diff suppressed because it is too large
Load diff
704
Libraries/LibDNS/Message.h
Normal file
704
Libraries/LibDNS/Message.h
Normal file
|
@ -0,0 +1,704 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Base64.h>
|
||||
#include <AK/MaybeOwned.h>
|
||||
#include <AK/RedBlackTree.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/Promise.h>
|
||||
#include <LibCore/SocketAddress.h>
|
||||
#include <LibURL/URL.h>
|
||||
|
||||
namespace DNS {
|
||||
namespace Messages {
|
||||
|
||||
struct DomainName;
|
||||
struct ParseContext {
|
||||
CountingStream& stream;
|
||||
NonnullOwnPtr<RedBlackTree<u16, DomainName>> pointers;
|
||||
};
|
||||
|
||||
enum class OpCode : u8;
|
||||
|
||||
struct Options {
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ID |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// |QR| Opcode |AA|TC|RD|RA| Z |AD|CD| RCODE |
|
||||
constexpr inline static u16 QRMask = 0b1000000000000000;
|
||||
constexpr inline static u16 OpCodeMask = 0b0111100000000000;
|
||||
constexpr inline static u16 AuthoritativeAnswerMask = 0b0000010000000000;
|
||||
constexpr inline static u16 TruncatedMask = 0b0000001000000000;
|
||||
constexpr inline static u16 RecursionDesiredMask = 0b0000000100000000;
|
||||
constexpr inline static u16 RecursionAvailableMask = 0b0000000010000000;
|
||||
constexpr inline static u16 AuthenticatedDataMask = 0b0000000000100000;
|
||||
constexpr inline static u16 CheckingDisabledMask = 0b0000000000010000;
|
||||
constexpr inline static u16 ResponseCodeMask = 0b0000000000001111;
|
||||
|
||||
enum class ResponseCode : u16 {
|
||||
NoError = 0,
|
||||
FormatError = 1,
|
||||
ServerFailure = 2,
|
||||
NameError = 3,
|
||||
NotImplemented = 4,
|
||||
Refused = 5,
|
||||
};
|
||||
|
||||
void set_is_question(bool value) { raw = (raw & ~QRMask) | (value ? QRMask : 0); }
|
||||
void set_is_authoritative_answer(bool value) { raw = (raw & ~AuthoritativeAnswerMask) | (value ? AuthoritativeAnswerMask : 0); }
|
||||
void set_is_truncated(bool value) { raw = (raw & ~TruncatedMask) | (value ? TruncatedMask : 0); }
|
||||
void set_recursion_desired(bool value) { raw = (raw & ~RecursionDesiredMask) | (value ? RecursionDesiredMask : 0); }
|
||||
void set_recursion_available(bool value) { raw = (raw & ~RecursionAvailableMask) | (value ? RecursionAvailableMask : 0); }
|
||||
void set_response_code(ResponseCode code) { raw = (raw & ~ResponseCodeMask) | static_cast<u16>(code); }
|
||||
void set_checking_disabled(bool value) { raw = (raw & ~CheckingDisabledMask) | (value ? CheckingDisabledMask : 0); }
|
||||
void set_authenticated_data(bool value) { raw = (raw & ~AuthenticatedDataMask) | (value ? AuthenticatedDataMask : 0); }
|
||||
void set_op_code(OpCode code) { raw = (raw & ~OpCodeMask) | (static_cast<u16>(code) << 11); }
|
||||
|
||||
bool is_question() const { return (raw & QRMask) == 0; }
|
||||
bool is_authoritative_answer() const { return (raw & AuthoritativeAnswerMask) != 0; }
|
||||
bool is_truncated() const { return (raw & TruncatedMask) != 0; }
|
||||
bool recursion_desired() const { return (raw & RecursionDesiredMask) != 0; }
|
||||
bool recursion_available() const { return (raw & RecursionAvailableMask) != 0; }
|
||||
bool checking_disabled() const { return (raw & CheckingDisabledMask) != 0; }
|
||||
bool authenticated_data() const { return (raw & AuthenticatedDataMask) != 0; }
|
||||
ResponseCode response_code() const { return static_cast<ResponseCode>(raw & ResponseCodeMask); }
|
||||
OpCode op_code() const { return static_cast<OpCode>((raw & OpCodeMask) >> 11); }
|
||||
|
||||
String to_string() const;
|
||||
|
||||
NetworkOrdered<u16> raw { 0 };
|
||||
};
|
||||
StringView to_string(Options::ResponseCode);
|
||||
|
||||
struct Header {
|
||||
NetworkOrdered<u16> id;
|
||||
Options options;
|
||||
NetworkOrdered<u16> question_count;
|
||||
NetworkOrdered<u16> answer_count;
|
||||
NetworkOrdered<u16> authority_count;
|
||||
NetworkOrdered<u16> additional_count;
|
||||
};
|
||||
|
||||
struct DomainName {
|
||||
Vector<ByteString> labels;
|
||||
|
||||
static DomainName from_string(StringView);
|
||||
static ErrorOr<DomainName> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const;
|
||||
String to_string() const;
|
||||
};
|
||||
|
||||
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4.
|
||||
enum class ResourceType : u16 {
|
||||
Reserved = 0, // [RFC6895]
|
||||
A = 1, // a host address [RFC1035]
|
||||
NS = 2, // an authoritative name server [RFC1035]
|
||||
MD = 3, // a mail destination (OBSOLETE - use MX) [RFC1035]
|
||||
MF = 4, // a mail forwarder (OBSOLETE - use MX) [RFC1035]
|
||||
CNAME = 5, // the canonical name for an alias [RFC1035]
|
||||
SOA = 6, // marks the start of a zone of authority [RFC1035]
|
||||
MB = 7, // a mailbox domain name (EXPERIMENTAL) [RFC1035]
|
||||
MG = 8, // a mail group member (EXPERIMENTAL) [RFC1035]
|
||||
MR = 9, // a mail rename domain name (EXPERIMENTAL) [RFC1035]
|
||||
NULL_ = 10, // a null RR (EXPERIMENTAL) [RFC1035]
|
||||
WKS = 11, // a well known service description [RFC1035]
|
||||
PTR = 12, // a domain name pointer [RFC1035]
|
||||
HINFO = 13, // host information [RFC1035]
|
||||
MINFO = 14, // mailbox or mail list information [RFC1035]
|
||||
MX = 15, // mail exchange [RFC1035]
|
||||
TXT = 16, // text strings [RFC1035]
|
||||
RP = 17, // for Responsible Person [RFC1183]
|
||||
AFSDB = 18, // for AFS Data Base location [RFC1183][RFC5864]
|
||||
X25 = 19, // for X.25 PSDN address [RFC1183]
|
||||
ISDN = 20, // for ISDN address [RFC1183]
|
||||
RT = 21, // for Route Through [RFC1183]
|
||||
NSAP = 22, // for NSAP address, NSAP style A record (DEPRECATED) [RFC1706][Moving TPC.INT and NSAP.INT infrastructure domains to historic]
|
||||
NSAP_PTR = 23, // for domain name pointer, NSAP style (DEPRECATED) [RFC1706][Moving TPC.INT and NSAP.INT infrastructure domains to historic]
|
||||
SIG = 24, // for security signature [RFC2536][RFC2931][RFC3110][RFC4034]
|
||||
KEY = 25, // for security key [RFC2536][RFC2539][RFC3110][RFC4034]
|
||||
PX = 26, // X.400 mail mapping information [RFC2163]
|
||||
GPOS = 27, // Geographical Position [RFC1712]
|
||||
AAAA = 28, // IP6 Address [RFC3596]
|
||||
LOC = 29, // Location Information [RFC1876]
|
||||
NXT = 30, // Next Domain (OBSOLETE) [RFC2535][RFC3755]
|
||||
EID = 31, // Endpoint Identifier [Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
|
||||
NIMLOC = 32, // Nimrod Locator [1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
|
||||
SRV = 33, // Server Selection [1][RFC2782]
|
||||
ATMA = 34, // ATM Address "[ ATM Forum Technical Committee, "ATM Name System, V2.0", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.]"
|
||||
NAPTR = 35, // Naming Authority Pointer [RFC3403]
|
||||
KX = 36, // Key Exchanger [RFC2230]
|
||||
CERT = 37, // CERT [RFC4398]
|
||||
A6 = 38, // A6 (OBSOLETE - use AAAA) [RFC2874][RFC3226][RFC6563]
|
||||
DNAME = 39, // DNAME [RFC6672]
|
||||
SINK = 40, // SINK [Donald_E_Eastlake][draft-eastlake-kitchen-sink]
|
||||
OPT = 41, // OPT [RFC3225][RFC6891]
|
||||
APL = 42, // APL [RFC3123]
|
||||
DS = 43, // Delegation Signer [RFC4034]
|
||||
SSHFP = 44, // SSH Key Fingerprint [RFC4255]
|
||||
IPSECKEY = 45, // IPSECKEY [RFC4025]
|
||||
RRSIG = 46, // RRSIG [RFC4034]
|
||||
NSEC = 47, // NSEC [RFC4034][RFC9077]
|
||||
DNSKEY = 48, // DNSKEY [RFC4034]
|
||||
DHCID = 49, // DHCID [RFC4701]
|
||||
NSEC3 = 50, // NSEC3 [RFC5155][RFC9077]
|
||||
NSEC3PARAM = 51, // NSEC3PARAM [RFC5155]
|
||||
TLSA = 52, // TLSA [RFC6698]
|
||||
SMIMEA = 53, // S/MIME cert association [RFC8162]
|
||||
HIP = 55, // Host Identity Protocol [RFC8005]
|
||||
NINFO = 56, // NINFO [Jim_Reid]
|
||||
RKEY = 57, // RKEY [Jim_Reid]
|
||||
TALINK = 58, // Trust Anchor LINK [Wouter_Wijngaards]
|
||||
CDS = 59, // Child DS [RFC7344]
|
||||
CDNSKEY = 60, // DNSKEY(s) the Child wants reflected in DS [RFC7344]
|
||||
OPENPGPKEY = 61, // OpenPGP Key [RFC7929]
|
||||
CSYNC = 62, // Child-To-Parent Synchronization [RFC7477]
|
||||
ZONEMD = 63, // Message Digest Over Zone Data [RFC8976]
|
||||
SVCB = 64, // General-purpose service binding [RFC9460]
|
||||
HTTPS = 65, // SVCB-compatible type for use with HTTP [RFC9460]
|
||||
SPF = 99, // [RFC7208]
|
||||
UINFO = 100, // [IANA-Reserved]
|
||||
UID = 101, // [IANA-Reserved]
|
||||
GID = 102, // [IANA-Reserved]
|
||||
UNSPEC = 103, // [IANA-Reserved]
|
||||
NID = 104, // [RFC6742]
|
||||
L32 = 105, // [RFC6742]
|
||||
L64 = 106, // [RFC6742]
|
||||
LP = 107, // [RFC6742]
|
||||
EUI48 = 108, // an EUI-48 address [RFC7043]
|
||||
EUI64 = 109, // an EUI-64 address [RFC7043]
|
||||
NXNAME = 128, // NXDOMAIN indicator for Compact Denial of Existence [draft-ietf-dnsop-compact-denial-of-existence-04]
|
||||
TKEY = 249, // Transaction Key [RFC2930]
|
||||
TSIG = 250, // Transaction Signature [RFC8945]
|
||||
IXFR = 251, // incremental transfer [RFC1995]
|
||||
AXFR = 252, // transfer of an entire zone [RFC1035][RFC5936]
|
||||
MAILB = 253, // mailbox-related RRs (MB, MG or MR) [RFC1035]
|
||||
MAILA = 254, // mail agent RRs (OBSOLETE - see MX) [RFC1035]
|
||||
ANY = 255, // A request for some or all records the server has available [RFC1035][RFC6895][RFC8482]
|
||||
URI = 256, // URI [RFC7553]
|
||||
CAA = 257, // Certification Authority Restriction [RFC8659]
|
||||
AVC = 258, // Application Visibility and Control [Wolfgang_Riedel]
|
||||
DOA = 259, // Digital Object Architecture [draft-durand-doa-over-dns]
|
||||
AMTRELAY = 260, // Automatic Multicast Tunneling Relay [RFC8777]
|
||||
RESINFO = 261, // Resolver Information as Key/Value Pairs [RFC9606]
|
||||
WALLET = 262, // Public wallet address [Paul_Hoffman]
|
||||
CLA = 263, // BP Convergence Layer Adapter [draft-johnson-dns-ipn-cla-07]
|
||||
IPN = 264, // BP Node Number [draft-johnson-dns-ipn-cla-07]
|
||||
TA = 32768, // DNSSEC Trust Authorities "[Sam_Weiler][Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.]"
|
||||
DLV = 32769, // DNSSEC Lookaside Validation (OBSOLETE) [RFC8749][RFC4431]
|
||||
};
|
||||
StringView to_string(ResourceType);
|
||||
Optional<ResourceType> resource_type_from_string(StringView);
|
||||
|
||||
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2.
|
||||
enum class Class : u16 {
|
||||
IN = 1, // the Internet [RFC1035]
|
||||
CH = 3, // the CHAOS class [Moon1981]
|
||||
HS = 4, // Hesiod [Dyer1987]
|
||||
};
|
||||
StringView to_string(Class);
|
||||
|
||||
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-3.
|
||||
enum class OpCode : u8 {
|
||||
Query = 0, // a standard query (QUERY)
|
||||
IQuery = 1, // an inverse query (IQUERY)
|
||||
Status = 2, // a server status request (STATUS)
|
||||
Notify = 4, // NOTIFY
|
||||
Update = 5, // dynamic update (RFC 2136)
|
||||
DSO = 6, // DNS Stateful Operations (DSO) [RFC8490]
|
||||
Reserved = 7, // [RFC6895]
|
||||
ReservedMask = 15 // [RFC6895]
|
||||
};
|
||||
StringView to_string(OpCode);
|
||||
|
||||
namespace TLSA {
|
||||
|
||||
// Listings from IANA https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml.
|
||||
enum class CertUsage : u8 {
|
||||
CAConstraint = 0,
|
||||
ServiceCertificateConstraint = 1,
|
||||
TrustAnchorAssertion = 2,
|
||||
DomainIssuedCertificate = 3,
|
||||
Private = 255
|
||||
};
|
||||
enum class Selector : u8 {
|
||||
FullCertificate = 0,
|
||||
SubjectPublicKeyInfo = 1,
|
||||
Private = 255
|
||||
};
|
||||
enum class MatchingType : u8 {
|
||||
Full = 0,
|
||||
SHA256 = 1,
|
||||
SHA512 = 2,
|
||||
Private = 255
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace DNSSEC {
|
||||
|
||||
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
|
||||
enum class Algorithm : u8 {
|
||||
RSAMD5 = 1, // RSA/MD5 [RFC4034][RFC3110]
|
||||
DSA = 3, // DSA/SHA-1 [RFC3755][RFC2536]
|
||||
RSASHA1 = 5, // RSA/SHA-1 [RFC3110]
|
||||
RSASHA1NSEC3SHA1 = 7, // [RFC5155]
|
||||
RSASHA256 = 8, // RSA/SHA-256 [RFC5702]
|
||||
RSASHA512 = 10, // RSA/SHA-512 [RFC5702]
|
||||
ECDSAP256SHA256 = 13, // ECDSA Curve P-256 with SHA-256 [RFC6605]
|
||||
ECDSAP384SHA384 = 14, // ECDSA Curve P-384 with SHA-384 [RFC6605]
|
||||
ED25519 = 15, // Ed25519 [RFC8080]
|
||||
Unknown = 255 // Reserved for Private Use
|
||||
};
|
||||
|
||||
static inline StringView to_string(Algorithm algorithm)
|
||||
{
|
||||
switch (algorithm) {
|
||||
case Algorithm::RSAMD5:
|
||||
return "RSAMD5"sv;
|
||||
case Algorithm::DSA:
|
||||
return "DSA"sv;
|
||||
case Algorithm::RSASHA1:
|
||||
return "RSASHA1"sv;
|
||||
case Algorithm::RSASHA1NSEC3SHA1:
|
||||
return "RSASHA1NSEC3SHA1"sv;
|
||||
case Algorithm::RSASHA256:
|
||||
return "RSASHA256"sv;
|
||||
case Algorithm::RSASHA512:
|
||||
return "RSASHA512"sv;
|
||||
case Algorithm::ECDSAP256SHA256:
|
||||
return "ECDSAP256SHA256"sv;
|
||||
case Algorithm::ECDSAP384SHA384:
|
||||
return "ECDSAP384SHA384"sv;
|
||||
case Algorithm::ED25519:
|
||||
return "ED25519"sv;
|
||||
case Algorithm::Unknown:
|
||||
return "Unknown"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
|
||||
enum class DigestType : u8 {
|
||||
SHA1 = 1, // SHA-1 [RFC3658]
|
||||
SHA256 = 2, // SHA-256 [RFC4509]
|
||||
GOST3411 = 3, // GOST R 34.11-94 [RFC5933]
|
||||
SHA384 = 4, // SHA-384 [RFC6605]
|
||||
SHA512 = 5, // SHA-512 [RFC6605]
|
||||
SHA224 = 6, // SHA-224 [RFC6605]
|
||||
Unknown = 255 // Reserved for Private Use
|
||||
};
|
||||
|
||||
static inline StringView to_string(DigestType digest_type)
|
||||
{
|
||||
switch (digest_type) {
|
||||
case DigestType::SHA1:
|
||||
return "SHA1"sv;
|
||||
case DigestType::SHA256:
|
||||
return "SHA256"sv;
|
||||
case DigestType::GOST3411:
|
||||
return "GOST3411"sv;
|
||||
case DigestType::SHA384:
|
||||
return "SHA384"sv;
|
||||
case DigestType::SHA512:
|
||||
return "SHA512"sv;
|
||||
case DigestType::SHA224:
|
||||
return "SHA224"sv;
|
||||
case DigestType::Unknown:
|
||||
return "Unknown"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
|
||||
enum class NSEC3HashAlgorithm : u8 {
|
||||
SHA1 = 1, // [RFC5155]
|
||||
SHA256 = 2, // [RFC6605]
|
||||
GOST3411 = 3, // [RFC5933]
|
||||
SHA384 = 4, // [RFC6605]
|
||||
SHA512 = 5, // [RFC6605]
|
||||
SHA224 = 6, // [RFC6605]
|
||||
Unknown = 255 // Reserved for Private Use
|
||||
};
|
||||
|
||||
static inline StringView to_string(NSEC3HashAlgorithm hash_algorithm)
|
||||
{
|
||||
switch (hash_algorithm) {
|
||||
case NSEC3HashAlgorithm::SHA1:
|
||||
return "SHA1"sv;
|
||||
case NSEC3HashAlgorithm::SHA256:
|
||||
return "SHA256"sv;
|
||||
case NSEC3HashAlgorithm::GOST3411:
|
||||
return "GOST3411"sv;
|
||||
case NSEC3HashAlgorithm::SHA384:
|
||||
return "SHA384"sv;
|
||||
case NSEC3HashAlgorithm::SHA512:
|
||||
return "SHA512"sv;
|
||||
case NSEC3HashAlgorithm::SHA224:
|
||||
return "SHA224"sv;
|
||||
case NSEC3HashAlgorithm::Unknown:
|
||||
return "Unknown"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Question {
|
||||
DomainName name;
|
||||
ResourceType type;
|
||||
Class class_;
|
||||
|
||||
static ErrorOr<Question> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const;
|
||||
};
|
||||
|
||||
namespace Records {
|
||||
|
||||
struct A {
|
||||
IPv4Address address;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::A;
|
||||
static ErrorOr<A> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return address.to_string(); }
|
||||
};
|
||||
struct AAAA {
|
||||
IPv6Address address;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::AAAA;
|
||||
static ErrorOr<AAAA> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return address.to_string(); }
|
||||
};
|
||||
struct TXT {
|
||||
ByteString content;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::TXT;
|
||||
static ErrorOr<TXT> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return String::formatted("Text: '{}'", StringView { content }); }
|
||||
};
|
||||
struct CNAME {
|
||||
DomainName names;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::CNAME;
|
||||
static ErrorOr<CNAME> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return names.to_string(); }
|
||||
};
|
||||
struct NS {
|
||||
DomainName name;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::NS;
|
||||
static ErrorOr<NS> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return name.to_string(); }
|
||||
};
|
||||
struct SOA {
|
||||
DomainName mname;
|
||||
DomainName rname;
|
||||
u32 serial;
|
||||
u32 refresh;
|
||||
u32 retry;
|
||||
u32 expire;
|
||||
u32 minimum;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::SOA;
|
||||
static ErrorOr<SOA> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const
|
||||
{
|
||||
return String::formatted("SOA MName: '{}', RName: '{}', Serial: {}, Refresh: {}, Retry: {}, Expire: {}, Minimum: {}", mname.to_string(), rname.to_string(), serial, refresh, retry, expire, minimum);
|
||||
}
|
||||
};
|
||||
struct MX {
|
||||
u16 preference;
|
||||
DomainName exchange;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::MX;
|
||||
static ErrorOr<MX> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return String::formatted("MX Preference: {}, Exchange: '{}'", preference, exchange.to_string()); }
|
||||
};
|
||||
struct PTR {
|
||||
DomainName name;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::PTR;
|
||||
static ErrorOr<PTR> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return name.to_string(); }
|
||||
};
|
||||
struct SRV {
|
||||
u16 priority;
|
||||
u16 weight;
|
||||
u16 port;
|
||||
DomainName target;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::SRV;
|
||||
static ErrorOr<SRV> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return String::formatted("SRV Priority: {}, Weight: {}, Port: {}, Target: '{}'", priority, weight, port, target.to_string()); }
|
||||
};
|
||||
struct DNSKEY {
|
||||
u16 flags;
|
||||
u8 protocol;
|
||||
DNSSEC::Algorithm algorithm;
|
||||
ByteBuffer public_key;
|
||||
|
||||
constexpr static inline u16 FlagSecureEntryPoint = 0b1000000000000000;
|
||||
constexpr static inline u16 FlagZoneKey = 0b0100000000000000;
|
||||
constexpr static inline u16 FlagRevoked = 0b0010000000000000;
|
||||
|
||||
constexpr bool is_secure_entry_point() const { return flags & FlagSecureEntryPoint; }
|
||||
constexpr bool is_zone_key() const { return flags & FlagZoneKey; }
|
||||
constexpr bool is_revoked() const { return flags & FlagRevoked; }
|
||||
constexpr bool is_key_signing_key() const { return is_secure_entry_point() && is_zone_key() && !is_revoked(); }
|
||||
|
||||
static constexpr ResourceType type = ResourceType::DNSKEY;
|
||||
static ErrorOr<DNSKEY> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const
|
||||
{
|
||||
return String::formatted("DNSKEY Flags: {}{}{}{}({}), Protocol: {}, Algorithm: {}, Public Key: {}",
|
||||
is_secure_entry_point() ? "sep "sv : ""sv,
|
||||
is_zone_key() ? "zone "sv : ""sv,
|
||||
is_revoked() ? "revoked "sv : ""sv,
|
||||
is_key_signing_key() ? "ksk "sv : ""sv,
|
||||
flags,
|
||||
protocol,
|
||||
DNSSEC::to_string(algorithm),
|
||||
TRY(encode_base64(public_key)));
|
||||
}
|
||||
};
|
||||
struct CDNSKEY : public DNSKEY {
|
||||
template<typename... Ts>
|
||||
CDNSKEY(Ts&&... args)
|
||||
: DNSKEY(forward<Ts>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr ResourceType type = ResourceType::CDNSKEY;
|
||||
static ErrorOr<CDNSKEY> from_raw(ParseContext& raw) { return DNSKEY::from_raw(raw); }
|
||||
};
|
||||
struct DS {
|
||||
u16 key_tag;
|
||||
DNSSEC::Algorithm algorithm;
|
||||
DNSSEC::DigestType digest_type;
|
||||
ByteBuffer digest;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::DS;
|
||||
static ErrorOr<DS> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return "DS"_string; }
|
||||
};
|
||||
struct CDS : public DS {
|
||||
template<typename... Ts>
|
||||
CDS(Ts&&... args)
|
||||
: DS(forward<Ts>(args)...)
|
||||
{
|
||||
}
|
||||
static constexpr ResourceType type = ResourceType::CDS;
|
||||
static ErrorOr<CDS> from_raw(ParseContext& raw) { return DS::from_raw(raw); }
|
||||
};
|
||||
struct SIG {
|
||||
ResourceType type_covered;
|
||||
DNSSEC::Algorithm algorithm;
|
||||
u8 label_count;
|
||||
u32 original_ttl;
|
||||
UnixDateTime expiration;
|
||||
UnixDateTime inception;
|
||||
u16 key_tag;
|
||||
DomainName signers_name;
|
||||
ByteBuffer signature;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::SIG;
|
||||
static ErrorOr<SIG> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const;
|
||||
};
|
||||
struct RRSIG : public SIG {
|
||||
template<typename... Ts>
|
||||
RRSIG(Ts&&... args)
|
||||
: SIG(forward<Ts>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr ResourceType type = ResourceType::RRSIG;
|
||||
static ErrorOr<RRSIG> from_raw(ParseContext& raw) { return SIG::from_raw(raw); }
|
||||
};
|
||||
struct NSEC {
|
||||
DomainName next_domain_name;
|
||||
Vector<ResourceType> types;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::NSEC;
|
||||
static ErrorOr<NSEC> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return "NSEC"_string; }
|
||||
};
|
||||
struct NSEC3 {
|
||||
DNSSEC::NSEC3HashAlgorithm hash_algorithm;
|
||||
u8 flags;
|
||||
u16 iterations;
|
||||
ByteBuffer salt;
|
||||
DomainName next_hashed_owner_name;
|
||||
Vector<ResourceType> types;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::NSEC3;
|
||||
static ErrorOr<NSEC3> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return "NSEC3"_string; }
|
||||
};
|
||||
struct NSEC3PARAM {
|
||||
DNSSEC::NSEC3HashAlgorithm hash_algorithm;
|
||||
u8 flags;
|
||||
u16 iterations;
|
||||
ByteBuffer salt;
|
||||
|
||||
constexpr static inline u8 FlagOptOut = 0b10000000;
|
||||
|
||||
constexpr bool is_opt_out() const { return flags & FlagOptOut; }
|
||||
|
||||
static constexpr ResourceType type = ResourceType::NSEC3PARAM;
|
||||
static ErrorOr<NSEC3PARAM> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return "NSEC3PARAM"_string; }
|
||||
};
|
||||
struct TLSA {
|
||||
Messages::TLSA::CertUsage cert_usage;
|
||||
Messages::TLSA::Selector selector;
|
||||
Messages::TLSA::MatchingType matching_type;
|
||||
ByteBuffer certificate_association_data;
|
||||
|
||||
static ErrorOr<TLSA> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return "TLSA"_string; }
|
||||
};
|
||||
struct HINFO {
|
||||
ByteString cpu;
|
||||
ByteString os;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::HINFO;
|
||||
static ErrorOr<HINFO> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
|
||||
ErrorOr<String> to_string() const { return String::formatted("HINFO CPU: '{}', OS: '{}'", StringView { cpu }, StringView { os }); }
|
||||
};
|
||||
struct OPT {
|
||||
struct Option {
|
||||
u16 code;
|
||||
ByteBuffer data;
|
||||
};
|
||||
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | UDP Payload Size |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | Extended RCode | VER | ZZ |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// |DO| Z |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | OPT-LEN / OPT-DATA...
|
||||
|
||||
NetworkOrdered<u16> udp_payload_size { 0 };
|
||||
NetworkOrdered<u32> extended_rcode_and_flags { 0 };
|
||||
Vector<Option> options;
|
||||
static constexpr u32 MaskExtendedRCode = 0b11111111000000000000000000000000;
|
||||
static constexpr u32 MaskVersion = 0b00000000111100000000000000000000;
|
||||
static constexpr u32 MaskDO = 0b00000000000000001000000000000000;
|
||||
|
||||
static constexpr ResourceType type = ResourceType::OPT;
|
||||
|
||||
constexpr u8 extended_rcode() const { return (extended_rcode_and_flags & MaskExtendedRCode) >> 24; }
|
||||
constexpr u8 version() const { return (extended_rcode_and_flags & MaskVersion) >> 20; }
|
||||
constexpr bool dnssec_ok() const { return extended_rcode_and_flags & MaskDO; }
|
||||
|
||||
void set_extended_rcode(u8 value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskExtendedRCode) | (value << 24); }
|
||||
void set_version(u8 value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskVersion) | (value << 20); }
|
||||
void set_dnssec_ok(bool value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskDO) | (value ? MaskDO : 0); }
|
||||
|
||||
static ErrorOr<OPT> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const;
|
||||
ErrorOr<String> to_string() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.appendff("OPT UDP Payload Size: {}, Extended RCode: {}, Version: {}, DNSSEC OK: {}", udp_payload_size, extended_rcode(), version(), dnssec_ok());
|
||||
for (auto& option : options)
|
||||
builder.appendff(", opt[{} = '{:hex-dump}']", option.code, option.data.bytes());
|
||||
|
||||
return builder.to_string();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using Record = Variant<
|
||||
Records::A,
|
||||
Records::AAAA,
|
||||
Records::TXT,
|
||||
Records::CNAME,
|
||||
Records::NS,
|
||||
Records::SOA,
|
||||
Records::MX,
|
||||
Records::PTR,
|
||||
Records::SRV,
|
||||
Records::DNSKEY,
|
||||
Records::CDNSKEY,
|
||||
Records::DS,
|
||||
Records::CDS,
|
||||
Records::RRSIG,
|
||||
Records::NSEC,
|
||||
Records::NSEC3,
|
||||
Records::NSEC3PARAM,
|
||||
Records::TLSA,
|
||||
Records::HINFO,
|
||||
Records::OPT,
|
||||
// TODO: Add more records.
|
||||
ByteBuffer>; // Fallback for unknown records.
|
||||
|
||||
struct ResourceRecord {
|
||||
DomainName name;
|
||||
ResourceType type;
|
||||
Class class_;
|
||||
u32 ttl;
|
||||
Record record;
|
||||
Optional<ByteBuffer> raw;
|
||||
|
||||
static ErrorOr<ResourceRecord> from_raw(ParseContext&);
|
||||
ErrorOr<void> to_raw(ByteBuffer&) const;
|
||||
ErrorOr<String> to_string() const;
|
||||
};
|
||||
|
||||
struct ZoneAuthority {
|
||||
DomainName name;
|
||||
ByteString admin_mailbox;
|
||||
u32 serial;
|
||||
u32 refresh;
|
||||
u32 retry;
|
||||
u32 expire;
|
||||
u32 minimum_ttl;
|
||||
};
|
||||
|
||||
struct Message {
|
||||
Header header;
|
||||
Vector<Question> questions;
|
||||
Vector<ResourceRecord> answers;
|
||||
Vector<ResourceRecord> authorities;
|
||||
Vector<ResourceRecord> additional_records;
|
||||
|
||||
static ErrorOr<Message> from_raw(ParseContext&);
|
||||
static ErrorOr<Message> from_raw(Stream&);
|
||||
ErrorOr<size_t> to_raw(ByteBuffer&) const;
|
||||
|
||||
ErrorOr<String> format_for_log() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
404
Libraries/LibDNS/Resolver.h
Normal file
404
Libraries/LibDNS/Resolver.h
Normal file
|
@ -0,0 +1,404 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/AtomicRefCounted.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Random.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibCore/Promise.h>
|
||||
#include <LibCore/SocketAddress.h>
|
||||
#include <LibDNS/Message.h>
|
||||
#include <LibThreading/MutexProtected.h>
|
||||
#include <LibThreading/RWLockProtected.h>
|
||||
|
||||
namespace DNS {
|
||||
class Resolver;
|
||||
|
||||
class LookupResult : public AtomicRefCounted<LookupResult>
|
||||
, public Weakable<LookupResult> {
|
||||
public:
|
||||
explicit LookupResult(Messages::DomainName name)
|
||||
: m_name(move(name))
|
||||
{
|
||||
}
|
||||
|
||||
Vector<Variant<IPv4Address, IPv6Address>> cached_addresses() const
|
||||
{
|
||||
Vector<Variant<IPv4Address, IPv6Address>> result;
|
||||
for (auto& record : m_cached_records) {
|
||||
record.record.visit(
|
||||
[&](Messages::Records::A const& a) { result.append(a.address); },
|
||||
[&](Messages::Records::AAAA const& aaaa) { result.append(aaaa.address); },
|
||||
[](auto&) {});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_record(Messages::ResourceRecord record)
|
||||
{
|
||||
m_valid = true;
|
||||
m_cached_records.append(move(record));
|
||||
}
|
||||
|
||||
Vector<Messages::ResourceRecord> const& records() const { return m_cached_records; }
|
||||
|
||||
bool has_record_of_type(Messages::ResourceType type, bool later = false) const
|
||||
{
|
||||
if (later && m_desired_types.contains(type))
|
||||
return true;
|
||||
|
||||
for (auto const& record : m_cached_records) {
|
||||
if (record.type == type)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void will_add_record_of_type(Messages::ResourceType type) { m_desired_types.set(type); }
|
||||
|
||||
void set_id(u16 id) { m_id = id; }
|
||||
u16 id() { return m_id; }
|
||||
|
||||
bool is_valid() const { return m_valid; }
|
||||
Messages::DomainName const& name() const { return m_name; }
|
||||
|
||||
private:
|
||||
bool m_valid { false };
|
||||
Messages::DomainName m_name;
|
||||
Vector<Messages::ResourceRecord> m_cached_records;
|
||||
HashTable<Messages::ResourceType> m_desired_types;
|
||||
u16 m_id { 0 };
|
||||
};
|
||||
|
||||
class Resolver {
|
||||
public:
|
||||
enum class ConnectionMode {
|
||||
TCP,
|
||||
UDP,
|
||||
};
|
||||
|
||||
struct SocketResult {
|
||||
MaybeOwned<Core::Socket> socket;
|
||||
ConnectionMode mode;
|
||||
};
|
||||
|
||||
Resolver(Function<ErrorOr<SocketResult>()> create_socket)
|
||||
: m_pending_lookups(make<RedBlackTree<u16, PendingLookup>>())
|
||||
, m_create_socket(move(create_socket))
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::Promise<Empty>> when_socket_ready()
|
||||
{
|
||||
auto promise = Core::Promise<Empty>::construct();
|
||||
m_socket_ready_promises.append(promise);
|
||||
if (has_connection(false)) {
|
||||
promise->resolve({});
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (!has_connection())
|
||||
promise->reject(Error::from_string_literal("Failed to create socket"));
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
void reset_connection()
|
||||
{
|
||||
m_socket.with_write_locked([&](auto& socket) { socket = {}; });
|
||||
}
|
||||
|
||||
NonnullRefPtr<LookupResult const> expect_cached(StringView name, Messages::Class class_ = Messages::Class::IN)
|
||||
{
|
||||
return expect_cached(name, class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
|
||||
}
|
||||
|
||||
NonnullRefPtr<LookupResult const> expect_cached(StringView name, Messages::Class class_, Span<Messages::ResourceType const> desired_types)
|
||||
{
|
||||
auto result = lookup_in_cache(name, class_, desired_types);
|
||||
VERIFY(!result.is_null());
|
||||
dbgln("DNS::expect({}) -> OK", name);
|
||||
return *result;
|
||||
}
|
||||
|
||||
RefPtr<LookupResult const> lookup_in_cache(StringView name, Messages::Class class_ = Messages::Class::IN)
|
||||
{
|
||||
return lookup_in_cache(name, class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
|
||||
}
|
||||
|
||||
RefPtr<LookupResult const> lookup_in_cache(StringView name, Messages::Class, Span<Messages::ResourceType const> desired_types)
|
||||
{
|
||||
return m_cache.with_read_locked([&](auto& cache) -> RefPtr<LookupResult const> {
|
||||
auto it = cache.find(name);
|
||||
if (it == cache.end())
|
||||
return {};
|
||||
|
||||
auto& result = *it->value;
|
||||
for (auto const& type : desired_types) {
|
||||
if (!result.has_record_of_type(type))
|
||||
return {};
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> lookup(ByteString name, Messages::Class class_ = Messages::Class::IN)
|
||||
{
|
||||
return lookup(move(name), class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
|
||||
}
|
||||
|
||||
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> lookup(ByteString name, Messages::Class class_, Span<Messages::ResourceType const> desired_types)
|
||||
{
|
||||
auto promise = Core::Promise<NonnullRefPtr<LookupResult const>>::construct();
|
||||
|
||||
if (auto result = lookup_in_cache(name, class_, desired_types)) {
|
||||
promise->resolve(result.release_nonnull());
|
||||
return promise;
|
||||
}
|
||||
|
||||
auto domain_name = Messages::DomainName::from_string(name);
|
||||
|
||||
if (!has_connection()) {
|
||||
// Use system resolver
|
||||
// FIXME: Use an underlying resolver instead.
|
||||
dbgln("Not ready to resolve, using system resolver and skipping cache for {}", name);
|
||||
auto record_or_error = Core::Socket::resolve_host(name, Core::Socket::SocketType::Stream);
|
||||
if (record_or_error.is_error()) {
|
||||
promise->reject(record_or_error.release_error());
|
||||
return promise;
|
||||
}
|
||||
auto result = make_ref_counted<LookupResult>(domain_name);
|
||||
auto record = record_or_error.release_value();
|
||||
record.visit(
|
||||
[&](IPv4Address const& address) {
|
||||
result->add_record({ .name = {}, .type = Messages::ResourceType::A, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::A { address }, .raw = {} });
|
||||
},
|
||||
[&](IPv6Address const& address) {
|
||||
result->add_record({ .name = {}, .type = Messages::ResourceType::AAAA, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::AAAA { address }, .raw = {} });
|
||||
});
|
||||
promise->resolve(result);
|
||||
return promise;
|
||||
}
|
||||
|
||||
auto already_in_cache = false;
|
||||
auto result = m_cache.with_write_locked([&](auto& cache) -> NonnullRefPtr<LookupResult> {
|
||||
auto existing = [&] -> RefPtr<LookupResult> {
|
||||
if (cache.contains(name)) {
|
||||
auto ptr = *cache.get(name);
|
||||
|
||||
already_in_cache = true;
|
||||
for (auto const& type : desired_types) {
|
||||
if (!ptr->has_record_of_type(type, true)) {
|
||||
already_in_cache = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (existing)
|
||||
return *existing;
|
||||
|
||||
auto ptr = make_ref_counted<LookupResult>(domain_name);
|
||||
for (auto const& type : desired_types)
|
||||
ptr->will_add_record_of_type(type);
|
||||
cache.set(name, ptr);
|
||||
return ptr;
|
||||
});
|
||||
|
||||
Optional<u16> cached_result_id;
|
||||
if (already_in_cache) {
|
||||
auto id = result->id();
|
||||
cached_result_id = id;
|
||||
auto existing_promise = m_pending_lookups.with_write_locked([&](auto& lookups) -> RefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> {
|
||||
if (auto* lookup = lookups->find(id))
|
||||
return lookup->promise;
|
||||
return nullptr;
|
||||
});
|
||||
if (existing_promise)
|
||||
return existing_promise.release_nonnull();
|
||||
|
||||
promise->resolve(*result);
|
||||
return promise;
|
||||
}
|
||||
|
||||
Messages::Message query;
|
||||
m_pending_lookups.with_read_locked([&](auto& lookups) {
|
||||
do
|
||||
fill_with_random({ &query.header.id, sizeof(query.header.id) });
|
||||
while (lookups->find(query.header.id) != nullptr);
|
||||
});
|
||||
query.header.question_count = max(1u, desired_types.size());
|
||||
query.header.options.set_response_code(Messages::Options::ResponseCode::NoError);
|
||||
query.header.options.set_recursion_desired(true);
|
||||
query.header.options.set_op_code(Messages::OpCode::Query);
|
||||
for (auto const& type : desired_types) {
|
||||
query.questions.append(Messages::Question {
|
||||
.name = domain_name,
|
||||
.type = type,
|
||||
.class_ = class_,
|
||||
});
|
||||
}
|
||||
|
||||
if (query.questions.is_empty()) {
|
||||
query.questions.append(Messages::Question {
|
||||
.name = Messages::DomainName::from_string(name),
|
||||
.type = Messages::ResourceType::A,
|
||||
.class_ = class_,
|
||||
});
|
||||
}
|
||||
|
||||
auto cached_entry = m_pending_lookups.with_write_locked([&](auto& pending_lookups) -> RefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> {
|
||||
// One more try to make sure we're not overwriting an existing lookup
|
||||
if (cached_result_id.has_value()) {
|
||||
if (auto* lookup = pending_lookups->find(*cached_result_id))
|
||||
return lookup->promise;
|
||||
}
|
||||
|
||||
pending_lookups->insert(query.header.id, { query.header.id, name, result->make_weak_ptr(), promise });
|
||||
return nullptr;
|
||||
});
|
||||
if (cached_entry) {
|
||||
dbgln("DNS::lookup({}) -> Already in cache", name);
|
||||
return cached_entry.release_nonnull();
|
||||
}
|
||||
|
||||
ByteBuffer query_bytes;
|
||||
MUST(query.to_raw(query_bytes));
|
||||
|
||||
if (m_mode == ConnectionMode::TCP) {
|
||||
auto original_query_bytes = query_bytes;
|
||||
query_bytes = MUST(ByteBuffer::create_uninitialized(query_bytes.size() + sizeof(u16)));
|
||||
NetworkOrdered<u16> size = original_query_bytes.size();
|
||||
query_bytes.overwrite(0, &size, sizeof(size));
|
||||
query_bytes.overwrite(sizeof(size), original_query_bytes.data(), original_query_bytes.size());
|
||||
}
|
||||
|
||||
auto write_result = m_socket.with_write_locked([&](auto& socket) {
|
||||
return (*socket)->write_until_depleted(query_bytes.bytes());
|
||||
});
|
||||
if (write_result.is_error()) {
|
||||
promise->reject(write_result.release_error());
|
||||
return promise;
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private:
|
||||
struct PendingLookup {
|
||||
u16 id { 0 };
|
||||
ByteString name;
|
||||
WeakPtr<LookupResult> result;
|
||||
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> promise;
|
||||
};
|
||||
|
||||
ErrorOr<Messages::Message> parse_one_message()
|
||||
{
|
||||
if (m_mode == ConnectionMode::UDP)
|
||||
return m_socket.with_write_locked([&](auto& socket) { return Messages::Message::from_raw(**socket); });
|
||||
|
||||
return m_socket.with_write_locked([&](auto& socket) -> ErrorOr<Messages::Message> {
|
||||
if (!TRY((*socket)->can_read_without_blocking()))
|
||||
return Error::from_errno(EAGAIN);
|
||||
|
||||
auto size = TRY((*socket)->template read_value<NetworkOrdered<u16>>());
|
||||
auto buffer = TRY(ByteBuffer::create_uninitialized(size));
|
||||
TRY((*socket)->read_until_filled(buffer));
|
||||
FixedMemoryStream stream { static_cast<ReadonlyBytes>(buffer) };
|
||||
return Messages::Message::from_raw(stream);
|
||||
});
|
||||
}
|
||||
|
||||
void process_incoming_messages()
|
||||
{
|
||||
while (true) {
|
||||
if (auto result = m_socket.with_read_locked([](auto& socket) { return (*socket)->can_read_without_blocking(); }); result.is_error() || !result.value())
|
||||
break;
|
||||
auto message_or_err = parse_one_message();
|
||||
if (message_or_err.is_error()) {
|
||||
if (!message_or_err.error().is_errno() || message_or_err.error().code() != EAGAIN)
|
||||
dbgln("Failed to receive message: {}", message_or_err.error());
|
||||
break;
|
||||
}
|
||||
|
||||
auto message = message_or_err.release_value();
|
||||
auto result = m_pending_lookups.with_write_locked([&](auto& lookups) -> ErrorOr<void> {
|
||||
auto* lookup = lookups->find(message.header.id);
|
||||
if (!lookup)
|
||||
return Error::from_string_literal("No pending lookup found for this message");
|
||||
|
||||
auto result = lookup->result.strong_ref();
|
||||
for (auto& record : message.answers)
|
||||
result->add_record(move(record));
|
||||
|
||||
lookup->promise->resolve(*result);
|
||||
lookups->remove(message.header.id);
|
||||
return {};
|
||||
});
|
||||
if (result.is_error()) {
|
||||
dbgln("Received a message with no pending lookup: {}", result.error());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool has_connection(bool attempt_restart = true)
|
||||
{
|
||||
auto result = m_socket.with_read_locked(
|
||||
[&](auto& socket) { return socket.has_value() && (*socket)->is_open(); });
|
||||
|
||||
if (attempt_restart && !result && !m_attempting_restart) {
|
||||
TemporaryChange change(m_attempting_restart, true);
|
||||
auto create_result = m_create_socket();
|
||||
if (create_result.is_error()) {
|
||||
dbgln("Failed to create socket: {}", create_result.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto [socket, mode] = MUST(move(create_result));
|
||||
set_socket(move(socket), mode);
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void set_socket(MaybeOwned<Core::Socket> socket, ConnectionMode mode = ConnectionMode::UDP)
|
||||
{
|
||||
m_mode = mode;
|
||||
m_socket.with_write_locked([&](auto& s) {
|
||||
s = move(socket);
|
||||
(*s)->on_ready_to_read = [this] {
|
||||
process_incoming_messages();
|
||||
};
|
||||
(*s)->set_notifications_enabled(true);
|
||||
});
|
||||
|
||||
for (auto& promise : m_socket_ready_promises)
|
||||
promise->resolve({});
|
||||
|
||||
m_socket_ready_promises.clear();
|
||||
}
|
||||
|
||||
Threading::RWLockProtected<HashMap<ByteString, NonnullRefPtr<LookupResult>>> m_cache;
|
||||
Threading::RWLockProtected<NonnullOwnPtr<RedBlackTree<u16, PendingLookup>>> m_pending_lookups;
|
||||
Threading::RWLockProtected<Optional<MaybeOwned<Core::Socket>>> m_socket;
|
||||
Function<ErrorOr<SocketResult>()> m_create_socket;
|
||||
bool m_attempting_restart { false };
|
||||
ConnectionMode m_mode { ConnectionMode::UDP };
|
||||
Vector<NonnullRefPtr<Core::Promise<Empty>>> m_socket_ready_promises;
|
||||
};
|
||||
|
||||
}
|
|
@ -72,6 +72,9 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
|
|||
Optional<StringView> profile_process;
|
||||
Optional<StringView> webdriver_content_ipc_path;
|
||||
Optional<StringView> user_agent_preset;
|
||||
Optional<StringView> dns_server_address;
|
||||
Optional<u16> dns_server_port;
|
||||
bool use_dns_over_tls = false;
|
||||
bool log_all_js_exceptions = false;
|
||||
bool enable_idl_tracing = false;
|
||||
bool enable_http_cache = false;
|
||||
|
@ -101,6 +104,9 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
|
|||
args_parser.add_option(force_cpu_painting, "Force CPU painting", "force-cpu-painting");
|
||||
args_parser.add_option(force_fontconfig, "Force using fontconfig for font loading", "force-fontconfig");
|
||||
args_parser.add_option(collect_garbage_on_every_allocation, "Collect garbage after every JS heap allocation", "collect-garbage-on-every-allocation", 'g');
|
||||
args_parser.add_option(dns_server_address, "Set the DNS server address", "dns-server", 0, "host|address");
|
||||
args_parser.add_option(dns_server_port, "Set the DNS server port", "dns-port", 0, "port (default: 53 or 853 if --dot)");
|
||||
args_parser.add_option(use_dns_over_tls, "Use DNS over TLS", "dot");
|
||||
args_parser.add_option(Core::ArgsParser::Option {
|
||||
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
||||
.help_string = "Name of the User-Agent preset to use in place of the default User-Agent",
|
||||
|
@ -120,6 +126,9 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
|
|||
if (force_new_process)
|
||||
disable_sql_database = true;
|
||||
|
||||
if (!dns_server_port.has_value())
|
||||
dns_server_port = use_dns_over_tls ? 853 : 53;
|
||||
|
||||
Optional<ProcessType> debug_process_type;
|
||||
Optional<ProcessType> profile_process_type;
|
||||
|
||||
|
@ -140,6 +149,11 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
|
|||
.disable_sql_database = disable_sql_database ? DisableSQLDatabase::Yes : DisableSQLDatabase::No,
|
||||
.debug_helper_process = move(debug_process_type),
|
||||
.profile_helper_process = move(profile_process_type),
|
||||
.dns_settings = (dns_server_address.has_value()
|
||||
? (use_dns_over_tls
|
||||
? DNSSettings(DNSOverTLS(dns_server_address.release_value(), *dns_server_port))
|
||||
: DNSSettings(DNSOverUDP(dns_server_address.release_value(), *dns_server_port)))
|
||||
: SystemDNS {}),
|
||||
};
|
||||
|
||||
if (webdriver_content_ipc_path.has_value())
|
||||
|
|
|
@ -163,7 +163,19 @@ ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process()
|
|||
arguments.append(server.value());
|
||||
}
|
||||
|
||||
return launch_server_process<Requests::RequestClient>("RequestServer"sv, move(arguments));
|
||||
auto client = TRY(launch_server_process<Requests::RequestClient>("RequestServer"sv, move(arguments)));
|
||||
WebView::Application::chrome_options().dns_settings.visit(
|
||||
[](WebView::SystemDNS) {},
|
||||
[&](WebView::DNSOverTLS const& dns_over_tls) {
|
||||
dbgln("Setting DNS server to {}:{} with TLS", dns_over_tls.server_address, dns_over_tls.port);
|
||||
client->async_set_dns_server(dns_over_tls.server_address, dns_over_tls.port, true);
|
||||
},
|
||||
[&](WebView::DNSOverUDP const& dns_over_udp) {
|
||||
dbgln("Setting DNS server to {}:{}", dns_over_udp.server_address, dns_over_udp.port);
|
||||
client->async_set_dns_server(dns_over_udp.server_address, dns_over_udp.port, false);
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
ErrorOr<IPC::File> connect_new_request_server_client()
|
||||
|
|
|
@ -45,6 +45,18 @@ enum class EnableAutoplay {
|
|||
Yes,
|
||||
};
|
||||
|
||||
struct SystemDNS { };
|
||||
struct DNSOverTLS {
|
||||
ByteString server_address;
|
||||
u16 port;
|
||||
};
|
||||
struct DNSOverUDP {
|
||||
ByteString server_address;
|
||||
u16 port;
|
||||
};
|
||||
|
||||
using DNSSettings = Variant<SystemDNS, DNSOverTLS, DNSOverUDP>;
|
||||
|
||||
struct ChromeOptions {
|
||||
Vector<URL::URL> urls;
|
||||
Vector<ByteString> raw_urls;
|
||||
|
@ -58,6 +70,7 @@ struct ChromeOptions {
|
|||
Optional<ProcessType> debug_helper_process {};
|
||||
Optional<ProcessType> profile_helper_process {};
|
||||
Optional<ByteString> webdriver_content_ipc_path {};
|
||||
DNSSettings dns_settings { SystemDNS {} };
|
||||
};
|
||||
|
||||
enum class IsLayoutTestMode {
|
||||
|
|
|
@ -382,6 +382,7 @@ set(lagom_standard_libraries
|
|||
Compress
|
||||
Crypto
|
||||
Diff
|
||||
DNS
|
||||
GC
|
||||
HTTP
|
||||
IPC
|
||||
|
@ -448,6 +449,8 @@ endif()
|
|||
# Lagom Utilities
|
||||
lagom_utility(abench SOURCES ../../Utilities/abench.cpp LIBS LibMain LibFileSystem LibMedia)
|
||||
|
||||
lagom_utility(dns SOURCES ../../Utilities/dns.cpp LIBS LibDNS LibMain LibTLS LibCrypto)
|
||||
|
||||
if (ENABLE_GUI_TARGETS)
|
||||
lagom_utility(animation SOURCES ../../Utilities/animation.cpp LIBS LibGfx LibMain)
|
||||
lagom_utility(icc SOURCES ../../Utilities/icc.cpp LIBS LibGfx LibMain LibURL)
|
||||
|
|
|
@ -25,7 +25,7 @@ target_include_directories(requestserverservice PRIVATE ${CMAKE_CURRENT_BINARY_D
|
|||
target_include_directories(requestserverservice PRIVATE ${LADYBIRD_SOURCE_DIR}/Services/)
|
||||
|
||||
target_link_libraries(RequestServer PRIVATE requestserverservice)
|
||||
target_link_libraries(requestserverservice PUBLIC LibCore LibMain LibCrypto LibFileSystem LibIPC LibMain LibTLS LibWebView LibWebSocket LibURL LibTextCodec LibThreading CURL::libcurl)
|
||||
target_link_libraries(requestserverservice PUBLIC LibCore LibDNS LibMain LibCrypto LibFileSystem LibIPC LibMain LibTLS LibWebView LibWebSocket LibURL LibTextCodec LibThreading CURL::libcurl)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
|
||||
# Solaris has socket and networking related functions in two extra libraries
|
||||
|
|
|
@ -24,6 +24,47 @@ ByteString g_default_certificate_path;
|
|||
static HashMap<int, RefPtr<ConnectionFromClient>> s_connections;
|
||||
static IDAllocator s_client_ids;
|
||||
static long s_connect_timeout_seconds = 90L;
|
||||
static HashMap<ByteString, ByteString> g_dns_cache; // host -> curl "resolve" string
|
||||
static struct {
|
||||
Optional<Core::SocketAddress> server_address;
|
||||
Optional<ByteString> server_hostname;
|
||||
u16 port;
|
||||
bool use_dns_over_tls = true;
|
||||
} g_dns_info;
|
||||
|
||||
static WeakPtr<Resolver> s_resolver {};
|
||||
static NonnullRefPtr<Resolver> default_resolver()
|
||||
{
|
||||
if (auto resolver = s_resolver.strong_ref())
|
||||
return *resolver;
|
||||
auto resolver = make_ref_counted<Resolver>([] -> ErrorOr<DNS::Resolver::SocketResult> {
|
||||
if (!g_dns_info.server_address.has_value()) {
|
||||
if (!g_dns_info.server_hostname.has_value())
|
||||
return Error::from_string_literal("No DNS server configured");
|
||||
|
||||
auto resolved = TRY(default_resolver()->dns.lookup(*g_dns_info.server_hostname)->await());
|
||||
if (resolved->cached_addresses().is_empty())
|
||||
return Error::from_string_literal("Failed to resolve DNS server hostname");
|
||||
auto address = resolved->cached_addresses().first().visit([](auto& addr) -> Core::SocketAddress { return { addr, g_dns_info.port }; });
|
||||
g_dns_info.server_address = address;
|
||||
}
|
||||
|
||||
if (g_dns_info.use_dns_over_tls) {
|
||||
return DNS::Resolver::SocketResult {
|
||||
MaybeOwned<Core::Socket>(TRY(TLS::TLSv12::connect(*g_dns_info.server_address, *g_dns_info.server_hostname))),
|
||||
DNS::Resolver::ConnectionMode::TCP,
|
||||
};
|
||||
}
|
||||
|
||||
return DNS::Resolver::SocketResult {
|
||||
MaybeOwned<Core::Socket>(TRY(Core::BufferedSocket<Core::UDPSocket>::create(TRY(Core::UDPSocket::connect(*g_dns_info.server_address))))),
|
||||
DNS::Resolver::ConnectionMode::UDP,
|
||||
};
|
||||
});
|
||||
|
||||
s_resolver = resolver;
|
||||
return resolver;
|
||||
}
|
||||
|
||||
struct ConnectionFromClient::ActiveRequest {
|
||||
CURLM* multi { nullptr };
|
||||
|
@ -199,6 +240,7 @@ int ConnectionFromClient::on_timeout_callback(void*, long timeout_ms, void* user
|
|||
|
||||
ConnectionFromClient::ConnectionFromClient(IPC::Transport transport)
|
||||
: IPC::ConnectionFromClient<RequestClientEndpoint, RequestServerEndpoint>(*this, move(transport), s_client_ids.allocate())
|
||||
, m_resolver(default_resolver())
|
||||
{
|
||||
s_connections.set(client_id(), *this);
|
||||
|
||||
|
@ -264,6 +306,33 @@ Messages::RequestServer::IsSupportedProtocolResponse ConnectionFromClient::is_su
|
|||
return protocol == "http"sv || protocol == "https"sv;
|
||||
}
|
||||
|
||||
void ConnectionFromClient::set_dns_server(ByteString const& host_or_address, u16 port, bool use_tls)
|
||||
{
|
||||
if (host_or_address == g_dns_info.server_hostname && port == g_dns_info.port && use_tls == g_dns_info.use_dns_over_tls)
|
||||
return;
|
||||
|
||||
auto result = [&] -> ErrorOr<void> {
|
||||
Core::SocketAddress addr;
|
||||
if (auto v4 = IPv4Address::from_string(host_or_address); v4.has_value())
|
||||
addr = { v4.value(), port };
|
||||
else if (auto v6 = IPv6Address::from_string(host_or_address); v6.has_value())
|
||||
addr = { v6.value(), port };
|
||||
else
|
||||
TRY(default_resolver()->dns.lookup(host_or_address)->await())->cached_addresses().first().visit([&](auto& address) { addr = { address, port }; });
|
||||
|
||||
g_dns_info.server_address = addr;
|
||||
g_dns_info.server_hostname = host_or_address;
|
||||
g_dns_info.port = port;
|
||||
g_dns_info.use_dns_over_tls = use_tls;
|
||||
return {};
|
||||
}();
|
||||
|
||||
if (result.is_error())
|
||||
dbgln("Failed to set DNS server: {}", result.error());
|
||||
else
|
||||
default_resolver()->dns.reset_connection();
|
||||
}
|
||||
|
||||
void ConnectionFromClient::start_request(i32 request_id, ByteString const& method, URL::URL const& url, HTTP::HeaderMap const& request_headers, ByteBuffer const& request_body, Core::ProxyData const& proxy_data)
|
||||
{
|
||||
if (!url.is_valid()) {
|
||||
|
@ -272,6 +341,22 @@ void ConnectionFromClient::start_request(i32 request_id, ByteString const& metho
|
|||
return;
|
||||
}
|
||||
|
||||
auto host = url.serialized_host().value().to_byte_string();
|
||||
auto dns_promise = m_resolver->dns.lookup(host, DNS::Messages::Class::IN, Array { DNS::Messages::ResourceType::A, DNS::Messages::ResourceType::AAAA }.span());
|
||||
auto resolve_result = dns_promise->await();
|
||||
if (resolve_result.is_error()) {
|
||||
dbgln("StartRequest: DNS lookup failed for '{}': {}", host, resolve_result.error());
|
||||
async_request_finished(request_id, 0, Requests::NetworkError::UnableToResolveHost);
|
||||
return;
|
||||
}
|
||||
|
||||
auto dns_result = resolve_result.release_value();
|
||||
if (dns_result->records().is_empty()) {
|
||||
dbgln("StartRequest: DNS lookup failed for '{}'", host);
|
||||
async_request_finished(request_id, 0, Requests::NetworkError::UnableToResolveHost);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* easy = curl_easy_init();
|
||||
if (!easy) {
|
||||
dbgln("StartRequest: Failed to initialize curl easy handle");
|
||||
|
@ -349,6 +434,24 @@ void ConnectionFromClient::start_request(i32 request_id, ByteString const& metho
|
|||
set_option(CURLOPT_HEADERFUNCTION, &on_header_received);
|
||||
set_option(CURLOPT_HEADERDATA, reinterpret_cast<void*>(request.ptr()));
|
||||
|
||||
StringBuilder resolve_opt_builder;
|
||||
resolve_opt_builder.appendff("{}:{}:", host, url.port_or_default());
|
||||
auto first = true;
|
||||
for (auto& addr : dns_result->cached_addresses()) {
|
||||
auto formatted_address = addr.visit(
|
||||
[&](IPv4Address const& ipv4) { return ipv4.to_byte_string(); },
|
||||
[&](IPv6Address const& ipv6) { return MUST(ipv6.to_string()).to_byte_string(); });
|
||||
if (!first)
|
||||
resolve_opt_builder.append(',');
|
||||
first = false;
|
||||
resolve_opt_builder.append(formatted_address);
|
||||
}
|
||||
|
||||
auto formatted_address = resolve_opt_builder.to_byte_string();
|
||||
g_dns_cache.set(host, formatted_address);
|
||||
curl_slist* resolve_list = curl_slist_append(nullptr, formatted_address.characters());
|
||||
curl_easy_setopt(easy, CURLOPT_RESOLVE, resolve_list);
|
||||
|
||||
auto result = curl_multi_add_handle(m_curl_multi, easy);
|
||||
VERIFY(result == CURLM_OK);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibDNS/Resolver.h>
|
||||
#include <LibIPC/ConnectionFromClient.h>
|
||||
#include <LibWebSocket/WebSocket.h>
|
||||
#include <RequestServer/Forward.h>
|
||||
|
@ -15,6 +16,16 @@
|
|||
|
||||
namespace RequestServer {
|
||||
|
||||
struct Resolver : public RefCounted<Resolver>
|
||||
, Weakable<Resolver> {
|
||||
Resolver(Function<ErrorOr<DNS::Resolver::SocketResult>()> create_socket)
|
||||
: dns(move(create_socket))
|
||||
{
|
||||
}
|
||||
|
||||
DNS::Resolver dns;
|
||||
};
|
||||
|
||||
class ConnectionFromClient final
|
||||
: public IPC::ConnectionFromClient<RequestClientEndpoint, RequestServerEndpoint> {
|
||||
C_OBJECT(ConnectionFromClient);
|
||||
|
@ -34,6 +45,7 @@ private:
|
|||
|
||||
virtual Messages::RequestServer::ConnectNewClientResponse connect_new_client() override;
|
||||
virtual Messages::RequestServer::IsSupportedProtocolResponse is_supported_protocol(ByteString const&) override;
|
||||
virtual void set_dns_server(ByteString const& host_or_address, u16 port, bool use_tls) override;
|
||||
virtual void start_request(i32 request_id, ByteString const&, URL::URL const&, HTTP::HeaderMap const&, ByteBuffer const&, Core::ProxyData const&) override;
|
||||
virtual Messages::RequestServer::StopRequestResponse stop_request(i32) override;
|
||||
virtual Messages::RequestServer::SetCertificateResponse set_certificate(i32, ByteString const&, ByteString const&) override;
|
||||
|
@ -61,6 +73,7 @@ private:
|
|||
RefPtr<Core::Timer> m_timer;
|
||||
HashMap<int, NonnullRefPtr<Core::Notifier>> m_read_notifiers;
|
||||
HashMap<int, NonnullRefPtr<Core::Notifier>> m_write_notifiers;
|
||||
NonnullRefPtr<Resolver> m_resolver;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ endpoint RequestServer
|
|||
{
|
||||
connect_new_client() => (IPC::File client_socket)
|
||||
|
||||
// use_tls: enable DNS over TLS
|
||||
set_dns_server(ByteString host_or_address, u16 port, bool use_tls) =|
|
||||
|
||||
// Test if a specific protocol is supported, e.g "http"
|
||||
is_supported_protocol(ByteString protocol) => (bool supported)
|
||||
|
||||
|
|
132
Utilities/dns.cpp
Normal file
132
Utilities/dns.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibDNS/Resolver.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
{
|
||||
struct Request {
|
||||
Vector<DNS::Messages::ResourceType> types;
|
||||
ByteString name;
|
||||
};
|
||||
Vector<Request> requests;
|
||||
Request current_request;
|
||||
StringView server_address;
|
||||
StringView cert_path;
|
||||
bool use_tls = false;
|
||||
|
||||
Core::ArgsParser args_parser;
|
||||
args_parser.add_option(cert_path, "Path to the CA certificate file", "ca-certs", 'C', "file");
|
||||
args_parser.add_option(server_address, "The address of the DNS server to query", "server", 's', "addr");
|
||||
args_parser.add_option(use_tls, "Use TLS to connect to the server", "tls", 0);
|
||||
args_parser.add_positional_argument(Core::ArgsParser::Arg {
|
||||
.help_string = "The resource types and name of the DNS record to query",
|
||||
.name = "rr,rr@name",
|
||||
.min_values = 1,
|
||||
.max_values = 99999,
|
||||
.accept_value = [&](StringView name) -> ErrorOr<bool> {
|
||||
auto parts = name.split_view('@');
|
||||
if (parts.size() > 2 || parts.is_empty())
|
||||
return Error::from_string_literal("Invalid record/name format");
|
||||
|
||||
if (parts.size() == 1) {
|
||||
current_request.types.append(DNS::Messages::ResourceType::ANY);
|
||||
current_request.name = parts[0];
|
||||
} else {
|
||||
auto rr_parts = parts[0].split_view(',');
|
||||
for (auto& rr : rr_parts) {
|
||||
ByteString rr_name = rr;
|
||||
auto type = DNS::Messages::resource_type_from_string(rr_name.to_uppercase());
|
||||
if (!type.has_value())
|
||||
return Error::from_string_literal("Invalid resource type");
|
||||
current_request.types.append(type.value());
|
||||
}
|
||||
current_request.name = parts[1];
|
||||
}
|
||||
requests.append(move(current_request));
|
||||
current_request = {};
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
args_parser.parse(arguments);
|
||||
|
||||
if (server_address.is_empty()) {
|
||||
outln("You must specify a server address to query");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (current_request.name.is_empty() && !current_request.types.is_empty()) {
|
||||
outln("You must specify at least one DNS record to query");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Core::EventLoop loop;
|
||||
|
||||
DNS::Resolver resolver {
|
||||
[&] -> ErrorOr<DNS::Resolver::SocketResult> {
|
||||
Core::SocketAddress addr;
|
||||
if (auto v4 = IPv4Address::from_string(server_address); v4.has_value()) {
|
||||
addr = { v4.value(), static_cast<u16>(use_tls ? 853 : 53) };
|
||||
} else if (auto v6 = IPv6Address::from_string(server_address); v6.has_value()) {
|
||||
addr = { v6.value(), static_cast<u16>(use_tls ? 853 : 53) };
|
||||
} else {
|
||||
return MUST(resolver.lookup(server_address)->await())->cached_addresses().first().visit([&](auto& address) -> DNS::Resolver::SocketResult {
|
||||
if (use_tls) {
|
||||
auto tls = MUST(TLS::TLSv12::connect({ address, 853 }, server_address));
|
||||
return { move(tls), DNS::Resolver::ConnectionMode::TCP };
|
||||
}
|
||||
return { MUST(Core::BufferedSocket<Core::UDPSocket>::create(MUST(Core::UDPSocket::connect({ address, 53 })))), DNS::Resolver::ConnectionMode::UDP };
|
||||
});
|
||||
}
|
||||
|
||||
if (use_tls)
|
||||
return DNS::Resolver::SocketResult { MUST(TLS::TLSv12::connect(addr, server_address)), DNS::Resolver::ConnectionMode::TCP };
|
||||
return DNS::Resolver::SocketResult { MUST(Core::BufferedSocket<Core::UDPSocket>::create(MUST(Core::UDPSocket::connect(addr)))), DNS::Resolver::ConnectionMode::UDP };
|
||||
}
|
||||
};
|
||||
|
||||
DefaultRootCACertificates::set_default_certificate_paths(Array<ByteString, 1> { cert_path.is_empty() ? "/etc/ssl/cert.pem"sv : cert_path });
|
||||
|
||||
MUST(resolver.when_socket_ready()->await());
|
||||
|
||||
size_t pending_requests = requests.size();
|
||||
for (auto& request : requests) {
|
||||
resolver.lookup(request.name, DNS::Messages::Class::IN, request.types)
|
||||
->when_resolved([&](auto& result) {
|
||||
outln("Resolved {}:", request.name);
|
||||
HashTable<DNS::Messages::ResourceType> types;
|
||||
auto& recs = result->records();
|
||||
for (auto& record : recs)
|
||||
types.set(record.type);
|
||||
|
||||
for (auto& type : types) {
|
||||
outln(" - {} IN {}:", request.name, DNS::Messages::to_string(type));
|
||||
for (auto& record : recs) {
|
||||
if (type != record.type)
|
||||
continue;
|
||||
|
||||
outln(" - {}", record.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if (--pending_requests == 0)
|
||||
loop.quit(0);
|
||||
})
|
||||
.when_rejected([&](auto& error) {
|
||||
outln("Failed to resolve {} IN {}: {}", request.name, DNS::Messages::to_string(request.types.first()), error);
|
||||
if (--pending_requests == 0)
|
||||
loop.quit(1);
|
||||
});
|
||||
}
|
||||
|
||||
return loop.exec();
|
||||
}
|
Loading…
Reference in a new issue