From 4ef25ef679785fc5c34303b25d5594360699a67c Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 28 Apr 2021 16:48:10 -0300 Subject: [PATCH] Add systemd-resolved dynamic DNS updating Wires up systemd support to configure DNS on startup and when enabling/disabling exit mode. On startup (and when turning off an exit) we tell systemd-resolved to direct .loki and .snode lookups to lokinet (leaving other DNS traffic alone). On exit enabling, we reconfigure it to resolve "." (i.e. the root DNS domain) so that all lookups come into it. --- contrib/systemd-resolved/README.md | 18 +++- contrib/systemd-resolved/lokinet.conf | 3 - contrib/systemd-resolved/lokinet.pkla | 4 + contrib/systemd-resolved/lokinet.rules | 9 ++ llarp/CMakeLists.txt | 1 + llarp/handlers/tun.cpp | 5 + llarp/router/route_poker.cpp | 11 +++ llarp/router/route_poker.hpp | 1 + llarp/router/systemd_resolved.cpp | 129 +++++++++++++++++++++++++ llarp/router/systemd_resolved.hpp | 18 ++++ 10 files changed, 194 insertions(+), 5 deletions(-) delete mode 100644 contrib/systemd-resolved/lokinet.conf create mode 100644 contrib/systemd-resolved/lokinet.pkla create mode 100644 contrib/systemd-resolved/lokinet.rules create mode 100644 llarp/router/systemd_resolved.cpp create mode 100644 llarp/router/systemd_resolved.hpp diff --git a/contrib/systemd-resolved/README.md b/contrib/systemd-resolved/README.md index e54f02f18..5cd18d98e 100644 --- a/contrib/systemd-resolved/README.md +++ b/contrib/systemd-resolved/README.md @@ -1,6 +1,20 @@ -To be put at `/usr/lib/systemd/resolved.conf.d/lokinet.conf` for distro use and `/etc/systemd/resolved.conf.d/lokinet.conf` for local admin use. +Lokinet now talks to systemd directly via sdbus to set up DNS, but in order for this to work the +user running lokinet (assumed `_lokinet` in these example files) needs permission to set dns servers +and domains. -To make use of it: +To set up the permissions: + +- If lokinet is running as some user other than `_lokinet` the change the `_lokinet` username inside + `lokinet.rules` and `lokinet.pkla`. + +- If on a Debian or Debian-derived distribution (such as Ubuntu) using polkit 105, + copy `lokinet.pkla` to `/var/lib/polkit-1/localauthority/10-vendor.d/lokinet.pkla` (for a distro + install) or `/etc/polkit-1/localauthority.conf.d/` (for a local install). + +- Copy `lokinet.rules` to `/usr/share/polkit-1/rules.d/` (distro install) or `/etc/polkit-1/rules.d` + (local install). + +Make use of it by switching to systemd-resolved: ``` sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf sudo systemctl enable --now systemd-resolved diff --git a/contrib/systemd-resolved/lokinet.conf b/contrib/systemd-resolved/lokinet.conf deleted file mode 100644 index 007db4fec..000000000 --- a/contrib/systemd-resolved/lokinet.conf +++ /dev/null @@ -1,3 +0,0 @@ -[Resolve] -DNS=127.3.2.1 -Domains=~loki ~snode diff --git a/contrib/systemd-resolved/lokinet.pkla b/contrib/systemd-resolved/lokinet.pkla new file mode 100644 index 000000000..ec7ad7098 --- /dev/null +++ b/contrib/systemd-resolved/lokinet.pkla @@ -0,0 +1,4 @@ +[Allow lokinet to set DNS settings] +Identity=unix-user:_lokinet +Action=org.freedesktop.resolve1.set-dns-servers;org.freedesktop.resolve1.set-domains +ResultAny=yes diff --git a/contrib/systemd-resolved/lokinet.rules b/contrib/systemd-resolved/lokinet.rules new file mode 100644 index 000000000..60bbfb3e3 --- /dev/null +++ b/contrib/systemd-resolved/lokinet.rules @@ -0,0 +1,9 @@ +/* Allow lokinet to set DNS settings */ +polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.resolve1.set-dns-servers" || + action.id == "org.freedesktop.resolve1.set-domains") && + subject.user == "_lokinet") { + return polkit.Result.YES; + } +}); + diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 279b621a4..7314971f3 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -186,6 +186,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/handlers/tun.cpp b/llarp/handlers/tun.cpp index ba7a1ac7f..441bf0843 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -784,6 +785,10 @@ namespace llarp Router()->loop()->add_ticker([this] { Flush(); }); + // Attempt to register DNS on the interface + systemd_resolved_set_dns(m_IfName, m_LocalResolverAddr.createSockAddr(), + false /* just .loki/.snode DNS initially */); + if (m_OnUp) { m_OnUp->NotifyAsync(NotifyParams()); diff --git a/llarp/router/route_poker.cpp b/llarp/router/route_poker.cpp index 6ad13d147..251216344 100644 --- a/llarp/router/route_poker.cpp +++ b/llarp/router/route_poker.cpp @@ -1,5 +1,6 @@ #include "route_poker.hpp" #include "abstractrouter.hpp" +#include "net/sock_addr.hpp" #include #include #include @@ -157,6 +158,11 @@ namespace llarp Update(); m_Enabling = false; m_Enabled = true; + + systemd_resolved_set_dns( + m_Router->hiddenServiceContext().GetDefault()->GetIfName(), + m_Router->GetConfig()->dns.m_bind.createSockAddr(), + true /* route all DNS */); } void @@ -167,6 +173,11 @@ namespace llarp DisableAllRoutes(); m_Enabled = false; + + systemd_resolved_set_dns( + m_Router->hiddenServiceContext().GetDefault()->GetIfName(), + m_Router->GetConfig()->dns.m_bind.createSockAddr(), + false /* route DNS only for .loki/.snode */); } void diff --git a/llarp/router/route_poker.hpp b/llarp/router/route_poker.hpp index 3ea8c694a..f494fada8 100644 --- a/llarp/router/route_poker.hpp +++ b/llarp/router/route_poker.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "systemd_resolved.hpp" namespace llarp { diff --git a/llarp/router/systemd_resolved.cpp b/llarp/router/systemd_resolved.cpp new file mode 100644 index 000000000..295ca9c17 --- /dev/null +++ b/llarp/router/systemd_resolved.cpp @@ -0,0 +1,129 @@ +#include "systemd_resolved.hpp" +#include + +#include + +extern "C" { +#include +#include +} + +using namespace std::literals; + +namespace llarp { + +#ifndef WITH_SYSTEMD + + bool systemd_resolved_set_dns(std::string, llarp::SockAddr, bool) { + LogDebug("lokinet is not build with systemd support, cannot set systemd resolved DNS"); + return false; + } + +#else + + namespace { + template + void resolved_call(sd_bus* bus, const char* method, const char* arg_format, T... args) { + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *msg = nullptr; + int r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + method, + &error, + &msg, + arg_format, + args...); + + if (r < 0) + throw std::runtime_error{"sdbus resolved "s + method + " failed: " + strerror(-r)}; + + sd_bus_message_unref(msg); + sd_bus_error_free(&error); + } + + struct sd_bus_deleter { void operator()(sd_bus* ptr) const { sd_bus_unref(ptr); } }; + } + + bool systemd_resolved_set_dns(std::string ifname, llarp::SockAddr dns, bool global) { + unsigned int if_ndx = if_nametoindex(ifname.c_str()); + if (if_ndx == 0) { + LogWarn("No such interface '", ifname, "'"); + return false; + } + + // Connect to the system bus + sd_bus *bus = nullptr; + int r = sd_bus_open_system(&bus); + if (r < 0) { + LogWarn("Failed to connect to system bus to set DNS: ", strerror(-r)); + return false; + } + std::unique_ptr bus_ptr{bus}; + + try { + // This passing address by bytes and using two separate calls for ipv4/ipv6 is gross, but the + // alternative is to build up a bunch of crap with va_args, which is slightly more gross. + if (dns.isIPv6()) { + auto ipv6 = dns.getIPv6(); + static_assert(sizeof(ipv6) == 16); + auto* a = reinterpret_cast(&ipv6); + resolved_call(bus, "SetLinkDNSEx", "ia(iayqs)", + (int32_t) if_ndx, + (int) 1, // number of "iayqs"s we are passing + (int32_t) AF_INET6, // network address type + (int) 16, // network addr byte size + a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], // yuck + (uint16_t) dns.getPort(), + nullptr // dns server name (for TLS SNI which we don't care about) + ); + } + else + { + auto ipv4 = dns.getIPv4(); + static_assert(sizeof(ipv4) == 4); + auto* a = reinterpret_cast(&ipv4); + resolved_call(bus, "SetLinkDNSEx", "ia(iayqs)", + (int32_t) if_ndx, + (int) 1, // number of "iayqs"s we are passing + (int32_t) AF_INET, // network address type + (int) 4, // network addr byte size + a[0], a[1], a[2], a[3], // yuck + (uint16_t) dns.getPort(), + nullptr // dns server name (for TLS SNI which we don't care about) + ); + } + + if (global) + // Setting "." as a routing domain gives this DNS server higher priority in resolution + // compared to dns servers that are set without a domain (e.g. the default for a + // DHCP-configured DNS server) + resolved_call(bus, "SetLinkDomains", "ia(sb)", + (int32_t) if_ndx, + (int) 1, // array size + "." // global DNS root + ); + else + // Only resolve .loki and .snode through lokinet (so you keep using your local DNS server for + // everything else, which is nicer than forcing everything though lokinet's upstream DNS). + resolved_call(bus, "SetLinkDomains", "ia(sb)", + (int32_t) if_ndx, + (int) 2, // array size + "loki", // domain + (int) 1, // routing domain = true + "snode", // domain + (int) 1 // routing domain = true + ); + + return true; + + } catch (const std::exception& e) { + LogWarn("Failed to set DNS via systemd-resolved: ", e.what()); + } + return false; + } + +#endif // WITH_SYSTEMD + +} // namespace llarp diff --git a/llarp/router/systemd_resolved.hpp b/llarp/router/systemd_resolved.hpp new file mode 100644 index 000000000..c07cc6f89 --- /dev/null +++ b/llarp/router/systemd_resolved.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace llarp +{ + /// 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). + /// + /// \param if_name -- the interface name to which we add the DNS servers, e.g. lokitun0. + /// Typically tun_endpoint.GetIfName(). + /// \param dns -- the listening address of the lokinet DNS server + /// \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); +}