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/serialize.cpp
dns/server.cpp dns/server.cpp
dns/srv_data.cpp dns/srv_data.cpp
dns/unbound_resolver.cpp dns/resolver.cpp
consensus/table.cpp consensus/table.cpp
consensus/reachability_testing.cpp consensus/reachability_testing.cpp
@ -151,7 +151,7 @@ add_library(liblokinet
peerstats/types.cpp peerstats/types.cpp
pow.cpp pow.cpp
profiling.cpp profiling.cpp
quic/address.cpp quic/address.cpp
quic/client.cpp quic/client.cpp
quic/connection.cpp quic/connection.cpp
@ -171,7 +171,7 @@ add_library(liblokinet
router/rc_gossiper.cpp router/rc_gossiper.cpp
router/router.cpp router/router.cpp
router/route_poker.cpp router/route_poker.cpp
router/systemd_resolved.cpp
routing/dht_message.cpp routing/dht_message.cpp
routing/message_parser.cpp routing/message_parser.cpp
routing/path_confirm_message.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 // 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 // can bind to other 127.* IPs to avoid conflicting with something else that may be listening on
// 127.0.0.1:53. // 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 // 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); m_upstreamDNS.emplace_back(DefaultUpstreamDNS.val);
if (!m_upstreamDNS.back().getPort())
m_upstreamDNS.back().setPort(53);
conf.defineOption<std::string>( conf.defineOption<std::string>(
"dns", "dns",
"upstream", "upstream",
DefaultUpstreamDNS,
MultiValue, MultiValue,
Comment{ Comment{
"Upstream resolver(s) to use as fallback for non-loki addresses.", "Upstream resolver(s) to use as fallback for non-loki addresses.",
@ -798,10 +805,10 @@ namespace llarp
m_upstreamDNS.clear(); m_upstreamDNS.clear();
first = false; first = false;
} }
if (!arg.empty()) if (not arg.empty())
{ {
auto& entry = m_upstreamDNS.emplace_back(std::move(arg)); auto& entry = m_upstreamDNS.emplace_back(std::move(arg));
if (!entry.getPort()) if (not entry.getPort())
entry.setPort(53); entry.setPort(53);
} }
}); });
@ -814,9 +821,12 @@ namespace llarp
"Address to bind to for handling DNS requests.", "Address to bind to for handling DNS requests.",
}, },
[=](std::string arg) { [=](std::string arg) {
m_bind = SockAddr{std::move(arg)}; SockAddr addr{arg};
if (!m_bind.getPort()) // set dns port if no explicit port specified
m_bind.setPort(53); // 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>( conf.defineOption<fs::path>(
@ -843,6 +853,11 @@ namespace llarp
"(This is not used directly by lokinet itself, but by the lokinet init scripts", "(This is not used directly by lokinet itself, but by the lokinet init scripts",
"on systems which use resolveconf)", "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 void

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

@ -414,5 +414,17 @@ namespace llarp
fmt::format("{}", fmt::join(additional, ","))); 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 dns
} // namespace llarp } // namespace llarp

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

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

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

@ -4,158 +4,539 @@
#include <array> #include <array>
#include <utility> #include <utility>
#include <llarp/ev/udp_handle.hpp> #include <llarp/ev/udp_handle.hpp>
#include <optional>
#include <memory>
#include <unbound.h>
#include <uvw.hpp>
namespace llarp::dns 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 void
PacketHandler::Stop() QueryJob_Base::Cancel() const
{ {
if (m_UnboundResolver) Message reply{m_Query};
m_UnboundResolver->Stop(); reply.AddServFail();
SendReply(reply.ToBuffer());
} }
bool /// sucks up udp packets from a bound socket and feeds it to a server
Proxy::Start(SockAddr addr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles) class UDPReader : public PacketSource_Base, public std::enable_shared_from_this<UDPReader>
{ {
if (not PacketHandler::Start(addr, std::move(resolvers), std::move(hostfiles))) Server& m_DNS;
return false; std::shared_ptr<llarp::UDPHandle> m_udp;
return m_Server->listen(addr); SockAddr m_LocalAddr;
}
void public:
PacketHandler::Restart() explicit UDPReader(Server& dns, const EventLoop_ptr& loop, llarp::SockAddr bindaddr)
{ : m_DNS{dns}
if (m_UnboundResolver)
{ {
LogInfo("reset libunbound's internal stuff"); m_udp = loop->make_udp([&](auto&, SockAddr src, llarp::OwnedBuffer buf) {
m_UnboundResolver->Init(); 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 std::optional<SockAddr>
PacketHandler::Start(SockAddr, std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles) BoundOn() const override
{ {
return SetupUnboundResolver(std::move(resolvers), std::move(hostfiles)); return m_udp->LocalAddr();
} }
bool bool
PacketHandler::SetupUnboundResolver( WouldLoop(const SockAddr& to, const SockAddr&) const override
std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles) {
{ return to != m_LocalAddr;
// 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());
};
auto replyFunc = [self = weak_from_this()](auto&&... args) { void
if (auto this_ptr = self.lock()) SendTo(const SockAddr& to, const SockAddr&, llarp::OwnedBuffer buf) const override
this_ptr->SendServerMessageBufferTo(std::forward<decltype(args)>(args)...); {
}; m_udp->send(to, std::move(buf));
}
m_UnboundResolver = void
std::make_shared<UnboundResolver>(m_Loop, std::move(replyFunc), std::move(failFunc)); Stop() override
m_Resolvers.clear();
if (not m_UnboundResolver->Init())
{ {
llarp::LogError("Failed to initialize upstream DNS resolver."); m_udp->close();
m_UnboundResolver = nullptr;
return false;
} }
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); Down();
m_UnboundResolver = nullptr; 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; 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 std::shared_ptr<PacketSource_Base>
Proxy::SendServerMessageBufferTo( Server::MakePacketSourceOn(const llarp::SockAddr& addr, const llarp::DnsConfig&)
const SockAddr& to, [[maybe_unused]] const SockAddr& from, llarp_buffer_t buf)
{ {
if (!m_Server->send(to, buf)) return std::make_shared<UDPReader>(*this, m_Loop, addr);
llarp::LogError("dns reply failed");
} }
bool std::shared_ptr<Resolver_Base>
PacketHandler::IsUpstreamResolver(const SockAddr& to, [[maybe_unused]] const SockAddr& from) const 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 std::vector<SockAddr>
PacketHandler::ShouldHandlePacket( Server::BoundPacketSourceAddrs() const
const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const
{ {
MessageHeader hdr; std::vector<SockAddr> addrs;
if (not hdr.Decode(&buf)) 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}; std::optional<SockAddr>
if (not msg.Decode(&buf)) 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)) void
return true; Server::AddPacketSource(std::weak_ptr<PacketSource_Base> pkt)
// 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 m_PacketSources.push_back(pkt);
// 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 void
PacketHandler::HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf) Server::AddPacketSource(std::shared_ptr<PacketSource_Base> pkt)
{ {
MessageHeader hdr; m_OwnedPacketSources.push_back(pkt);
if (not hdr.Decode(&buf)) 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); if (auto ptr = resolver.lock())
return; ptr->CancelPendingQueries();
} }
}
Message msg(hdr); void
if (not msg.Decode(&buf)) Server::Reset()
{
for (const auto& resolver : m_Resolvers)
{ {
llarp::LogWarn("failed to parse dns message from ", from); if (auto ptr = resolver.lock())
return; 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 // 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 // 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 // thankfully mozilla added a backdoor that allows ISPs to turn it off
// so we disable DoH for firefox using mozilla's ISP backdoor // 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) for (const auto& q : msg.questions)
{ {
// is this firefox looking for their backdoor record? // 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. // yea it is, let's turn off DoH because god is dead.
msg.AddNXReply(); msg.AddNXReply();
// press F to pay respects // press F to pay respects and send it back where it came from
SendServerMessageBufferTo(from, resolver, msg.ToBuffer()); ptr->SendTo(from, to, msg.ToBuffer());
return; 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) { if (auto res_ptr = resolver.lock())
self->SendServerMessageBufferTo(to, resolver, msg.ToBuffer());
};
if (!m_QueryHandler->HandleHookedDNSMessage(std::move(msg), reply))
{ {
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) return false;
{
// no upstream resolvers
// let's serv fail it
msg.AddServFail();
SendServerMessageBufferTo(from, resolver, msg.ToBuffer());
}
else
{
m_UnboundResolver->Lookup(resolver, from, std::move(msg));
}
} }
} // namespace llarp::dns } // namespace llarp::dns

@ -1,99 +1,226 @@
#pragma once #pragma once
#include "message.hpp" #include "message.hpp"
#include <llarp/config/config.hpp>
#include <llarp/ev/ev.hpp> #include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp> #include <llarp/net/net.hpp>
#include "unbound_resolver.hpp" #include <llarp/util/fs.hpp>
#include <set>
#include <unordered_map> namespace llarp::dns
namespace llarp
{ {
namespace dns /// a job handling 1 dns query
class QueryJob_Base
{ {
/// handler of dns query hooking protected:
class IQueryHandler /// the original dns query
{ Message m_Query;
public:
virtual ~IQueryHandler() = default;
/// return true if we should hook this message public:
virtual bool explicit QueryJob_Base(Message query) : m_Query{std::move(query)}
ShouldHookDNSMessage(const Message& msg) const = 0; {}
/// handle a hooked message virtual ~QueryJob_Base() = default;
virtual bool
HandleHookedDNSMessage(Message query, std::function<void(Message)> sendReply) = 0;
};
// Base class for DNS lookups Message&
class PacketHandler : public std::enable_shared_from_this<PacketHandler> Underlying()
{ {
public: return m_Query;
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;
// Returns true if this packet is something that looks like it's going to an upstream const Message&
// resolver, i.e. matches a configured resolver. Underlying() const
virtual bool {
IsUpstreamResolver(const SockAddr& to, const SockAddr& from) const; return m_Query;
}
private: /// cancel this operation and inform anyone who cares
void void
HandleUpstreamFailure(const SockAddr& from, const SockAddr& to, Message msg); Cancel() const;
bool /// send a raw buffer back to the querier
SetupUnboundResolver(std::vector<SockAddr> resolvers, std::vector<fs::path> hostfiles); virtual void
SendReply(llarp::OwnedBuffer replyBuf) const = 0;
};
IQueryHandler* const m_QueryHandler; class PacketSource_Base
std::set<SockAddr> m_Resolvers; {
std::shared_ptr<UnboundResolver> m_UnboundResolver; public:
EventLoop_ptr m_Loop; 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. /// greater than via rank
class Proxy : public PacketHandler 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: (void)to;
explicit Proxy(EventLoop_ptr loop, IQueryHandler* handler); (void)from;
return false;
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;
}; };
} // 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; class TunnelManager;
} }
namespace dns
{
class Server;
}
class EndpointBase class EndpointBase
{ {
std::unordered_set<dns::SRVData> m_SRVRecords; std::unordered_set<dns::SRVData> m_SRVRecords;
@ -72,6 +77,13 @@ namespace llarp
void void
PutSRVRecord(dns::SRVData srv); 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 /// called when srv data changes in some way
virtual void virtual void
SRVRecordsChanged() = 0; SRVRecordsChanged() = 0;

@ -72,6 +72,13 @@ namespace llarp::uv
bool bool
send(const SockAddr& dest, const llarp_buffer_t& buf) override; 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> std::optional<int>
file_descriptor() override file_descriptor() override
{ {

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

@ -18,11 +18,7 @@ namespace llarp
namespace handlers namespace handlers
{ {
ExitEndpoint::ExitEndpoint(std::string name, AbstractRouter* r) ExitEndpoint::ExitEndpoint(std::string name, AbstractRouter* r)
: m_Router(r) : m_Router(r), m_Name(std::move(name)), m_QUIC{std::make_shared<quic::TunnelManager>(*this)}
, 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_ShouldInitTun = true; m_ShouldInitTun = true;
m_QUIC = std::make_shared<quic::TunnelManager>(*this); m_QUIC = std::make_shared<quic::TunnelManager>(*this);
@ -211,6 +207,22 @@ namespace llarp
return false; 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 bool
ExitEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)> reply) ExitEndpoint::HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)> reply)
{ {
@ -459,9 +471,7 @@ namespace llarp
} }
GetRouter()->loop()->add_ticker([this] { Flush(); }); GetRouter()->loop()->add_ticker([this] { Flush(); });
m_Resolver->Start();
llarp::LogInfo("Trying to start resolver ", m_LocalResolverAddr);
return m_Resolver->Start(m_LocalResolverAddr, m_UpstreamResolvers, {});
} }
return true; return true;
} }
@ -703,8 +713,7 @@ namespace llarp
m_ShouldInitTun = false; m_ShouldInitTun = false;
} }
m_LocalResolverAddr = dnsConfig.m_bind; m_Resolver = std::make_shared<dns::Server>(m_Router->loop(), dnsConfig);
m_UpstreamResolvers = dnsConfig.m_upstreamDNS;
m_OurRange = networkConfig.m_ifaddr; m_OurRange = networkConfig.m_ifaddr;
if (!m_OurRange.addr.h) if (!m_OurRange.addr.h)

@ -10,8 +10,33 @@ namespace llarp
struct AbstractRouter; struct AbstractRouter;
namespace handlers 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(std::string name, AbstractRouter* r);
~ExitEndpoint() override; ~ExitEndpoint() override;
@ -66,10 +91,10 @@ namespace llarp
SupportsV6() const; SupportsV6() const;
bool bool
ShouldHookDNSMessage(const dns::Message& msg) const override; ShouldHookDNSMessage(const dns::Message& msg) const;
bool bool
HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>) override; HandleHookedDNSMessage(dns::Message msg, std::function<void(dns::Message)>);
void void
LookupServiceAsync( LookupServiceAsync(
@ -174,7 +199,7 @@ namespace llarp
KickIdentOffExit(const PubKey& pk); KickIdentOffExit(const PubKey& pk);
AbstractRouter* m_Router; AbstractRouter* m_Router;
std::shared_ptr<dns::Proxy> m_Resolver; std::shared_ptr<dns::Server> m_Resolver;
bool m_ShouldInitTun; bool m_ShouldInitTun;
std::string m_Name; std::string m_Name;
bool m_PermitExit; bool m_PermitExit;

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

@ -23,12 +23,31 @@ namespace llarp
namespace handlers namespace handlers
{ {
struct TunEndpoint : public service::Endpoint, struct TunEndpoint : public service::Endpoint,
public dns::IQueryHandler, public dns::Resolver_Base,
public std::enable_shared_from_this<TunEndpoint> public std::enable_shared_from_this<TunEndpoint>
{ {
TunEndpoint(AbstractRouter* r, llarp::service::Context* parent); TunEndpoint(AbstractRouter* r, llarp::service::Context* parent);
~TunEndpoint() override; ~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 path::PathSet_ptr
GetSelf() override GetSelf() override
{ {
@ -71,11 +90,10 @@ namespace llarp
SupportsV6() const override; SupportsV6() const override;
bool bool
ShouldHookDNSMessage(const dns::Message& msg) const override; ShouldHookDNSMessage(const dns::Message& msg) const;
bool bool
HandleHookedDNSMessage( HandleHookedDNSMessage(dns::Message query, std::function<void(dns::Message)> sendreply);
dns::Message query, std::function<void(dns::Message)> sendreply) override;
void void
TickTun(llarp_time_t now); TickTun(llarp_time_t now);
@ -96,6 +114,16 @@ namespace llarp
bool bool
SetupTun(); SetupTun();
void
SetupDNS();
/// overrides Endpoint
std::shared_ptr<dns::Server>
DNS() const override
{
return m_DNS;
};
/// overrides Endpoint /// overrides Endpoint
bool bool
SetupNetworking() override; SetupNetworking() override;
@ -249,8 +277,11 @@ namespace llarp
query->AddNXReply(); query->AddNXReply();
reply(*query); 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 /// maps ip address to timestamp last active
std::unordered_map<huint128_t, llarp_time_t> m_IPActivity; std::unordered_map<huint128_t, llarp_time_t> m_IPActivity;
@ -265,12 +296,6 @@ namespace llarp
huint128_t m_MaxIP; huint128_t m_MaxIP;
/// our ip range we are using /// our ip range we are using
llarp::IPRange m_OurRange; 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 /// list of strict connect addresses for hooks
std::vector<IpAddress> m_StrictConnectAddrs; std::vector<IpAddress> m_StrictConnectAddrs;
/// use v6? /// use v6?

@ -228,7 +228,7 @@ struct lokinet_context
[[nodiscard]] std::optional<int> [[nodiscard]] std::optional<int>
make_udp_handler( make_udp_handler(
const std::shared_ptr<llarp::service::Endpoint>& ep, 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_filter filter,
lokinet_udp_flow_recv_func recv, lokinet_udp_flow_recv_func recv,
lokinet_udp_flow_timeout_func timeout, 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>( 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; auto id = udp->m_SocketID;
std::promise<bool> result; std::promise<bool> result;
impl->router->loop()->call([ep, &result, udp, exposePort]() { impl->router->loop()->call([ep, &result, udp, exposePort]() {
if (auto pkt = ep->EgresPacketRouter()) 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)); udp->HandlePacketFrom(std::move(from), std::move(pkt));
}); });
result.set_value(true); result.set_value(true);
@ -903,8 +903,8 @@ extern "C"
auto lock = ctx->acquire(); auto lock = ctx->acquire();
if (auto ep = ctx->endpoint()) if (auto ep = ctx->endpoint())
{ {
if (auto maybe = if (auto maybe = ctx->make_udp_handler(
ctx->make_udp_handler(ep, llarp::huint16_t{exposedPort}, filter, recv, timeout, user)) ep, llarp::net::port_t::from_host(exposedPort), filter, recv, timeout, user))
{ {
result->socket_id = *maybe; result->socket_id = *maybe;
return 0; return 0;
@ -934,7 +934,7 @@ extern "C"
return EINVAL; return EINVAL;
std::shared_ptr<llarp::EndpointBase> ep; std::shared_ptr<llarp::EndpointBase> ep;
llarp::nuint16_t srcport{0}; 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(); auto lock = ctx->acquire();
if (auto itr = ctx->udp_sockets.find(remote->socket_id); itr != ctx->udp_sockets.end()) 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; 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 /// shared utility functions
/// ///
@ -112,7 +118,8 @@ namespace llarp
else if (other.sa_family == AF_INET) else if (other.sa_family == AF_INET)
*this = reinterpret_cast<const sockaddr_in&>(other); *this = reinterpret_cast<const sockaddr_in&>(other);
else 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; return *this;
} }
@ -209,11 +216,8 @@ namespace llarp
bool bool
SockAddr::operator<(const SockAddr& other) const SockAddr::operator<(const SockAddr& other) const
{ {
return memcmp( return (m_addr.sin6_addr < other.m_addr.sin6_addr)
m_addr.sin6_addr.s6_addr, or (m_addr.sin6_port < other.m_addr.sin6_port);
other.m_addr.sin6_addr.s6_addr,
sizeof(m_addr.sin6_addr.s6_addr))
< 0;
} }
bool bool

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

@ -2,6 +2,7 @@
#include "abstractrouter.hpp" #include "abstractrouter.hpp"
#include "net/sock_addr.hpp" #include "net/sock_addr.hpp"
#include <llarp/service/context.hpp> #include <llarp/service/context.hpp>
#include <llarp/dns/resolver.hpp>
#include <unordered_set> #include <unordered_set>
namespace llarp 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 void
RoutePoker::Enable() RoutePoker::Enable()
{ {
@ -173,10 +195,7 @@ namespace llarp
m_Enabled = true; m_Enabled = true;
} }
systemd_resolved_set_dns( SetDNSMode(true);
m_Router->hiddenServiceContext().GetDefault()->GetIfName(),
m_Router->GetConfig()->dns.m_bind,
true /* route all DNS */);
} }
void void
@ -188,10 +207,7 @@ namespace llarp
DisableAllRoutes(); DisableAllRoutes();
m_Enabled = false; m_Enabled = false;
systemd_resolved_set_dns( SetDNSMode(false);
m_Router->hiddenServiceContext().GetDefault()->GetIfName(),
m_Router->GetConfig()->dns.m_bind,
false /* route DNS only for .loki/.snode */);
} }
void void

@ -5,7 +5,6 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <llarp/net/net_int.hpp> #include <llarp/net/net_int.hpp>
#include "systemd_resolved.hpp"
namespace llarp namespace llarp
{ {
@ -45,6 +44,11 @@ namespace llarp
void void
Down(); 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: private:
void void
DeleteAllRoutes(); DeleteAllRoutes();

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

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

@ -53,6 +53,44 @@ namespace llarp::rpc
return obj.dump(); 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 /// a function that replies to an rpc request
using ReplyFunction_t = std::function<void(std::string)>; using ReplyFunction_t = std::function<void(std::string)>;
@ -606,19 +644,23 @@ namespace llarp::rpc
dns::Message msg{dns::Question{qname, qtype}}; 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(CreateJSONError("dns query not accepted by endpoint"));
reply(CreateJSONResponse(msg.ToJSON()));
});
return;
} }
} }
reply(CreateJSONError("dns query not accepted by endpoint")); else
reply(CreateJSONError("endpoint does not have dns"));
return; return;
} }
reply(CreateJSONError("no such endpoint for dns query")); reply(CreateJSONError("no such endpoint for dns query"));

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

@ -134,6 +134,16 @@ operator==(const llarp_buffer_t& buff, std::string_view data)
} }
namespace llarp 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
OwnedBuffer::copy_from(const llarp_buffer_t& b) 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} 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(const OwnedBuffer&) = delete;
OwnedBuffer& OwnedBuffer&
operator=(const OwnedBuffer&) = delete; operator=(const OwnedBuffer&) = delete;
@ -273,6 +279,10 @@ namespace llarp
// cur), for when a llarp_buffer_t is used in write mode. // cur), for when a llarp_buffer_t is used in write mode.
static OwnedBuffer static OwnedBuffer
copy_used(const llarp_buffer_t& b); copy_used(const llarp_buffer_t& b);
/// copy everything in this owned buffer into a vector
std::vector<byte_t>
copy() const;
}; };
} // namespace llarp } // namespace llarp

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <memory>
namespace llarp namespace llarp
{ {
@ -16,4 +17,16 @@ namespace llarp
return Compare()(left, right); 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 } // namespace llarp

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

@ -12,12 +12,19 @@ namespace mocks
class MockUDPHandle : public llarp::UDPHandle class MockUDPHandle : public llarp::UDPHandle
{ {
Network* const _net; Network* const _net;
std::optional<llarp::SockAddr> _addr;
public: public:
MockUDPHandle(Network* net, llarp::UDPHandle::ReceiveFunc recv) MockUDPHandle(Network* net, llarp::UDPHandle::ReceiveFunc recv)
: llarp::UDPHandle{recv}, _net{net} : llarp::UDPHandle{recv}, _net{net}
{} {}
std::optional<llarp::SockAddr>
LocalAddr() const override
{
return _addr;
}
bool bool
listen(const llarp::SockAddr& addr) override; listen(const llarp::SockAddr& addr) override;
@ -186,7 +193,10 @@ namespace mocks
bool bool
MockUDPHandle::listen(const llarp::SockAddr& addr) 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 } // namespace mocks

Loading…
Cancel
Save