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); +}