#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