From 74362149eb2958b9f939d171f4b5cf6a740dcfaf Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 7 Apr 2022 16:44:23 -0400 Subject: [PATCH] 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. --- llarp/CMakeLists.txt | 6 +- llarp/config/config.cpp | 35 +- llarp/config/config.hpp | 3 +- llarp/dns/message.cpp | 12 + llarp/dns/message.hpp | 3 + .../systemd_resolved.cpp => dns/resolver.cpp} | 14 +- .../systemd_resolved.hpp => dns/resolver.hpp} | 16 +- llarp/dns/server.cpp | 607 ++++++++++++++---- llarp/dns/server.hpp | 287 ++++++--- llarp/endpoint_base.hpp | 12 + llarp/ev/ev_libuv.cpp | 7 + llarp/ev/udp_handle.hpp | 4 + llarp/handlers/exit.cpp | 29 +- llarp/handlers/exit.hpp | 33 +- llarp/handlers/tun.cpp | 190 ++++-- llarp/handlers/tun.hpp | 49 +- llarp/lokinet_shared.cpp | 14 +- llarp/net/sock_addr.cpp | 16 +- llarp/net/sock_addr.hpp | 6 + llarp/router/route_poker.cpp | 32 +- llarp/router/route_poker.hpp | 6 +- llarp/router/router.cpp | 6 - llarp/router/router.hpp | 3 - llarp/rpc/rpc_server.cpp | 58 +- llarp/service/endpoint.hpp | 1 + llarp/util/buffer.cpp | 10 + llarp/util/buffer.hpp | 10 + llarp/util/compare_ptr.hpp | 13 + pybind/llarp/config.cpp | 10 +- test/mocks/mock_network.hpp | 12 +- 30 files changed, 1141 insertions(+), 363 deletions(-) rename llarp/{router/systemd_resolved.cpp => dns/resolver.cpp} (95%) rename llarp/{router/systemd_resolved.hpp => dns/resolver.hpp} (55%) diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index f95543089..9635d984d 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -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 diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 7fc78746d..c62cb42a8 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.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( "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( @@ -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 diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 0d7a6856d..33c38aef7 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -155,9 +155,10 @@ namespace llarp struct DnsConfig { - SockAddr m_bind; + std::vector m_bind; std::vector m_upstreamDNS; std::vector m_hostfiles; + std::unordered_multimap m_ExtraOpts; void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); diff --git a/llarp/dns/message.cpp b/llarp/dns/message.cpp index 97bc3707e..556f3d6d8 100644 --- a/llarp/dns/message.cpp +++ b/llarp/dns/message.cpp @@ -414,5 +414,17 @@ namespace llarp fmt::format("{}", fmt::join(additional, ","))); } + std::optional + 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 diff --git a/llarp/dns/message.hpp b/llarp/dns/message.hpp index 5d10e2901..6140d307c 100644 --- a/llarp/dns/message.hpp +++ b/llarp/dns/message.hpp @@ -103,6 +103,9 @@ namespace llarp std::vector authorities; std::vector additional; }; + + std::optional + MaybeParseDNSMessage(llarp_buffer_t buf); } // namespace dns template <> diff --git a/llarp/router/systemd_resolved.cpp b/llarp/dns/resolver.cpp similarity index 95% rename from llarp/router/systemd_resolved.cpp rename to llarp/dns/resolver.cpp index 61d9ef930..cb64f9e6f 100644 --- a/llarp/router/systemd_resolved.cpp +++ b/llarp/dns/resolver.cpp @@ -1,17 +1,17 @@ -#include "systemd_resolved.hpp" +#include "resolver.hpp" #include #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 diff --git a/llarp/router/systemd_resolved.hpp b/llarp/dns/resolver.hpp similarity index 55% rename from llarp/router/systemd_resolved.hpp rename to llarp/dns/resolver.hpp index 8fc8c20af..62c5e6111 100644 --- a/llarp/router/systemd_resolved.hpp +++ b/llarp/dns/resolver.hpp @@ -1,13 +1,14 @@ #pragma once - #include #include -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 diff --git a/llarp/dns/server.cpp b/llarp/dns/server.cpp index d5b3b6b4b..3d91eab3d 100644 --- a/llarp/dns/server.cpp +++ b/llarp/dns/server.cpp @@ -4,158 +4,539 @@ #include #include #include +#include +#include +#include +#include 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 resolvers, std::vector 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 { - if (not PacketHandler::Start(addr, std::move(resolvers), std::move(hostfiles))) - return false; - return m_Server->listen(addr); - } + Server& m_DNS; + std::shared_ptr 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 resolvers, std::vector hostfiles) - { - return SetupUnboundResolver(std::move(resolvers), std::move(hostfiles)); - } + std::optional + BoundOn() const override + { + return m_udp->LocalAddr(); + } - bool - PacketHandler::SetupUnboundResolver( - std::vector resolvers, std::vector 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(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(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 parent; + std::weak_ptr src; + SockAddr resolverAddr; + SockAddr askerAddr; + + public: + explicit Query( + std::weak_ptr parent_, + Message query, + std::weak_ptr 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 { - if (not m_UnboundResolver->AddUpstreamResolver(resolver)) + std::shared_ptr m_ctx; + std::weak_ptr m_Loop; +#ifdef _WIN32 + // windows is dumb so we do ub mainloop in a thread + std::thread runner; + std::atomic running; +#else + std::shared_ptr 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 result{_result}; + // take ownership of our query + std::unique_ptr query{static_cast(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(ub_fd(m_ctx.get())); + m_Poller->on([ptr = std::weak_ptr{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(ub_fd(m_ctx.get())); + m_Poller->on([ptr = std::weak_ptr{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 + void + call(Callable&& f) + { + if (auto loop = m_Loop.lock()) + loop->call(std::forward(f)); + else + LogError("no mainloop?"); + } + + bool + MaybeHookDNS( + std::weak_ptr 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(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 + Server::MakePacketSourceOn(const llarp::SockAddr& addr, const llarp::DnsConfig&) { - if (!m_Server->send(to, buf)) - llarp::LogError("dns reply failed"); + return std::make_shared(*this, m_Loop, addr); } - bool - PacketHandler::IsUpstreamResolver(const SockAddr& to, [[maybe_unused]] const SockAddr& from) const + std::shared_ptr + 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(m_Loop, m_Config); } - bool - PacketHandler::ShouldHandlePacket( - const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) const + std::vector + Server::BoundPacketSourceAddrs() const { - MessageHeader hdr; - if (not hdr.Decode(&buf)) + std::vector 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 + 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) + { + m_Resolvers.insert(resolver); + } + + void + Server::AddResolver(std::shared_ptr resolver) + { + m_OwnedResolvers.insert(resolver); + AddResolver(std::weak_ptr{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 pkt) + { + m_PacketSources.push_back(pkt); } void - PacketHandler::HandlePacket(const SockAddr& resolver, const SockAddr& from, llarp_buffer_t buf) + Server::AddPacketSource(std::shared_ptr pkt) { - MessageHeader hdr; - if (not hdr.Decode(&buf)) + m_OwnedPacketSources.push_back(pkt); + AddPacketSource(std::weak_ptr{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 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 diff --git a/llarp/dns/server.hpp b/llarp/dns/server.hpp index 025ec8ef6..c101581af 100644 --- a/llarp/dns/server.hpp +++ b/llarp/dns/server.hpp @@ -1,99 +1,226 @@ #pragma once #include "message.hpp" +#include #include #include -#include "unbound_resolver.hpp" +#include +#include -#include - -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 sendReply) = 0; - }; + virtual ~QueryJob_Base() = default; - // Base class for DNS lookups - class PacketHandler : public std::enable_shared_from_this + Message& + Underlying() { - public: - explicit PacketHandler(EventLoop_ptr loop, IQueryHandler* handler); - - virtual ~PacketHandler() = default; - - virtual bool - Start( - SockAddr localaddr, - std::vector upstreamResolvers, - std::vector 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 resolvers, std::vector hostfiles); + /// send a raw buffer back to the querier + virtual void + SendReply(llarp::OwnedBuffer replyBuf) const = 0; + }; - IQueryHandler* const m_QueryHandler; - std::set m_Resolvers; - std::shared_ptr 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 + 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 + { + std::weak_ptr src; + const SockAddr resolver; + const SockAddr asker; + + public: + explicit QueryJob( + std::weak_ptr 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 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 upstreamResolvers, - std::vector hostfiles) override; - - protected: - void - SendServerMessageBufferTo( - const SockAddr& to, const SockAddr& from, llarp_buffer_t buf) override; - - private: - std::shared_ptr 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 + { + protected: + /// add a packet source to this server, does share ownership + void + AddPacketSource(std::shared_ptr resolver); + /// add a resolver to this packet handler, does share ownership + void + AddResolver(std::shared_ptr 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 + BoundPacketSourceAddrs() const; + + /// returns the first sockaddr we have on our packet sources if we have one + std::optional + FirstBoundPacketSourceAddr() const; + + /// add a resolver to this packet handler, does not share ownership + void + AddResolver(std::weak_ptr resolver); + + /// add a packet source to this server, does not share ownership + void + AddPacketSource(std::weak_ptr resolver); + + /// create a packet source bound on bindaddr but does not add it + virtual std::shared_ptr + 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 + 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 pktsource, + const SockAddr& resolver, + const SockAddr& from, + llarp::OwnedBuffer buf); + + protected: + EventLoop_ptr m_Loop; + llarp::DnsConfig m_Config; + + private: + std::set, ComparePtr>> + m_OwnedResolvers; + std::set, CompareWeakPtr> m_Resolvers; + + std::vector> m_PacketSources; + std::vector> m_OwnedPacketSources; + }; + +} // namespace llarp::dns diff --git a/llarp/endpoint_base.hpp b/llarp/endpoint_base.hpp index e924ab523..6bbd97bce 100644 --- a/llarp/endpoint_base.hpp +++ b/llarp/endpoint_base.hpp @@ -23,6 +23,11 @@ namespace llarp class TunnelManager; } + namespace dns + { + class Server; + } + class EndpointBase { std::unordered_set 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() const + { + return nullptr; + }; + /// called when srv data changes in some way virtual void SRVRecordsChanged() = 0; diff --git a/llarp/ev/ev_libuv.cpp b/llarp/ev/ev_libuv.cpp index d5f009f16..3f4784b94 100644 --- a/llarp/ev/ev_libuv.cpp +++ b/llarp/ev/ev_libuv.cpp @@ -72,6 +72,13 @@ namespace llarp::uv bool send(const SockAddr& dest, const llarp_buffer_t& buf) override; + std::optional + LocalAddr() const override + { + auto addr = handle->sock(); + return SockAddr{addr.ip, huint16_t{static_cast(addr.port)}}; + } + std::optional file_descriptor() override { diff --git a/llarp/ev/udp_handle.hpp b/llarp/ev/udp_handle.hpp index 8169bd8a1..9e8425cd5 100644 --- a/llarp/ev/udp_handle.hpp +++ b/llarp/ev/udp_handle.hpp @@ -33,6 +33,10 @@ namespace llarp return std::nullopt; } + /// returns the local address we are bound on + virtual std::optional + LocalAddr() const = 0; + // Base class destructor virtual ~UDPHandle() = default; diff --git a/llarp/handlers/exit.cpp b/llarp/handlers/exit.cpp index 00f1f8679..972dfeecc 100644 --- a/llarp/handlers/exit.cpp +++ b/llarp/handlers/exit.cpp @@ -18,11 +18,7 @@ namespace llarp namespace handlers { ExitEndpoint::ExitEndpoint(std::string name, AbstractRouter* r) - : m_Router(r) - , m_Resolver(std::make_shared(r->loop(), this)) - , m_Name(std::move(name)) - , m_LocalResolverAddr{"127.0.0.1:53"} - , m_QUIC{std::make_shared(*this)} + : m_Router(r), m_Name(std::move(name)), m_QUIC{std::make_shared(*this)} { m_ShouldInitTun = true; m_QUIC = std::make_shared(*this); @@ -211,6 +207,22 @@ namespace llarp return false; } + bool + ExitEndpoint::MaybeHookDNS( + std::weak_ptr source, + const dns::Message& query, + const SockAddr& to, + const SockAddr& from) + { + if (not ShouldHookDNSMessage(query)) + return false; + + auto job = std::make_shared(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 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(m_Router->loop(), dnsConfig); m_OurRange = networkConfig.m_ifaddr; if (!m_OurRange.addr.h) diff --git a/llarp/handlers/exit.hpp b/llarp/handlers/exit.hpp index 5c2f3ec41..9984a3431 100644 --- a/llarp/handlers/exit.hpp +++ b/llarp/handlers/exit.hpp @@ -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 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) override; + HandleHookedDNSMessage(dns::Message msg, std::function); void LookupServiceAsync( @@ -174,7 +199,7 @@ namespace llarp KickIdentOffExit(const PubKey& pk); AbstractRouter* m_Router; - std::shared_ptr m_Resolver; + std::shared_ptr m_Resolver; bool m_ShouldInitTun; std::string m_Name; bool m_PermitExit; diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 776b2edb1..4b5259146 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -27,26 +27,46 @@ #include #include +#include + #include namespace llarp { namespace handlers { + bool + TunEndpoint::MaybeHookDNS( + std::weak_ptr source, + const dns::Message& query, + const SockAddr& to, + const SockAddr& from) + { + if (not ShouldHookDNSMessage(query)) + return false; + + auto job = std::make_shared(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 + 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 PacketSource; + + virtual ~TunDNS() = default; + explicit TunDNS(TunEndpoint* ep, const llarp::DnsConfig& conf) + : dns::Server{ep->Router()->loop(), conf}, m_Endpoint{ep} + {} + + std::shared_ptr + MakePacketSourceOn(const SockAddr&, const llarp::DnsConfig& conf) override + { + auto ptr = std::make_shared(m_Endpoint, conf); + PacketSource = ptr; + return ptr; + } + + std::shared_ptr + 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( [this](net::IPPacket pkt) { HandleGotUserPacket(std::move(pkt)); }); -#if defined(ANDROID) || (defined(__APPLE__) && !defined(MACOS_SYSTEM_EXTENSION)) - m_Resolver = std::make_shared(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(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(r->loop(), this); + m_DNS = std::make_shared(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 resolvers; - for (const auto& addr : m_UpstreamResolvers) - resolvers.emplace_back(addr.ToString()); - obj["ustreamResolvers"] = resolvers; - obj["localResolver"] = m_LocalResolverAddr.ToString(); + + std::vector upstreamRes; + for (const auto& ent : m_DnsConfig.m_upstreamDNS) + upstreamRes.emplace_back(ent.ToString()); + obj["ustreamResolvers"] = upstreamRes; + + std::vector 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 TunEndpoint::ReconfigureDNS(std::vector 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(); } diff --git a/llarp/handlers/tun.hpp b/llarp/handlers/tun.hpp index 74161754b..e8e4ad22a 100644 --- a/llarp/handlers/tun.hpp +++ b/llarp/handlers/tun.hpp @@ -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(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 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 sendreply) override; + HandleHookedDNSMessage(dns::Message query, std::function sendreply); void TickTun(llarp_time_t now); @@ -96,6 +114,16 @@ namespace llarp bool SetupTun(); + void + SetupDNS(); + + /// overrides Endpoint + std::shared_ptr + 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 m_Resolver; + + /// dns subsystem for this endpoint + std::shared_ptr m_DNS; + + DnsConfig m_DnsConfig; /// maps ip address to timestamp last active std::unordered_map 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 m_UpstreamResolvers; - /// dns host files list - std::vector m_hostfiles; - /// local dns - IpAddress m_LocalResolverAddr; /// list of strict connect addresses for hooks std::vector m_StrictConnectAddrs; /// use v6? diff --git a/llarp/lokinet_shared.cpp b/llarp/lokinet_shared.cpp index f2177d3de..de8f48528 100644 --- a/llarp/lokinet_shared.cpp +++ b/llarp/lokinet_shared.cpp @@ -228,7 +228,7 @@ struct lokinet_context [[nodiscard]] std::optional make_udp_handler( const std::shared_ptr& 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 weak{ep}; auto udp = std::make_shared( - 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 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 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()) diff --git a/llarp/net/sock_addr.cpp b/llarp/net/sock_addr.cpp index 1a2d9c046..7f66e0aff 100644 --- a/llarp/net/sock_addr.cpp +++ b/llarp/net/sock_addr.cpp @@ -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(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 diff --git a/llarp/net/sock_addr.hpp b/llarp/net/sock_addr.hpp index 447c4771e..82ea367c7 100644 --- a/llarp/net/sock_addr.hpp +++ b/llarp/net/sock_addr.hpp @@ -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); diff --git a/llarp/router/route_poker.cpp b/llarp/router/route_poker.cpp index b24b88476..c92aa741f 100644 --- a/llarp/router/route_poker.cpp +++ b/llarp/router/route_poker.cpp @@ -2,6 +2,7 @@ #include "abstractrouter.hpp" #include "net/sock_addr.hpp" #include +#include #include 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 diff --git a/llarp/router/route_poker.hpp b/llarp/router/route_poker.hpp index f494fada8..17969a2b5 100644 --- a/llarp/router/route_poker.hpp +++ b/llarp/router/route_poker.hpp @@ -5,7 +5,6 @@ #include #include #include -#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(); diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 62a42049d..62f17e9e5 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -1217,12 +1217,6 @@ namespace llarp return true; } - int - Router::OutboundUDPSocket() const - { - return m_OutboundUDPSocket; - } - bool Router::Run() { diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 3500ed9cf..dc4b901a1 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -211,9 +211,6 @@ namespace llarp bool ShouldTestOtherRouters() const; - int - OutboundUDPSocket() const override; - std::optional _ourAddress; EventLoop_ptr _loop; diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 957bba6db..175ccdf91 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -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)> func; + + public: + SockAddr dumb; + + template + DummyPacketSource(Callable&& f) : func{std::forward(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 + BoundOn() const override + { + return std::nullopt; + } + }; + /// a function that replies to an rpc request using ReplyFunction_t = std::function; @@ -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(ep_ptr.get())) + if (auto dns = ep_ptr->DNS()) { - if (ep->ShouldHookDNSMessage(msg)) + auto src = std::make_shared([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")); diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index a90752961..64b8b68ab 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -26,6 +26,7 @@ #include "auth.hpp" #include +#include // minimum time between introset shifts #ifndef MIN_SHIFT_INTERVAL diff --git a/llarp/util/buffer.cpp b/llarp/util/buffer.cpp index f02eae652..15cfca265 100644 --- a/llarp/util/buffer.cpp +++ b/llarp/util/buffer.cpp @@ -134,6 +134,16 @@ operator==(const llarp_buffer_t& buff, std::string_view data) } namespace llarp { + std::vector + OwnedBuffer::copy() const + { + std::vector 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) { diff --git a/llarp/util/buffer.hpp b/llarp/util/buffer.hpp index 841945d03..08010616f 100644 --- a/llarp/util/buffer.hpp +++ b/llarp/util/buffer.hpp @@ -250,6 +250,12 @@ namespace llarp explicit OwnedBuffer(size_t sz) : OwnedBuffer{std::make_unique(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 + copy() const; }; } // namespace llarp diff --git a/llarp/util/compare_ptr.hpp b/llarp/util/compare_ptr.hpp index e203688b9..4c3ec7cc7 100644 --- a/llarp/util/compare_ptr.hpp +++ b/llarp/util/compare_ptr.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include namespace llarp { @@ -16,4 +17,16 @@ namespace llarp return Compare()(left, right); } }; + + /// type for comparing weak_ptr by value + template > + struct CompareWeakPtr + { + bool + operator()(const std::weak_ptr& left, const std::weak_ptr& right) const + { + return ComparePtr, Compare>{}(left.lock(), right.lock()); + } + }; + } // namespace llarp diff --git a/pybind/llarp/config.cpp b/pybind/llarp/config.cpp index 9b705bc5a..4ec4c0131 100644 --- a/pybind/llarp/config.cpp +++ b/pybind/llarp/config.cpp @@ -69,12 +69,12 @@ namespace llarp py::class_(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_(mod, "ApiConfig") diff --git a/test/mocks/mock_network.hpp b/test/mocks/mock_network.hpp index f5a47548e..54e92a44a 100644 --- a/test/mocks/mock_network.hpp +++ b/test/mocks/mock_network.hpp @@ -12,12 +12,19 @@ namespace mocks class MockUDPHandle : public llarp::UDPHandle { Network* const _net; + std::optional _addr; public: MockUDPHandle(Network* net, llarp::UDPHandle::ReceiveFunc recv) : llarp::UDPHandle{recv}, _net{net} {} + std::optional + 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