refactor dns subsystem

we want to be able to have multiple locally bound dns sockets in lokinet so
i restructured most of the dns subsystem in order to make this easier.

specifically, we have a new structure to dns subsystem:

* dns::QueryJob_Base

base type for holding a dns query and response with virtual methods
in charge of sending a reply to whoever requested.

* dns::PacketSource_Base

base type for reading and writing dns messages to and from wherever they came from

* dns::Resolver_Base

base type for filtering and handling of dns messages asynchronously.

* dns::Server

contextualized per endpoint dns object, responsible for all dns related isms.

this change hides all impelementation details of all of the dns components.
adds some more helper functions for parsing dns and dealing with OwnedBuffer.

overall dns becomes less of a pain with this new structure. probably.
pull/1969/head
Jeff 2 years ago committed by Jeff Becker
parent bf2488d9e8
commit 74362149eb
No known key found for this signature in database
GPG Key ID: 025C02EE3A092F2D

@ -85,7 +85,7 @@ add_library(liblokinet
dns/serialize.cpp
dns/server.cpp
dns/srv_data.cpp
dns/unbound_resolver.cpp
dns/resolver.cpp
consensus/table.cpp
consensus/reachability_testing.cpp
@ -151,7 +151,7 @@ add_library(liblokinet
peerstats/types.cpp
pow.cpp
profiling.cpp
quic/address.cpp
quic/client.cpp
quic/connection.cpp
@ -171,7 +171,7 @@ add_library(liblokinet
router/rc_gossiper.cpp
router/router.cpp
router/route_poker.cpp
router/systemd_resolved.cpp
routing/dht_message.cpp
routing/message_parser.cpp
routing/path_confirm_message.cpp

@ -775,18 +775,25 @@ namespace llarp
// Most non-linux platforms have loopback as 127.0.0.1/32, but linux uses 127.0.0.1/8 so that we
// can bind to other 127.* IPs to avoid conflicting with something else that may be listening on
// 127.0.0.1:53.
constexpr Default DefaultDNSBind{platform::is_linux ? "127.3.2.1:53" : "127.0.0.1:53"};
#ifdef __linux__
#ifdef WITH_SYSTEMD
// when we have systemd support add a random high port on loopback
// see https://github.com/oxen-io/lokinet/issues/1887#issuecomment-1091897282
constexpr Default DefaultDNSBind{"127.0.0.1:0"};
#else
constexpr Default DefaultDNSBind{"127.3.2.1:53"};
#endif
#else
constexpr Default DefaultDNSBind{"127.0.0.1:53"};
#endif
// Default, but if we get any upstream (including upstream=, i.e. empty string) we clear it
constexpr Default DefaultUpstreamDNS{"9.9.9.10"};
constexpr Default DefaultUpstreamDNS{"9.9.9.10:53"};
m_upstreamDNS.emplace_back(DefaultUpstreamDNS.val);
if (!m_upstreamDNS.back().getPort())
m_upstreamDNS.back().setPort(53);
conf.defineOption<std::string>(
"dns",
"upstream",
DefaultUpstreamDNS,
MultiValue,
Comment{
"Upstream resolver(s) to use as fallback for non-loki addresses.",
@ -798,10 +805,10 @@ namespace llarp
m_upstreamDNS.clear();
first = false;
}
if (!arg.empty())
if (not arg.empty())
{
auto& entry = m_upstreamDNS.emplace_back(std::move(arg));
if (!entry.getPort())
if (not entry.getPort())
entry.setPort(53);
}
});
@ -814,9 +821,12 @@ namespace llarp
"Address to bind to for handling DNS requests.",
},
[=](std::string arg) {
m_bind = SockAddr{std::move(arg)};
if (!m_bind.getPort())
m_bind.setPort(53);
SockAddr addr{arg};
// set dns port if no explicit port specified
// explicit :0 allowed
if (not addr.getPort() and not ends_with(arg, ":0"))
addr.setPort(53);
m_bind.emplace_back(addr);
});
conf.defineOption<fs::path>(
@ -843,6 +853,11 @@ namespace llarp
"(This is not used directly by lokinet itself, but by the lokinet init scripts",
"on systems which use resolveconf)",
});
// forwad the rest to libunbound
conf.addUndeclaredHandler("dns", [this](auto, std::string_view key, std::string_view val) {
m_ExtraOpts.emplace(key, val);
});
}
void

@ -155,9 +155,10 @@ namespace llarp
struct DnsConfig
{
SockAddr m_bind;
std::vector<SockAddr> m_bind;
std::vector<SockAddr> m_upstreamDNS;
std::vector<fs::path> m_hostfiles;
std::unordered_multimap<std::string, std::string> m_ExtraOpts;
void
defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params);

@ -414,5 +414,17 @@ namespace llarp
fmt::format("{}", fmt::join(additional, ",")));
}
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf)
{
MessageHeader hdr{};
if (not hdr.Decode(&buf))
return std::nullopt;
Message msg{hdr};
if (not msg.Decode(&buf))
return std::nullopt;
return msg;
}
} // namespace dns
} // namespace llarp

@ -103,6 +103,9 @@ namespace llarp
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additional;
};
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf);
} // namespace dns
template <>

@ -1,17 +1,17 @@
#include "systemd_resolved.hpp"
#include "resolver.hpp"
#include <llarp/util/logging.hpp>
#ifndef WITH_SYSTEMD
namespace llarp
namespace llarp::dns
{
bool
systemd_resolved_set_dns(std::string, llarp::SockAddr, bool)
set_resolver(std::string, llarp::SockAddr, bool)
{
LogDebug("lokinet is not built with systemd support, cannot set systemd resolved DNS");
return false;
}
} // namespace llarp
} // namespace llarp::dns
#else
@ -25,7 +25,7 @@ extern "C"
using namespace std::literals;
namespace llarp
namespace llarp::dns
{
namespace
{
@ -64,7 +64,7 @@ namespace llarp
} // namespace
bool
systemd_resolved_set_dns(std::string ifname, llarp::SockAddr dns, bool global)
set_resolver(std::string ifname, llarp::SockAddr dns, bool global)
{
unsigned int if_ndx = if_nametoindex(ifname.c_str());
if (if_ndx == 0)
@ -204,6 +204,6 @@ namespace llarp
return false;
}
} // namespace llarp
} // namespace llarp::dns
#endif // WITH_SYSTEMD

@ -1,13 +1,14 @@
#pragma once
#include <string>
#include <llarp/net/sock_addr.hpp>
namespace llarp
namespace llarp::dns
{
/// Attempts to set lokinet as the DNS server for systemd-resolved. Returns true if successful,
/// false if unsupported or fails. (When compiled without systemd support this always returns
/// false without doing anything).
/// Attempts to set lokinet as the DNS server for systemd-resolved.
/// Returns true if successful, false if unsupported or fails.
///
/// If systemd support is enabled it will attempt via dbus call to system-resolved
/// When compiled without systemd support this always return false without doing anything.
///
/// \param if_name -- the interface name to which we add the DNS servers, e.g. lokitun0.
/// Typically tun_endpoint.GetIfName().
@ -15,5 +16,6 @@ namespace llarp
/// \param global -- whether to set up lokinet for all DNS queries (true) or just .loki & .snode
/// addresses (false).
bool
systemd_resolved_set_dns(std::string if_name, llarp::SockAddr dns, bool global);
} // namespace llarp
set_resolver(std::string if_name, llarp::SockAddr dns, bool global);
} // namespace llarp::dns

@ -4,158 +4,539 @@
#include <array>
#include <utility>
#include <llarp/ev/udp_handle.hpp>
#include <optional>
#include <memory>
#include <unbound.h>
#include <uvw.hpp>
namespace llarp::dns
{
PacketHandler::PacketHandler(EventLoop_ptr loop, IQueryHandler* h)
: m_QueryHandler{h}, m_Loop{std::move(loop)}
{}
Proxy::Proxy(EventLoop_ptr loop, IQueryHandler* h)
: PacketHandler{loop, h}, m_Loop(std::move(loop))
{
m_Server = m_Loop->make_udp(
[this](UDPHandle&, SockAddr a, OwnedBuffer buf) { HandlePacket(a, a, buf); });
}
void
PacketHandler::Stop()
QueryJob_Base::Cancel() const
{
if (m_UnboundResolver)
m_UnboundResolver->Stop();
Message reply{m_Query};
reply.AddServFail();
SendReply(reply.ToBuffer());
}
bool
Proxy::Start(SockAddr addr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles)
/// sucks up udp packets from a bound socket and feeds it to a server
class UDPReader : public PacketSource_Base, public std::enable_shared_from_this<UDPReader>
{
if (not PacketHandler::Start(addr, std::move(resolvers), std::move(hostfiles)))
return false;
return m_Server->listen(addr);
}
Server& m_DNS;
std::shared_ptr<llarp::UDPHandle> m_udp;
SockAddr m_LocalAddr;
void
PacketHandler::Restart()
{
if (m_UnboundResolver)
public:
explicit UDPReader(Server& dns, const EventLoop_ptr& loop, llarp::SockAddr bindaddr)
: m_DNS{dns}
{
LogInfo("reset libunbound's internal stuff");
m_UnboundResolver->Init();
m_udp = loop->make_udp([&](auto&, SockAddr src, llarp::OwnedBuffer buf) {
if (src == m_LocalAddr)
return;
if (not m_DNS.MaybeHandlePacket(weak_from_this(), m_LocalAddr, src, std::move(buf)))
{
LogWarn("did not handle dns packet from ", src, " to ", m_LocalAddr);
}
});
m_udp->listen(bindaddr);
if (auto maybe_addr = BoundOn())
{
m_LocalAddr = *maybe_addr;
}
else
throw std::runtime_error{"cannot find which address our dns socket is bound on"};
}
}
bool
PacketHandler::Start(SockAddr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles)
{
return SetupUnboundResolver(std::move(resolvers), std::move(hostfiles));
}
std::optional<SockAddr>
BoundOn() const override
{
return m_udp->LocalAddr();
}
bool
PacketHandler::SetupUnboundResolver(
std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles)
{
// if we have no resolvers don't set up unbound
if (resolvers.empty())
return true;
auto failFunc = [self = weak_from_this()](
const SockAddr& to, const SockAddr& from, Message msg) {
if (auto this_ptr = self.lock())
this_ptr->SendServerMessageBufferTo(to, from, msg.ToBuffer());
};
bool
WouldLoop(const SockAddr& to, const SockAddr&) const override
{
return to != m_LocalAddr;
}
auto replyFunc = [self = weak_from_this()](auto&&... args) {
if (auto this_ptr = self.lock())
this_ptr->SendServerMessageBufferTo(std::forward<decltype(args)>(args)...);
};
void
SendTo(const SockAddr& to, const SockAddr&, llarp::OwnedBuffer buf) const override
{
m_udp->send(to, std::move(buf));
}
m_UnboundResolver =
std::make_shared<UnboundResolver>(m_Loop, std::move(replyFunc), std::move(failFunc));
m_Resolvers.clear();
if (not m_UnboundResolver->Init())
void
Stop() override
{
llarp::LogError("Failed to initialize upstream DNS resolver.");
m_UnboundResolver = nullptr;
return false;
m_udp->close();
}
for (const auto& resolver : resolvers)
};
namespace libunbound
{
class Resolver;
class Query : public QueryJob_Base
{
std::weak_ptr<Resolver> parent;
std::weak_ptr<PacketSource_Base> src;
SockAddr resolverAddr;
SockAddr askerAddr;
public:
explicit Query(
std::weak_ptr<Resolver> parent_,
Message query,
std::weak_ptr<PacketSource_Base> pktsrc,
SockAddr toaddr,
SockAddr fromaddr)
: QueryJob_Base{std::move(query)}
, parent{parent_}
, src{pktsrc}
, resolverAddr{std::move(toaddr)}
, askerAddr{std::move(fromaddr)}
{}
virtual void
SendReply(llarp::OwnedBuffer replyBuf) const override;
};
/// Resolver_Base that uses libunbound
class Resolver : public Resolver_Base, public std::enable_shared_from_this<Resolver>
{
if (not m_UnboundResolver->AddUpstreamResolver(resolver))
std::shared_ptr<ub_ctx> m_ctx;
std::weak_ptr<EventLoop> m_Loop;
#ifdef _WIN32
// windows is dumb so we do ub mainloop in a thread
std::thread runner;
std::atomic<bool> running;
#else
std::shared_ptr<uvw::PollHandle> m_Poller;
#endif
struct ub_result_deleter
{
void
operator()(ub_result* ptr)
{
::ub_resolve_free(ptr);
}
};
static void
Callback(void* data, int err, ub_result* _result)
{
// take ownership of ub_result
std::unique_ptr<ub_result, ub_result_deleter> result{_result};
// take ownership of our query
std::unique_ptr<Query> query{static_cast<Query*>(data)};
if (err)
{
// some kind of error from upstream
query->Cancel();
return;
}
// rewrite response
OwnedBuffer pkt{(const byte_t*)result->answer_packet, (size_t)result->answer_len};
llarp_buffer_t buf{pkt};
MessageHeader hdr;
hdr.Decode(&buf);
hdr.id = query->Underlying().hdr_id;
buf.cur = buf.base;
hdr.Encode(&buf);
// send reply
query->SendReply(std::move(pkt));
}
void
SetOpt(std::string key, std::string val)
{
ub_ctx_set_option(m_ctx.get(), key.c_str(), val.c_str());
}
llarp::DnsConfig m_conf;
public:
explicit Resolver(const EventLoop_ptr& loop, llarp::DnsConfig conf)
: m_ctx{::ub_ctx_create(), ::ub_ctx_delete}, m_Loop{loop}, m_conf{std::move(conf)}
{
Up(m_conf);
}
#ifdef _WIN32
virtual ~Resolver()
{
running = false;
runner.join();
}
#else
virtual ~Resolver() = default;
#endif
std::string_view
ResolverName() const override
{
return "unbound";
}
void
Up(const llarp::DnsConfig& conf)
{
// set libunbound settings
for (const auto& [k, v] : conf.m_ExtraOpts)
SetOpt(k, v);
// add host files
for (const auto& file : conf.m_hostfiles)
{
const auto str = file.u8string();
if (auto ret = ub_ctx_hosts(m_ctx.get(), str.c_str()))
{
throw std::runtime_error{
fmt::format("Failed to add host file {}: {}", file, ub_strerror(ret))};
}
}
// set up forward dns
for (const auto& dns : conf.m_upstreamDNS)
{
std::stringstream ss;
auto hoststr = dns.hostString();
ss << hoststr;
if (const auto port = dns.getPort(); port != 53)
ss << "@" << port;
const auto str = ss.str();
if (auto err = ub_ctx_set_fwd(m_ctx.get(), str.c_str()))
{
throw std::runtime_error{
fmt::format("cannot use {} as upstream dns: {}", str, ub_strerror(err))};
}
#ifdef __APPLE__
// On Apple, we configure a localhost resolver to trampoline requests through the tunnel
// to the actual upstream (because the network extension itself cannot route through the
// tunnel using normal sockets but instead we "get" to use Apple's interfaces, hurray).
if (hoststr == "127.0.0.1")
{
// 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.
SetOpt("outgoing-interface:", hoststr.c_str());
// The trampoline expects just a single source port (and sends everything back to it)
SetOpt("outgoing-range:", "1");
SetOpt("outgoing-port-avoid:", "0-65535");
SetOpt("outgoing-port-permit:", "1253");
}
#endif
}
// set async
ub_ctx_async(m_ctx.get(), 1);
// setup mainloop
#ifdef _WIN32
running = true;
runner = std::thread{[this]() {
while (running)
{
if (m_ctx.get())
ub_wait(m_ctx.get());
std::this_thread::sleep_for(25ms);
}
if (m_ctx.get())
ub_process(m_ctx.get());
}};
#else
if (auto loop = m_Loop.lock())
{
if (auto loop_ptr = loop->MaybeGetUVWLoop())
{
m_Poller = loop_ptr->resource<uvw::PollHandle>(ub_fd(m_ctx.get()));
m_Poller->on<uvw::PollEvent>([ptr = std::weak_ptr<ub_ctx>{m_ctx}](auto&, auto&) {
if (auto ctx = ptr.lock())
ub_process(ctx.get());
});
m_Poller->start(uvw::PollHandle::Event::READABLE);
return;
}
}
throw std::runtime_error{"no uvw loop"};
#endif
}
void
Down()
{
#ifdef _WIN32
running = false;
runner.join();
#else
m_Poller->close();
if (auto loop = m_Loop.lock())
{
if (auto loop_ptr = loop->MaybeGetUVWLoop())
{
m_Poller = loop_ptr->resource<uvw::PollHandle>(ub_fd(m_ctx.get()));
m_Poller->on<uvw::PollEvent>([ptr = std::weak_ptr<ub_ctx>{m_ctx}](auto&, auto&) {
if (auto ctx = ptr.lock())
ub_process(ctx.get());
});
m_Poller->start(uvw::PollHandle::Event::READABLE);
}
}
#endif
m_ctx.reset();
}
int
Rank() const override
{
return 10;
}
void
ResetInternalState() override
{
llarp::LogError("Failed to add upstream DNS server: ", resolver);
m_UnboundResolver = nullptr;
Down();
Up(m_conf);
}
void
CancelPendingQueries() override
{
Down();
}
bool
WouldLoop(const SockAddr& to, const SockAddr& from) const override
{
#if defined(ANDROID)
(void)to;
(void)from;
return false;
#else
const auto& vec = m_conf.m_upstreamDNS;
return std::find(vec.begin(), vec.end(), to) != std::end(vec)
or std::find(vec.begin(), vec.end(), from) != std::end(vec);
#endif
}
template <typename Callable>
void
call(Callable&& f)
{
if (auto loop = m_Loop.lock())
loop->call(std::forward<Callable>(f));
else
LogError("no mainloop?");
}
bool
MaybeHookDNS(
std::weak_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to,
const SockAddr& from) override
{
if (WouldLoop(to, from))
return false;
// we use this unique ptr to clean up on fail
auto tmp = std::make_unique<Query>(weak_from_this(), query, source, to, from);
// no questions, send fail
if (query.questions.empty())
{
tmp->Cancel();
return true;
}
for (const auto& q : query.questions)
{
// dont process .loki or .snode
if (q.HasTLD(".loki") or q.HasTLD(".snode"))
{
tmp->Cancel();
return true;
}
}
// leak bare pointer and try to do the request
auto* pending = tmp.release();
const auto& q = query.questions[0];
if (auto err = ub_resolve_async(
m_ctx.get(),
q.Name().c_str(),
q.qtype,
q.qclass,
(void*)pending,
&Resolver::Callback,
nullptr))
{
// take back ownership on fail
LogWarn("failed to send upstream query with libunbound: ", ub_strerror(err));
tmp.reset(pending);
tmp->Cancel();
}
return true;
}
m_Resolvers.emplace(resolver);
};
void
Query::SendReply(llarp::OwnedBuffer replyBuf) const
{
auto packet_src = src.lock();
auto parent_ptr = parent.lock();
if (packet_src and parent_ptr)
{
parent_ptr->call([packet_src, from = resolverAddr, to = askerAddr, buf = replyBuf.copy()] {
packet_src->SendTo(to, from, OwnedBuffer::copy_from(buf));
});
}
else
LogError("no source or parent");
}
for (const auto& path : hostfiles)
} // namespace libunbound
Server::Server(EventLoop_ptr loop, llarp::DnsConfig conf)
: m_Loop{std::move(loop)}, m_Config{std::move(conf)}
{}
void
Server::Start()
{
// set up udp sockets
for (const auto& addr : m_Config.m_bind)
{
m_UnboundResolver->AddHostsFile(path);
if (auto ptr = MakePacketSourceOn(addr, m_Config))
AddPacketSource(std::move(ptr));
}
return true;
// add default resolver as needed
if (auto ptr = MakeDefaultResolver())
AddResolver(ptr);
}
void
Proxy::SendServerMessageBufferTo(
const SockAddr& to, [[maybe_unused]] const SockAddr& from, llarp_buffer_t buf)
std::shared_ptr<PacketSource_Base>
Server::MakePacketSourceOn(const llarp::SockAddr& addr, const llarp::DnsConfig&)
{
if (!m_Server->send(to, buf))
llarp::LogError("dns reply failed");
return std::make_shared<UDPReader>(*this, m_Loop, addr);
}
bool
PacketHandler::IsUpstreamResolver(const SockAddr& to, [[maybe_unused]] const SockAddr& from) const
std::shared_ptr<Resolver_Base>
Server::MakeDefaultResolver()
{
return m_Resolvers.count(to);
if (m_Config.m_upstreamDNS.empty())
{
LogInfo(
"explicitly no upstream dns providers specified, we will not resolve anything but .loki "
"and .snode");
return nullptr;
}
return std::make_shared<libunbound::Resolver>(m_Loop, m_Config);
}
bool
PacketHandler::ShouldHandlePacket(
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const
std::vector<SockAddr>
Server::BoundPacketSourceAddrs() const
{
MessageHeader hdr;
if (not hdr.Decode(&buf))
std::vector<SockAddr> addrs;
for (const auto& src : m_PacketSources)
{
return false;
if (auto ptr = src.lock())
if (auto maybe_addr = ptr->BoundOn())
addrs.emplace_back(*maybe_addr);
}
return addrs;
}
Message msg{hdr};
if (not msg.Decode(&buf))
std::optional<SockAddr>
Server::FirstBoundPacketSourceAddr() const
{
for (const auto& src : m_PacketSources)
{
return false;
if (auto ptr = src.lock())
if (auto bound = ptr->BoundOn())
return bound;
}
return std::nullopt;
}
void
Server::AddResolver(std::weak_ptr<Resolver_Base> resolver)
{
m_Resolvers.insert(resolver);
}
void
Server::AddResolver(std::shared_ptr<Resolver_Base> resolver)
{
m_OwnedResolvers.insert(resolver);
AddResolver(std::weak_ptr<Resolver_Base>{resolver});
}
if (m_QueryHandler and m_QueryHandler->ShouldHookDNSMessage(msg))
return true;
// If this request is going to an upstream resolver then we want to let it through (i.e. don't
// handle it), and so want to return false. If we have something else then we want to
// intercept it to route it through our caching libunbound server (which then redirects the
// request to the lokinet-configured upstream, if not cached).
return !IsUpstreamResolver(to, from);
void
Server::AddPacketSource(std::weak_ptr<PacketSource_Base> pkt)
{
m_PacketSources.push_back(pkt);
}
void
PacketHandler::HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf)
Server::AddPacketSource(std::shared_ptr<PacketSource_Base> pkt)
{
MessageHeader hdr;
if (not hdr.Decode(&buf))
m_OwnedPacketSources.push_back(pkt);
AddPacketSource(std::weak_ptr<PacketSource_Base>{pkt});
}
void
Server::Stop()
{
for (const auto& resolver : m_Resolvers)
{
llarp::LogWarn("failed to parse dns header from ", from);
return;
if (auto ptr = resolver.lock())
ptr->CancelPendingQueries();
}
}
Message msg(hdr);
if (not msg.Decode(&buf))
void
Server::Reset()
{
for (const auto& resolver : m_Resolvers)
{
llarp::LogWarn("failed to parse dns message from ", from);
return;
if (auto ptr = resolver.lock())
ptr->ResetInternalState();
}
}
bool
Server::MaybeHandlePacket(
std::weak_ptr<PacketSource_Base> src,
const SockAddr& to,
const SockAddr& from,
llarp::OwnedBuffer buf)
{
auto ptr = src.lock();
if (not ptr)
return false;
// dont process to prevent feedback loop
if (ptr->WouldLoop(to, from))
{
LogWarn("preventing dns packet replay to=", to, " from=", from);
return false;
}
auto maybe = MaybeParseDNSMessage(buf);
if (not maybe)
{
LogWarn("invalid dns message format from ", from, " to dns listener on ", to);
return false;
}
auto& msg = *maybe;
// we don't provide a DoH resolver because it requires verified TLS
// TLS needs X509/ASN.1-DER and opting into the Root CA Cabal
// thankfully mozilla added a backdoor that allows ISPs to turn it off
// so we disable DoH for firefox using mozilla's ISP backdoor
// see: https://github.com/loki-project/loki-network/issues/832
// see: https://github.com/oxen-io/lokinet/issues/832
for (const auto& q : msg.questions)
{
// is this firefox looking for their backdoor record?
@ -163,32 +544,22 @@ namespace llarp::dns
{
// yea it is, let's turn off DoH because god is dead.
msg.AddNXReply();
// press F to pay respects
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
return;
// press F to pay respects and send it back where it came from
ptr->SendTo(from, to, msg.ToBuffer());
return true;
}
}
if (m_QueryHandler && m_QueryHandler->ShouldHookDNSMessage(msg))
for (const auto& resolver : m_Resolvers)
{
auto reply = [self = shared_from_this(), to = from, resolver](dns::Message msg) {
self->SendServerMessageBufferTo(to, resolver, msg.ToBuffer());
};
if (!m_QueryHandler->HandleHookedDNSMessage(std::move(msg), reply))
if (auto res_ptr = resolver.lock())
{
llarp::LogWarn("failed to handle hooked dns");
LogDebug("check resolver ", res_ptr->ResolverName(), " for dns from ", from, " to ", to);
if (res_ptr->MaybeHookDNS(src, msg, to, from))
return true;
}
}
else if (not m_UnboundResolver)
{
// no upstream resolvers
// let's serv fail it
msg.AddServFail();
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
}
else
{
m_UnboundResolver->Lookup(resolver, from, std::move(msg));
}
return false;
}
} // namespace llarp::dns

@ -1,99 +1,226 @@
#pragma once
#include "message.hpp"
#include <llarp/config/config.hpp>
#include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp>
#include "unbound_resolver.hpp"
#include <llarp/util/fs.hpp>
#include <set>
#include <unordered_map>
namespace llarp
namespace llarp::dns
{
namespace dns
/// a job handling 1 dns query
class QueryJob_Base
{
/// handler of dns query hooking
class IQueryHandler
{
public:
virtual ~IQueryHandler() = default;
protected:
/// the original dns query
Message m_Query;
/// return true if we should hook this message
virtual bool
ShouldHookDNSMessage(const Message& msg) const = 0;
public:
explicit QueryJob_Base(Message query) : m_Query{std::move(query)}
{}
/// handle a hooked message
virtual bool
HandleHookedDNSMessage(Message query, std::function<void(Message)> sendReply) = 0;
};
virtual ~QueryJob_Base() = default;
// Base class for DNS lookups
class PacketHandler : public std::enable_shared_from_this<PacketHandler>
Message&
Underlying()
{
public:
explicit PacketHandler(EventLoop_ptr loop, IQueryHandler* handler);
virtual ~PacketHandler() = default;
virtual bool
Start(
SockAddr localaddr,
std::vector<SockAddr> upstreamResolvers,
std::vector<fs::path> hostfiles);
void
Stop();
void
Restart();
void
HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf);
bool
ShouldHandlePacket(const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const;
protected:
virtual void
SendServerMessageBufferTo(const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) = 0;
return m_Query;
}
// Returns true if this packet is something that looks like it's going to an upstream
// resolver, i.e. matches a configured resolver.
virtual bool
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const;
const Message&
Underlying() const
{
return m_Query;
}
private:
void
HandleUpstreamFailure(const SockAddr& from, const SockAddr& to, Message msg);
/// cancel this operation and inform anyone who cares
void
Cancel() const;
bool
SetupUnboundResolver(std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles);
/// send a raw buffer back to the querier
virtual void
SendReply(llarp::OwnedBuffer replyBuf) const = 0;
};
IQueryHandler* const m_QueryHandler;
std::set<SockAddr> m_Resolvers;
std::shared_ptr<UnboundResolver> m_UnboundResolver;
EventLoop_ptr m_Loop;
};
class PacketSource_Base
{
public:
virtual ~PacketSource_Base() = default;
/// return true if traffic with source and dest addresses would cause a
/// loop in resolution and thus should not be sent to query handlers
virtual bool
WouldLoop(const SockAddr& to, const SockAddr& from) const = 0;
/// send packet with src and dst address containing buf on this packet source
virtual void
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const = 0;
/// stop reading packets and end operation
virtual void
Stop() = 0;
/// returns the sockaddr we are bound on if applicable
virtual std::optional<SockAddr>
BoundOn() const = 0;
};
/// non complex implementation of QueryJob_Base for use in things that
/// only ever called on the mainloop thread
class QueryJob : public QueryJob_Base, std::enable_shared_from_this<QueryJob>
{
std::weak_ptr<PacketSource_Base> src;
const SockAddr resolver;
const SockAddr asker;
public:
explicit QueryJob(
std::weak_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to_,
const SockAddr& from_)
: QueryJob_Base{query}, src{source}, resolver{to_}, asker{from_}
{}
void
SendReply(llarp::OwnedBuffer replyBuf) const override
{
if (auto ptr = src.lock())
ptr->SendTo(asker, resolver, std::move(replyBuf));
}
};
/// handler of dns query hooking
/// intercepts dns for internal processing
class Resolver_Base
{
protected:
/// return the sorting order for this resolver
/// lower means it will be tried first
virtual int
Rank() const = 0;
public:
virtual ~Resolver_Base() = default;
/// less than via rank
bool
operator<(const Resolver_Base& other) const
{
return Rank() < other.Rank();
}
// Proxying DNS handler that listens on a UDP port for proper DNS requests.
class Proxy : public PacketHandler
/// greater than via rank
bool
operator>(const Resolver_Base& other) const
{
return Rank() > other.Rank();
}
/// get printable name
virtual std::string_view
ResolverName() const = 0;
/// reset state
virtual void
ResetInternalState(){};
/// cancel all pending requests and ceace further operation
virtual void
CancelPendingQueries(){};
/// attempt to handle a dns message
/// returns true if we consumed this query and it should not be processed again
virtual bool
MaybeHookDNS(
std::weak_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to,
const SockAddr& from) = 0;
/// Returns true if a packet with to and from addresses is something that would cause a
/// resolution loop and thus should not be used on this resolver
virtual bool
WouldLoop(const SockAddr& to, const SockAddr& from) const
{
public:
explicit Proxy(EventLoop_ptr loop, IQueryHandler* handler);
bool
Start(
SockAddr localaddr,
std::vector<SockAddr> upstreamResolvers,
std::vector<fs::path> hostfiles) override;
protected:
void
SendServerMessageBufferTo(
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override;
private:
std::shared_ptr<UDPHandle> m_Server;
EventLoop_ptr m_Loop;
(void)to;
(void)from;
return false;
};
} // namespace dns
} // namespace llarp
};
// Base class for DNS proxy
class Server : public std::enable_shared_from_this<Server>
{
protected:
/// add a packet source to this server, does share ownership
void
AddPacketSource(std::shared_ptr<PacketSource_Base> resolver);
/// add a resolver to this packet handler, does share ownership
void
AddResolver(std::shared_ptr<Resolver_Base> resolver);
public:
virtual ~Server() = default;
explicit Server(EventLoop_ptr loop, llarp::DnsConfig conf);
/// returns all sockaddr we have from all of our PacketSources
std::vector<SockAddr>
BoundPacketSourceAddrs() const;
/// returns the first sockaddr we have on our packet sources if we have one
std::optional<SockAddr>
FirstBoundPacketSourceAddr() const;
/// add a resolver to this packet handler, does not share ownership
void
AddResolver(std::weak_ptr<Resolver_Base> resolver);
/// add a packet source to this server, does not share ownership
void
AddPacketSource(std::weak_ptr<PacketSource_Base> resolver);
/// create a packet source bound on bindaddr but does not add it
virtual std::shared_ptr<PacketSource_Base>
MakePacketSourceOn(const SockAddr& bindaddr, const llarp::DnsConfig& conf);
/// sets up all internal binds and such and begins operation
virtual void
Start();
/// stops all operation
virtual void
Stop();
/// reset the internal state
virtual void
Reset();
/// create the default resolver for out config
virtual std::shared_ptr<Resolver_Base>
MakeDefaultResolver();
/// feed a packet buffer from a packet source
/// returns true if we decided to process the packet and consumed it
/// returns false if we dont want to process the packet
bool
MaybeHandlePacket(
std::weak_ptr<PacketSource_Base> pktsource,
const SockAddr& resolver,
const SockAddr& from,
llarp::OwnedBuffer buf);
protected:
EventLoop_ptr m_Loop;
llarp::DnsConfig m_Config;
private:
std::set<std::shared_ptr<Resolver_Base>, ComparePtr<std::shared_ptr<Resolver_Base>>>
m_OwnedResolvers;
std::set<std::weak_ptr<Resolver_Base>, CompareWeakPtr<Resolver_Base>> m_Resolvers;
std::vector<std::weak_ptr<PacketSource_Base>> m_PacketSources;
std::vector<std::shared_ptr<PacketSource_Base>> m_OwnedPacketSources;
};
} // namespace llarp::dns

@ -23,6 +23,11 @@ namespace llarp
class TunnelManager;
}
namespace dns
{
class Server;
}
class EndpointBase
{
std::unordered_set<dns::SRVData> m_SRVRecords;
@ -72,6 +77,13 @@ namespace llarp
void
PutSRVRecord(dns::SRVData srv);
/// get dns serverr if we have on on this endpoint
virtual std::shared_ptr<dns::Server>
DNS() const
{
return nullptr;
};
/// called when srv data changes in some way
virtual void
SRVRecordsChanged() = 0;

@ -72,6 +72,13 @@ namespace llarp::uv
bool
send(const SockAddr& dest, const llarp_buffer_t& buf) override;
std::optional<SockAddr>
LocalAddr() const override
{
auto addr = handle->sock<uvw::IPv4>();
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
}
std::optional<int>
file_descriptor() override
{

@ -33,6 +33,10 @@ namespace llarp
return std::nullopt;
}
/// returns the local address we are bound on
virtual std::optional<SockAddr>
LocalAddr() const = 0;
// Base class destructor
virtual ~UDPHandle() = default;

@ -18,11 +18,7 @@ namespace llarp
namespace handlers
{
ExitEndpoint::ExitEndpoint(std::string name, AbstractRouter* r)
: m_Router(r)
, m_Resolver(std::make_shared<dns::Proxy>(r->loop(), this))
, m_Name(std::move(name))
, m_LocalResolverAddr{"127.0.0.1:53"}
, m_QUIC{std::make_shared<quic::TunnelManager>(*this)}
: m_Router(r), m_Name(std::move(name)), m_QUIC{std::make_shared<quic::TunnelManager>(*this)}
{
m_ShouldInitTun = true;
m_QUIC = std::make_shared<quic::TunnelManager>(*this);
@ -211,6 +207,22 @@ namespace llarp
return false;
}
bool
ExitEndpoint::MaybeHookDNS(
std::weak_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from)
{
if (not ShouldHookDNSMessage(query))
return false;
auto job = std::make_shared<dns::QueryJob>(source, query, to, from);
if (not HandleHookedDNSMessage(query, [job](auto msg) { job->SendReply(msg.ToBuffer()); }))
job->Cancel();
return true;
}
bool
ExitEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)> reply)
{
@ -459,9 +471,7 @@ namespace llarp
}
GetRouter()->loop()->add_ticker([this] { Flush(); });
llarp::LogInfo("Trying to start resolver ", m_LocalResolverAddr);
return m_Resolver->Start(m_LocalResolverAddr, m_UpstreamResolvers, {});
m_Resolver->Start();
}
return true;
}
@ -703,8 +713,7 @@ namespace llarp
m_ShouldInitTun = false;
}
m_LocalResolverAddr = dnsConfig.m_bind;
m_UpstreamResolvers = dnsConfig.m_upstreamDNS;
m_Resolver = std::make_shared<dns::Server>(m_Router->loop(), dnsConfig);
m_OurRange = networkConfig.m_ifaddr;
if (!m_OurRange.addr.h)

@ -10,8 +10,33 @@ namespace llarp
struct AbstractRouter;
namespace handlers
{
struct ExitEndpoint : public dns::IQueryHandler, public EndpointBase
struct ExitEndpoint : public dns::Resolver_Base, public EndpointBase
{
int
Rank() const override
{
return 0;
};
std::string_view
ResolverName() const override
{
return "snode";
}
void
ResetInternalState() override{};
void
CancelPendingQueries() override{};
bool
MaybeHookDNS(
std::weak_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from) override;
ExitEndpoint(std::string name, AbstractRouter* r);
~ExitEndpoint() override;
@ -66,10 +91,10 @@ namespace llarp
SupportsV6() const;
bool
ShouldHookDNSMessage(const dns::Message& msg) const override;
ShouldHookDNSMessage(const dns::Message& msg) const;
bool
HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>) override;
HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>);
void
LookupServiceAsync(
@ -174,7 +199,7 @@ namespace llarp
KickIdentOffExit(const PubKey& pk);
AbstractRouter* m_Router;
std::shared_ptr<dns::Proxy> m_Resolver;
std::shared_ptr<dns::Server> m_Resolver;
bool m_ShouldInitTun;
std::string m_Name;
bool m_PermitExit;

@ -12,7 +12,7 @@
#include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/router/systemd_resolved.hpp>
#include <llarp/router/route_poker.hpp>
#include <llarp/service/context.hpp>
#include <llarp/service/outbound_context.hpp>
#include <llarp/service/endpoint_state.hpp>
@ -27,26 +27,46 @@
#include <llarp/dns/srv_data.hpp>
#include <llarp/constants/platform.hpp>
#include <llarp/constants/platform.hpp>
#include <oxenc/bt.h>
namespace llarp
{
namespace handlers
{
bool
TunEndpoint::MaybeHookDNS(
std::weak_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from)
{
if (not ShouldHookDNSMessage(query))
return false;
auto job = std::make_shared<dns::QueryJob>(source, query, to, from);
if (not HandleHookedDNSMessage(query, [job](auto msg) { job->SendReply(msg.ToBuffer()); }))
job->Cancel();
return true;
}
// Intercepts DNS IP packets going to an IP on the tun interface; this is currently used on
// Android and macOS where binding to a DNS port (i.e. via llarp::dns::Proxy) isn't possible
// Android and macOS where binding to a low port isn't possible
// because of OS restrictions, but a tun interface *is* available.
class DnsInterceptor : public dns::PacketHandler
class DnsInterceptor : public dns::PacketSource_Base
{
public:
TunEndpoint* const m_Endpoint;
llarp::DnsConfig m_Config;
explicit DnsInterceptor(AbstractRouter* router, TunEndpoint* ep)
: dns::PacketHandler{router->loop(), ep}, m_Endpoint{ep} {};
explicit DnsInterceptor(TunEndpoint* ep, llarp::DnsConfig conf)
: m_Endpoint{ep}, m_Config{conf}
{}
virtual ~DnsInterceptor() = default;
void
SendServerMessageBufferTo(
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
{
const auto pkt = net::IPPacket::UDP(
from.getIPv4(),
@ -61,11 +81,20 @@ namespace llarp
pkt.ConstBuffer(), net::ExpandV4(from.asIPv4()), net::ExpandV4(to.asIPv4()), 0);
}
void
Stop() override{};
std::optional<SockAddr>
BoundOn() const override
{
return std::nullopt;
}
#ifdef ANDROID
bool
IsUpstreamResolver(const SockAddr&, const SockAddr&) const override
WouldLoop(const SockAddr&, const SockAddr&) const override
{
return true;
return false;
}
#endif
@ -77,21 +106,57 @@ namespace llarp
// IP for DNS, so we consider anything else to be upstream-bound DNS to let it through the
// tunnel.
bool
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const override
WouldLoop(const SockAddr& to, const SockAddr&) const override
{
return to.asIPv6() != m_Endpoint->GetIfAddr();
}
#endif
};
#if defined(ANDROID) || defined(__APPLE__)
class TunDNS : public dns::Server
{
TunEndpoint* const m_Endpoint;
public:
std::weak_ptr<dns::PacketSource_Base> PacketSource;
virtual ~TunDNS() = default;
explicit TunDNS(TunEndpoint* ep, const llarp::DnsConfig& conf)
: dns::Server{ep->Router()->loop(), conf}, m_Endpoint{ep}
{}
std::shared_ptr<dns::PacketSource_Base>
MakePacketSourceOn(const SockAddr&, const llarp::DnsConfig& conf) override
{
auto ptr = std::make_shared<DnsInterceptor>(m_Endpoint, conf);
PacketSource = ptr;
return ptr;
}
std::shared_ptr<dns::Resolver_Base>
MakeDefaultResolver() override
{
// android will not cache dns via unbound it only intercepts .loki
return nullptr;
}
};
#endif
TunEndpoint::TunEndpoint(AbstractRouter* r, service::Context* parent)
: service::Endpoint(r, parent)
{
m_PacketRouter = std::make_unique<vpn::PacketRouter>(
[this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); });
#if defined(ANDROID) || (defined(__APPLE__) && !defined(MACOS_SYSTEM_EXTENSION))
m_Resolver = std::make_shared<DnsInterceptor>(r, this);
m_PacketRouter->AddUDPHandler(huint16_t{53}, [&](net::IPPacket pkt) {
}
void
TunEndpoint::SetupDNS()
{
#if defined(ANDROID) || defined(__APPLE__) && !defined(MACOS_SYSTEM_EXTENSION))
auto dns = std::make_shared<TunDNS>(this, m_DnsConfig);
m_DNS = dns;
m_PacketRouter->AddUDPHandler(huint16_t{53}, [this, dns](net::IPPacket pkt) {
const size_t ip_header_size = (pkt.Header()->ihl * 4);
const uint8_t* ptr = pkt.buf + ip_header_size;
@ -102,14 +167,17 @@ namespace llarp
OwnedBuffer buf{pkt.sz - (8 + ip_header_size)};
std::copy_n(ptr + 8, buf.sz, buf.buf.get());
if (m_Resolver->ShouldHandlePacket(raddr, laddr, buf))
m_Resolver->HandlePacket(raddr, laddr, buf);
else
HandleGotUserPacket(std::move(pkt));
if (dns->MaybeHandlePacket(dns->PacketSource, raddr, laddr, std::move(buf)))
return;
HandleGotUserPacket(std::move(pkt));
});
#else
m_Resolver = std::make_shared<dns::Proxy>(r->loop(), this);
m_DNS = std::make_shared<dns::Server>(Loop(), m_DnsConfig);
#endif
m_DNS->AddResolver(weak_from_this());
m_DNS->Start();
}
util::StatusObject
@ -118,11 +186,21 @@ namespace llarp
auto obj = service::Endpoint::ExtractStatus();
obj["ifaddr"] = m_OurRange.ToString();
obj["ifname"] = m_IfName;
std::vector<std::string> resolvers;
for (const auto& addr : m_UpstreamResolvers)
resolvers.emplace_back(addr.ToString());
obj["ustreamResolvers"] = resolvers;
obj["localResolver"] = m_LocalResolverAddr.ToString();
std::vector<std::string> upstreamRes;
for (const auto& ent : m_DnsConfig.m_upstreamDNS)
upstreamRes.emplace_back(ent.ToString());
obj["ustreamResolvers"] = upstreamRes;
std::vector<std::string> localRes;
for (const auto& ent : m_DnsConfig.m_bind)
localRes.emplace_back(ent.ToString());
obj["localResolvers"] = localRes;
// for backwards compat
if (not m_DnsConfig.m_bind.empty())
obj["localResolver"] = localRes[0];
util::StatusObject ips{};
for (const auto& item : m_IPActivity)
{
@ -147,18 +225,14 @@ namespace llarp
void
TunEndpoint::Thaw()
{
if (m_Resolver)
m_Resolver->Restart();
if (m_DNS)
m_DNS->Reset();
}
std::vector<SockAddr>
TunEndpoint::ReconfigureDNS(std::vector<SockAddr> servers)
{
std::swap(m_UpstreamResolvers, servers);
m_Resolver->Stop();
if (!m_Resolver->Start(
m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers, m_hostfiles))
llarp::LogError(Name(), " failed to reconfigure DNS server");
// TODO: implement me
return servers;
}
@ -199,13 +273,10 @@ namespace llarp
m_AuthPolicy = std::move(auth);
}
m_DnsConfig = dnsConf;
m_TrafficPolicy = conf.m_TrafficPolicy;
m_OwnedRanges = conf.m_OwnedRanges;
m_LocalResolverAddr = dnsConf.m_bind;
m_UpstreamResolvers = dnsConf.m_upstreamDNS;
m_hostfiles = dnsConf.m_hostfiles;
m_BaseV6Address = conf.m_baseV6Address;
if (conf.m_PathAlignmentTimeout)
@ -354,7 +425,6 @@ namespace llarp
return llarp::SockAddr{net::TruncateV6(GetIfAddr()), huint16_t{port}};
});
}
return Endpoint::Configure(conf, dnsConf);
}
@ -862,11 +932,8 @@ namespace llarp
bool
TunEndpoint::Start()
{
if (!Endpoint::Start())
{
llarp::LogWarn("Couldn't start endpoint");
if (not Endpoint::Start())
return false;
}
return SetupNetworking();
}
@ -904,7 +971,12 @@ namespace llarp
}
info.ifname = m_IfName;
info.dnsaddr.FromString(m_LocalResolverAddr.toHost());
LogInfo(Name(), " setting up dns...");
SetupDNS();
if (auto maybe_addr = m_DNS->FirstBoundPacketSourceAddr())
info.dnsaddr = maybe_addr->asIPv4();
LogInfo(Name(), " setting up network...");
@ -931,23 +1003,20 @@ namespace llarp
LogError(Name(), " failed to add network interface");
return false;
}
#ifdef __APPLE__
m_OurIPv6 = llarp::huint128_t{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}};
#else
const auto maybe = m_router->Net().GetInterfaceIPv6Address(m_IfName);
if (maybe.has_value())
if constexpr (not llarp::platform::is_apple)
{
m_OurIPv6 = *maybe;
LogInfo(Name(), " has ipv6 address ", m_OurIPv6);
if (auto maybe = m_router->Net().GetInterfaceIPv6Address(m_IfName))
{
m_OurIPv6 = *maybe;
LogInfo(Name(), " has ipv6 address ", m_OurIPv6);
}
}
#endif
// Attempt to register DNS on the interface
systemd_resolved_set_dns(
m_IfName,
m_LocalResolverAddr.createSockAddr(),
false /* just .loki/.snode DNS initially */);
m_router->routePoker().SetDNSMode(false);
return HasAddress(ourAddr);
}
@ -970,18 +1039,7 @@ namespace llarp
TunEndpoint::SetupNetworking()
{
llarp::LogInfo("Set Up networking for ", Name());
if (!SetupTun())
{
llarp::LogError(Name(), " failed to set up network interface");
return false;
}
if (!m_Resolver->Start(
m_LocalResolverAddr.createSockAddr(), m_UpstreamResolvers, m_hostfiles))
{
llarp::LogError(Name(), " failed to start DNS server");
return false;
}
return true;
return SetupTun();
}
void
@ -1016,8 +1074,8 @@ namespace llarp
}
}
#endif
if (m_Resolver)
m_Resolver->Stop();
if (m_DNS)
m_DNS->Stop();
return llarp::service::Endpoint::Stop();
}

@ -23,12 +23,31 @@ namespace llarp
namespace handlers
{
struct TunEndpoint : public service::Endpoint,
public dns::IQueryHandler,
public dns::Resolver_Base,
public std::enable_shared_from_this<TunEndpoint>
{
TunEndpoint(AbstractRouter* r, llarp::service::Context* parent);
~TunEndpoint() override;
int
Rank() const override
{
return 0;
}
std::string_view
ResolverName() const override
{
return "lokinet";
}
bool
MaybeHookDNS(
std::weak_ptr<dns::PacketSource_Base> source,
const dns::Message& query,
const SockAddr& to,
const SockAddr& from) override;
path::PathSet_ptr
GetSelf() override
{
@ -71,11 +90,10 @@ namespace llarp
SupportsV6() const override;
bool
ShouldHookDNSMessage(const dns::Message& msg) const override;
ShouldHookDNSMessage(const dns::Message& msg) const;
bool
HandleHookedDNSMessage(
dns::Message query, std::function<void(dns::Message)> sendreply) override;
HandleHookedDNSMessage(dns::Message query, std::function<void(dns::Message)> sendreply);
void
TickTun(llarp_time_t now);
@ -96,6 +114,16 @@ namespace llarp
bool
SetupTun();
void
SetupDNS();
/// overrides Endpoint
std::shared_ptr<dns::Server>
DNS() const override
{
return m_DNS;
};
/// overrides Endpoint
bool
SetupNetworking() override;
@ -249,8 +277,11 @@ namespace llarp
query->AddNXReply();
reply(*query);
}
/// our dns resolver
std::shared_ptr<dns::PacketHandler> m_Resolver;
/// dns subsystem for this endpoint
std::shared_ptr<dns::Server> m_DNS;
DnsConfig m_DnsConfig;
/// maps ip address to timestamp last active
std::unordered_map<huint128_t, llarp_time_t> m_IPActivity;
@ -265,12 +296,6 @@ namespace llarp
huint128_t m_MaxIP;
/// our ip range we are using
llarp::IPRange m_OurRange;
/// upstream dns resolver list
std::vector<SockAddr> m_UpstreamResolvers;
/// dns host files list
std::vector<fs::path> m_hostfiles;
/// local dns
IpAddress m_LocalResolverAddr;
/// list of strict connect addresses for hooks
std::vector<IpAddress> m_StrictConnectAddrs;
/// use v6?

@ -228,7 +228,7 @@ struct lokinet_context
[[nodiscard]] std::optional<int>
make_udp_handler(
const std::shared_ptr<llarp::service::Endpoint>& ep,
llarp::huint16_t exposePort,
llarp::net::port_t exposePort,
lokinet_udp_flow_filter filter,
lokinet_udp_flow_recv_func recv,
lokinet_udp_flow_timeout_func timeout,
@ -245,16 +245,16 @@ struct lokinet_context
}
});
}
std::weak_ptr<llarp::service::Endpoint> weak{ep};
auto udp = std::make_shared<UDPHandler>(
next_socket_id(), llarp::ToNet(exposePort), filter, recv, timeout, user, std::weak_ptr{ep});
next_socket_id(), exposePort, filter, recv, timeout, user, weak);
auto id = udp->m_SocketID;
std::promise<bool> result;
impl->router->loop()->call([ep, &result, udp, exposePort]() {
if (auto pkt = ep->EgresPacketRouter())
{
pkt->AddUDPHandler(exposePort, [udp](auto from, auto pkt) {
pkt->AddUDPHandler(llarp::net::ToHost(exposePort), [udp](auto from, auto pkt) {
udp->HandlePacketFrom(std::move(from), std::move(pkt));
});
result.set_value(true);
@ -903,8 +903,8 @@ extern "C"
auto lock = ctx->acquire();
if (auto ep = ctx->endpoint())
{
if (auto maybe =
ctx->make_udp_handler(ep, llarp::huint16_t{exposedPort}, filter, recv, timeout, user))
if (auto maybe = ctx->make_udp_handler(
ep, llarp::net::port_t::from_host(exposedPort), filter, recv, timeout, user))
{
result->socket_id = *maybe;
return 0;
@ -934,7 +934,7 @@ extern "C"
return EINVAL;
std::shared_ptr<llarp::EndpointBase> ep;
llarp::nuint16_t srcport{0};
llarp::nuint16_t dstport{llarp::ToNet(llarp::huint16_t{remote->remote_port})};
auto dstport = llarp::net::port_t::from_host(remote->remote_port);
{
auto lock = ctx->acquire();
if (auto itr = ctx->udp_sockets.find(remote->socket_id); itr != ctx->udp_sockets.end())

@ -16,6 +16,12 @@ namespace llarp
{
return memcmp(&lh, &rh, sizeof(in6_addr)) == 0;
}
bool
operator<(const in6_addr& lh, const in6_addr& rh)
{
return memcmp(&lh, &rh, sizeof(in6_addr)) < 0;
}
/// shared utility functions
///
@ -112,7 +118,8 @@ namespace llarp
else if (other.sa_family == AF_INET)
*this = reinterpret_cast<const sockaddr_in&>(other);
else
throw std::invalid_argument("Invalid sockaddr (not AF_INET or AF_INET6)");
throw std::invalid_argument{
fmt::format("Invalid sockaddr (not AF_INET or AF_INET6) was {}", other.sa_family)};
return *this;
}
@ -209,11 +216,8 @@ namespace llarp
bool
SockAddr::operator<(const SockAddr& other) const
{
return memcmp(
m_addr.sin6_addr.s6_addr,
other.m_addr.sin6_addr.s6_addr,
sizeof(m_addr.sin6_addr.s6_addr))
< 0;
return (m_addr.sin6_addr < other.m_addr.sin6_addr)
or (m_addr.sin6_port < other.m_addr.sin6_port);
}
bool

@ -74,6 +74,12 @@ namespace llarp
bool
operator==(const SockAddr& other) const;
bool
operator!=(const SockAddr& other) const
{
return not(*this == other);
};
void
fromString(std::string_view str, bool allow_port = true);

@ -2,6 +2,7 @@
#include "abstractrouter.hpp"
#include "net/sock_addr.hpp"
#include <llarp/service/context.hpp>
#include <llarp/dns/resolver.hpp>
#include <unordered_set>
namespace llarp
@ -159,6 +160,27 @@ namespace llarp
}
}
void
RoutePoker::SetDNSMode(bool exit_mode_on) const
{
if (auto dns = m_Router->hiddenServiceContext().GetDefault()->DNS())
{
if (auto maybe_addr = dns->FirstBoundPacketSourceAddr())
{
if (dns::set_resolver(
m_Router->hiddenServiceContext().GetDefault()->GetIfName(),
*maybe_addr,
exit_mode_on))
{
LogInfo(
"DNS set to ",
*maybe_addr,
exit_mode_on ? " for all traffic" : " for just lokinet traffic");
}
}
}
}
void
RoutePoker::Enable()
{
@ -173,10 +195,7 @@ namespace llarp
m_Enabled = true;
}
systemd_resolved_set_dns(
m_Router->hiddenServiceContext().GetDefault()->GetIfName(),
m_Router->GetConfig()->dns.m_bind,
true /* route all DNS */);
SetDNSMode(true);
}
void
@ -188,10 +207,7 @@ namespace llarp
DisableAllRoutes();
m_Enabled = false;
systemd_resolved_set_dns(
m_Router->hiddenServiceContext().GetDefault()->GetIfName(),
m_Router->GetConfig()->dns.m_bind,
false /* route DNS only for .loki/.snode */);
SetDNSMode(false);
}
void

@ -5,7 +5,6 @@
#include <memory>
#include <optional>
#include <llarp/net/net_int.hpp>
#include "systemd_resolved.hpp"
namespace llarp
{
@ -45,6 +44,11 @@ namespace llarp
void
Down();
/// set dns resolver
/// pass in if we are using exit node mode right now as a bool
void
SetDNSMode(bool using_exit_mode) const;
private:
void
DeleteAllRoutes();

@ -1217,12 +1217,6 @@ namespace llarp
return true;
}
int
Router::OutboundUDPSocket() const
{
return m_OutboundUDPSocket;
}
bool
Router::Run()
{

@ -211,9 +211,6 @@ namespace llarp
bool
ShouldTestOtherRouters() const;
int
OutboundUDPSocket() const override;
std::optional<SockAddr> _ourAddress;
EventLoop_ptr _loop;

@ -53,6 +53,44 @@ namespace llarp::rpc
return obj.dump();
}
/// fake packet source that serializes repsonses back into dns
class DummyPacketSource : public dns::PacketSource_Base
{
std::function<void(std::optional<dns::Message>)> func;
public:
SockAddr dumb;
template <typename Callable>
DummyPacketSource(Callable&& f) : func{std::forward<Callable>(f)}
{}
bool
WouldLoop(const SockAddr&, const SockAddr&) const override
{
return false;
};
/// send packet with src and dst address containing buf on this packet source
void
SendTo(const SockAddr&, const SockAddr&, OwnedBuffer buf) const override
{
func(dns::MaybeParseDNSMessage(buf));
}
/// stop reading packets and end operation
void
Stop() override{};
/// returns the sockaddr we are bound on if applicable
std::optional<SockAddr>
BoundOn() const override
{
return std::nullopt;
}
};
/// a function that replies to an rpc request
using ReplyFunction_t = std::function<void(std::string)>;
@ -606,19 +644,23 @@ namespace llarp::rpc
dns::Message msg{dns::Question{qname, qtype}};
if (auto ep_ptr = (GetEndpointByName(r, endpoint)))
if (auto ep_ptr = GetEndpointByName(r, endpoint))
{
if (auto ep = reinterpret_cast<dns::IQueryHandler*>(ep_ptr.get()))
if (auto dns = ep_ptr->DNS())
{
if (ep->ShouldHookDNSMessage(msg))
auto src = std::make_shared<DummyPacketSource>([reply](auto result) {
if (result)
reply(CreateJSONResponse(result->ToJSON()));
else
reply(CreateJSONError("no response from dns"));
});
if (not dns->MaybeHandlePacket(src, src->dumb, src->dumb, msg.ToBuffer()))
{
ep->HandleHookedDNSMessage(std::move(msg), [reply](dns::Message msg) {
reply(CreateJSONResponse(msg.ToJSON()));
});
return;
reply(CreateJSONError("dns query not accepted by endpoint"));
}
}
reply(CreateJSONError("dns query not accepted by endpoint"));
else
reply(CreateJSONError("endpoint does not have dns"));
return;
}
reply(CreateJSONError("no such endpoint for dns query"));

@ -26,6 +26,7 @@
#include "auth.hpp"
#include <llarp/vpn/egres_packet_router.hpp>
#include <llarp/dns/server.hpp>
// minimum time between introset shifts
#ifndef MIN_SHIFT_INTERVAL

@ -134,6 +134,16 @@ operator==(const llarp_buffer_t& buff, std::string_view data)
}
namespace llarp
{
std::vector<byte_t>
OwnedBuffer::copy() const
{
std::vector<byte_t> ret;
ret.resize(sz);
const auto* ptr = buf.get();
std::copy(ptr, ptr + sz, ret.data());
return ret;
}
OwnedBuffer
OwnedBuffer::copy_from(const llarp_buffer_t& b)
{

@ -250,6 +250,12 @@ namespace llarp
explicit OwnedBuffer(size_t sz) : OwnedBuffer{std::make_unique<byte_t[]>(sz), sz}
{}
// copy content from existing memory
explicit OwnedBuffer(const byte_t* ptr, size_t sz) : OwnedBuffer{sz}
{
std::copy_n(ptr, sz, buf.get());
}
OwnedBuffer(const OwnedBuffer&) = delete;
OwnedBuffer&
operator=(const OwnedBuffer&) = delete;
@ -273,6 +279,10 @@ namespace llarp
// cur), for when a llarp_buffer_t is used in write mode.
static OwnedBuffer
copy_used(const llarp_buffer_t& b);
/// copy everything in this owned buffer into a vector
std::vector<byte_t>
copy() const;
};
} // namespace llarp

@ -1,5 +1,6 @@
#pragma once
#include <functional>
#include <memory>
namespace llarp
{
@ -16,4 +17,16 @@ namespace llarp
return Compare()(left, right);
}
};
/// type for comparing weak_ptr by value
template <typename Type_t, typename Compare = std::less<>>
struct CompareWeakPtr
{
bool
operator()(const std::weak_ptr<Type_t>& left, const std::weak_ptr<Type_t>& right) const
{
return ComparePtr<std::shared_ptr<Type_t>, Compare>{}(left.lock(), right.lock());
}
};
} // namespace llarp

@ -69,12 +69,12 @@ namespace llarp
py::class_<LinksConfig>(mod, "LinksConfig")
.def(py::init<>())
.def(
"setOutboundLink",
[](LinksConfig& self, std::string addr) {
self.OutboundLinks.emplace_back(std::move(addr));
"addOutboundLink",
[](LinksConfig& self, std::string _addr) {
self.OutboundLinks.emplace_back(std::move(_addr));
})
.def("addInboundLink", [](LinksConfig& self, std::string addr) {
self.InboundListenAddrs.emplace_back(std::move(addr));
.def("addInboundLink", [](LinksConfig& self, std::string _addr) {
self.InboundLinks.emplace_back(std::move(_addr));
});
py::class_<ApiConfig>(mod, "ApiConfig")

@ -12,12 +12,19 @@ namespace mocks
class MockUDPHandle : public llarp::UDPHandle
{
Network* const _net;
std::optional<llarp::SockAddr> _addr;
public:
MockUDPHandle(Network* net, llarp::UDPHandle::ReceiveFunc recv)
: llarp::UDPHandle{recv}, _net{net}
{}
std::optional<llarp::SockAddr>
LocalAddr() const override
{
return _addr;
}
bool
listen(const llarp::SockAddr& addr) override;
@ -186,7 +193,10 @@ namespace mocks
bool
MockUDPHandle::listen(const llarp::SockAddr& addr)
{
return _net->HasInterfaceAddress(addr.getIP());
if (not _net->HasInterfaceAddress(addr.getIP()))
return false;
_addr = addr;
return true;
}
} // namespace mocks

Loading…
Cancel
Save