From 2aae56b0e05be7e04b9931b1aeb0550008e47447 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Fri, 16 Sep 2022 12:23:25 -0300 Subject: [PATCH] Apple DNS configuration fix: don't obliterate trampoline On Apple, the network extension is outside the tunnel routing, so we cannot have libunbound talk directly to upstream (it would leak DNS when exit mode is enabled). Instead unbound *always* talks to a localhost port where we have a "dns trampoline" that takes UDP packets and shoves them through the tunnel. We were doing that already, but recent changes here were overwriting the libunbound settings with. This also moves the upstream DNS configuration part of `Up()` into its own method. --- llarp/dns/server.cpp | 174 ++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 83 deletions(-) diff --git a/llarp/dns/server.cpp b/llarp/dns/server.cpp index fe3565e3b..7c9cf4a14 100644 --- a/llarp/dns/server.cpp +++ b/llarp/dns/server.cpp @@ -171,62 +171,38 @@ namespace llarp::dns 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"; - } - - virtual std::optional - GetLocalAddr() const override + void ConfigureUpstream(const llarp::DnsConfig& conf) { - return m_LocalAddr; - } - - void - Up(const llarp::DnsConfig& conf) - { - // set libunbound settings - - SetOpt("do-tcp:", "no"); - - for (const auto& [k, v] : conf.m_ExtraOpts) - SetOpt(k, v); + auto* ctx = m_ctx.get(); - // add host files - for (const auto& file : conf.m_hostfiles) + if constexpr (platform::is_apple) { - 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))}; - } + // On Apple, when we turn on exit mode, we can't directly connect to upstream from here + // because, from within the network extension, macOS ignores setting the tunnel as the + // default route and would leak all DNS; instead we have to bounce things through the + // objective C trampoline code (which is what actually handles the upstream querying) so + // that it can call into Apple's special snowflake API to set up a socket that has the + // magic Apple snowflake sauce added on top so that it actually routes through the tunnel + // instead of around it. + // + // This behaviour is all carefully and explicitly documented by Apple with plenty of + // examples and other exposition, of course, just like all of their wonderful new APIs to + // reinvent standard unix interfaces. + + // Not at all clear why this is needed but without it we get "send failed: Can't + // assign requested address" when unbound tries to connect to the localhost address + // using a source address of 0.0.0.0. Yay apple. + ub_ctx_set_option(ctx, "outgoing-interface:", "127.0.0.1"); + + // The trampoline expects just a single source port (and sends everything back to it) + ub_ctx_set_option(ctx, "outgoing-range:", "1"); + ub_ctx_set_option(ctx, "outgoing-port-avoid:", "0-65535"); + ub_ctx_set_option( + ctx, + "outgoing-port-permit:", + std::to_string(apple::dns_trampoline_source_port).c_str()); + + return; } // set up forward dns @@ -237,43 +213,14 @@ namespace llarp::dns if (const auto port = dns.getPort(); port != 53) fmt::format_to(std::back_inserter(str), "@{}", port); - log::info(logcat, "Using upstream dns {}", str); + log::critical(logcat, "Using upstream dns {}", str); - auto* ctx = m_ctx.get(); if (auto err = ub_ctx_set_fwd(ctx, str.c_str())) { throw std::runtime_error{ fmt::format("cannot use {} as upstream dns: {}", str, ub_strerror(err))}; } - if constexpr (platform::is_apple) - { - // On Apple, when we turn on exit mode, we can't directly connect to upstream from here - // because, from within the network extension, macOS ignores setting the tunnel as the - // default route and would leak all DNS; instead we have to bounce things through the - // objective C trampoline code so that it can call into Apple's special snowflake API to - // set up a socket that has the magic Apple snowflake sauce added on top so that it - // actually routes through the tunnel instead of around it. - // - // This behaviour is all carefully and explicitly documented by Apple with plenty of - // examples and other exposition, of course, just like all of their wonderful new APIs - // to reinvent standard unix interfaces. - if (dns.hostString() == "127.0.0.1" && dns.getPort() == apple::dns_trampoline_port) - { - // Not at all clear why this is needed but without it we get "send failed: Can't - // assign requested address" when unbound tries to connect to the localhost address - // using a source address of 0.0.0.0. Yay apple. - ub_ctx_set_option(ctx, "outgoing-interface:", "127.0.0.1"); - - // The trampoline expects just a single source port (and sends everything back to it) - ub_ctx_set_option(ctx, "outgoing-range:", "1"); - ub_ctx_set_option(ctx, "outgoing-port-avoid:", "0-65535"); - ub_ctx_set_option( - ctx, - "outgoing-port-permit:", - std::to_string(apple::dns_trampoline_source_port).c_str()); - } - } } if (auto maybe_addr = conf.m_QueryBind) @@ -331,6 +278,67 @@ namespace llarp::dns SetOpt("outgoing-port-avoid:", "0-65535"); SetOpt("outgoing-port-permit:", std::to_string(addr.getPort())); } + } + + 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"; + } + + virtual std::optional + GetLocalAddr() const override + { + return m_LocalAddr; + } + + void + Up(const llarp::DnsConfig& conf) + { + // set libunbound settings + + SetOpt("do-tcp:", "no"); + + 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))}; + } + } + + ConfigureUpstream(conf); // set async ub_ctx_async(m_ctx.get(), 1);