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.
This commit is contained in:
Jason Rhinelander 2021-04-28 16:48:10 -03:00
parent 35e4e8817b
commit 4ef25ef679
10 changed files with 194 additions and 5 deletions

View File

@ -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

View File

@ -1,3 +0,0 @@
[Resolve]
DNS=127.3.2.1
Domains=~loki ~snode

View File

@ -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

View File

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

View File

@ -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

View File

@ -13,6 +13,7 @@
#include <llarp/ev/ev.hpp>
#include <llarp/net/net.hpp>
#include <llarp/router/abstractrouter.hpp>
#include <llarp/router/systemd_resolved.hpp>
#include <llarp/service/context.hpp>
#include <llarp/service/outbound_context.hpp>
#include <llarp/service/endpoint_state.hpp>
@ -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());

View File

@ -1,5 +1,6 @@
#include "route_poker.hpp"
#include "abstractrouter.hpp"
#include "net/sock_addr.hpp"
#include <llarp/net/route.hpp>
#include <llarp/service/context.hpp>
#include <unordered_set>
@ -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

View File

@ -5,6 +5,7 @@
#include <memory>
#include <optional>
#include <llarp/net/net_int.hpp>
#include "systemd_resolved.hpp"
namespace llarp
{

View File

@ -0,0 +1,129 @@
#include "systemd_resolved.hpp"
#include <llarp/util/logging/logger.hpp>
#include <stdexcept>
extern "C" {
#include <systemd/sd-bus.h>
#include <net/if.h>
}
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 <typename... T>
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<sd_bus, sd_bus_deleter> 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<const uint8_t*>(&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<const uint8_t*>(&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

View File

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <llarp/net/sock_addr.hpp>
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);
}