You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lokinet/llarp/dns/unbound_resolver.cpp

241 lines
6.2 KiB
C++

#include "unbound_resolver.hpp"
#include "server.hpp"
#include <llarp/constants/apple.hpp>
#include <llarp/constants/platform.hpp>
#include <llarp/util/buffer.hpp>
#include <sstream>
#include <llarp/util/str.hpp>
#include <unbound.h>
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
struct PendingUnboundLookup
{
std::weak_ptr<UnboundResolver> resolver;
Message msg;
SockAddr resolverAddr;
SockAddr askerAddr;
};
void
UnboundResolver::Stop()
{
Reset();
}
4 years ago
void
UnboundResolver::Reset()
{
started = false;
#ifdef _WIN32
if (runner.joinable())
{
runner.join();
}
#else
if (udp)
{
udp->close();
}
udp.reset();
#endif
if (unboundContext)
{
ub_ctx_delete(unboundContext);
}
unboundContext = nullptr;
}
UnboundResolver::UnboundResolver(EventLoop_ptr _loop, ReplyFunction reply, FailFunction fail)
: unboundContext{nullptr}
, started{false}
, replyFunc{_loop->make_caller(std::move(reply))}
, failFunc{_loop->make_caller(std::move(fail))}
{
#ifndef _WIN32
loop = _loop->MaybeGetUVWLoop();
#endif
}
// static callback
4 years ago
void
UnboundResolver::Callback(void* data, int err, ub_result* result)
{
std::unique_ptr<PendingUnboundLookup> lookup{static_cast<PendingUnboundLookup*>(data)};
auto this_ptr = lookup->resolver.lock();
4 years ago
if (not this_ptr)
return; // resolver is gone, so we don't reply.
if (err != 0)
{
Message& msg = lookup->msg;
msg.AddServFail();
this_ptr->failFunc(lookup->askerAddr, lookup->resolverAddr, msg);
ub_resolve_free(result);
return;
}
Replace libuv with uvw & related refactoring - removes all the llarp_ev_* functions, replacing with methods/classes/functions in the llarp namespace. - banish ev/ev.h to the void - Passes various things by const lvalue ref, especially shared_ptr's that don't need to be copied (to avoid an atomic refcount increment/decrement). - Add a llarp::UDPHandle abstract class for UDP handling - Removes the UDP tick handler; code that needs tick can just do a separate handler on the event loop outside the UDP socket. - Adds an "OwnedBuffer" which owns its own memory but is implicitly convertible to a llarp_buffer_t. This is mostly needed to take over ownership of buffers from uvw without copying them as, currently, uvw does its own allocation (pending some open upstream issues/PRs). - Logic: - add `make_caller`/`call_forever`/`call_every` utility functions to abstract Call wrapping and dependent timed tasks. - Add inLogicThread() so that code can tell its inside the logic thread (typically for debugging assertions). - get rid of janky integer returns and dealing with cancellations on call_later: the other methods added here and the event loop code remove the need for them. - Event loop: - redo everything with uvw instead of libuv - rename EventLoopWakeup::Wakeup to EventLoopWakeup::Trigger to better reflect what it does. - add EventLoopRepeater for repeated events, and replace the code that reschedules itself every time it is called with a repeater. - Split up `EventLoop::run()` into a non-virtual base method and abstract `run_loop()` methods; the base method does a couple extra setup/teardown things that don't need to be in the derived class. - udp_listen is replaced with ev->udp(...) which returns a new UDPHandle object rather that needing gross C-style-but-not-actually-C-compatible structs. - Remove unused register_poll_fd_(un)readable - Use shared_ptr for EventLoopWakeup rather than returning a raw pointer; uvw lets us not have to worry about having the event loop class maintain ownership of it. - Add factory EventLoop::create() function to create a default (uvw-based) event loop (previously this was one of the llarp_ev_blahblah unnamespaced functions). - ev_libuv: this is mostly rewritten; all of the glue code/structs, in particular, are gone as they are no longer needed with uvw. - DNS: - Rename DnsHandler to DnsInterceptor to better describe what it does (this is the code that intercepts all DNS to the tun IP range for Android). - endpoint: - remove unused "isolated network" code - remove distinct (but actually always the same) variables for router/endpoint logic objects - llarp_buffer_t - make constructors type-safe against being called with points to non-size-1 values - tun packet reading: - read all available packets off the device/file descriptor; previously we were reading one packet at a time then returning to the event loop to poll again. - ReadNextPacket() now returns a 0-size packet if the read would block (so that we can implement the previous point). - ReadNextPacket() now throws on I/O error - Miscellaneous code cleanups/simplifications
3 years ago
OwnedBuffer pkt{(size_t)result->answer_len};
std::memcpy(pkt.buf.get(), result->answer_packet, pkt.sz);
llarp_buffer_t buf(pkt);
MessageHeader hdr;
hdr.Decode(&buf);
hdr.id = lookup->msg.hdr_id;
buf.cur = buf.base;
hdr.Encode(&buf);
this_ptr->replyFunc(lookup->askerAddr, lookup->resolverAddr, std::move(pkt));
ub_resolve_free(result);
}
4 years ago
bool
UnboundResolver::Init()
{
if (started)
{
Reset();
}
unboundContext = ub_ctx_create();
if (not unboundContext)
{
return false;
}
// disable ip6 for upstream dns
ub_ctx_set_option(unboundContext, "prefer-ip6", "0");
// enable async
ub_ctx_async(unboundContext, 1);
#ifdef _WIN32
runner = std::thread{[&]() {
while (started)
{
if (unboundContext)
ub_wait(unboundContext);
std::this_thread::sleep_for(25ms);
}
if (unboundContext)
ub_process(unboundContext);
}};
#else
if (auto loop_ptr = loop.lock())
{
udp = loop_ptr->resource<uvw::PollHandle>(ub_fd(unboundContext));
udp->on<uvw::PollEvent>([ptr = weak_from_this()](auto&, auto&) {
if (auto self = ptr.lock())
{
if (self->unboundContext)
{
ub_process(self->unboundContext);
}
}
});
udp->start(uvw::PollHandle::Event::READABLE);
}
#endif
started = true;
return true;
}
4 years ago
bool
UnboundResolver::AddUpstreamResolver(const SockAddr& upstreamResolver)
{
const auto hoststr = upstreamResolver.hostString();
std::string upstream = hoststr;
const auto port = upstreamResolver.getPort();
if (port != 53)
{
upstream += '@';
upstream += std::to_string(port);
}
log::info("Adding upstream resolver ", upstream);
if (ub_ctx_set_fwd(unboundContext, upstream.c_str()) != 0)
{
Reset();
return false;
}
if constexpr (platform::is_apple)
3 years ago
{
// On Apple, when we turn on exit mode, we can't directly connect to upstream from here
// because, from within the network extension, macOS ignores setting the tunnel as the default
// route and would leak all DNS; instead we have to bounce things through the objective C
// trampoline code so that it can call into Apple's special snowflake API to set up a socket
// that has the magic Apple snowflake sauce added on top so that it actually routes through
// the tunnel instead of around it.
//
// This behaviour is all carefully and explicitly documented by Apple with plenty of examples
// and other exposition, of course, just like all of their wonderful new APIs to reinvent
// standard unix interfaces.
if (hoststr == "127.0.0.1" && port == apple::dns_trampoline_port)
{
// Not at all clear why this is needed but without it we get "send failed: Can't assign
// requested address" when unbound tries to connect to the localhost address using a source
// address of 0.0.0.0. Yay apple.
ub_ctx_set_option(unboundContext, "outgoing-interface:", "127.0.0.1");
// The trampoline expects just a single source port (and sends everything back to it)
ub_ctx_set_option(unboundContext, "outgoing-range:", "1");
ub_ctx_set_option(unboundContext, "outgoing-port-avoid:", "0-65535");
ub_ctx_set_option(
unboundContext,
"outgoing-port-permit:",
std::to_string(apple::dns_trampoline_source_port).c_str());
}
}
return true;
}
void
UnboundResolver::AddHostsFile(const fs::path& file)
{
log::debug(logcat, "adding hosts file {}", file);
const auto str = file.u8string();
if (auto ret = ub_ctx_hosts(unboundContext, str.c_str()))
throw std::runtime_error{
fmt::format("Failed to add host file {}: {}", file, ub_strerror(ret))};
log::info(logcat, "added hosts file {}", file);
}
4 years ago
void
UnboundResolver::Lookup(SockAddr to, SockAddr from, Message msg)
{
if (not unboundContext)
{
msg.AddServFail();
failFunc(from, to, std::move(msg));
return;
}
const auto& q = msg.questions[0];
auto* lookup = new PendingUnboundLookup{weak_from_this(), msg, to, from};
4 years ago
int err = ub_resolve_async(
unboundContext,
q.Name().c_str(),
q.qtype,
q.qclass,
(void*)lookup,
&UnboundResolver::Callback,
nullptr);
if (err != 0)
{
msg.AddServFail();
failFunc(from, to, std::move(msg));
return;
}
}
4 years ago
} // namespace llarp::dns