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:
Ali Mohammad Pur 2024-11-01 23:53:43 +01:00 committed by Ali Mohammad Pur
commit 7e20f4726f
Notes: github-actions[bot] 2024-11-20 20:44:27 +00:00
13 changed files with 2586 additions and 2 deletions

View file

@ -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

View file

@ -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);

View file

@ -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;
};
}

View file

@ -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)