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