How to allow systemd-resolved to listen to an interface other than loopback

dnsdockersystemdsystemd-resolved

systemd-resolved is a daemon that, among other things, acts as a DNS server by listening IP address 127.0.0.53 on the local loopback interface.

I would like to let the daemon listen to another interface. My use-case is to expose it to docker containers, so that docker containers share the DNS caching provided by systemd-resolved. I know how to configure the host as a DNS server for docker containers, but at least by default, systemd-resolved rejects these DNS queries because they are not coming from the loopback interface, but from the docker bridge interface.

With dnsmasq (a tool similar to systemd-resolved), I did this by adding listen-address=172.17.0.1 to the configuration file. Unfortunately, I couldn't find a systemd-resolved equivalent.

Since systemd-resolved is the default at least on Ubuntu 18.04, I would like a solution that works in this configuration.

Is there a way to configure which interface systemd-resolved listens on?

Best Answer

You can't. As cristian-rodríguez mentioned above, it was strictly designed to provide services to loopback only.

Not even an alternative solution using net.ipv4.conf.all.route_localnet=1 + iptables NAT (such as https://serverfault.com/questions/211536/iptables-port-redirect-not-working-for-localhost), https://superuser.com/questions/594163/how-do-i-route-a-port-range-in-a-linux-host-to-a-guest-vm), and https://stackoverflow.com/questions/18580637/iptables-redirect-from-external-interface-to-loopbacks-port) will work, since systemd-resolve explicitly inspects if the destination is outside the loopback network. See the code below for static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p)

    if (in_addr_is_localhost(p->family, &p->sender) <= 0 ||
        in_addr_is_localhost(p->family, &p->destination) <= 0) {
            log_error("Got packet on unexpected IP range, refusing.");
            dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL, false);
            goto fail;
    }

A workaround is to use socat to listen at your docker interface, and forward it to systemd-resolved. The line below does the trick. If required, alter it to listen to TCP:

socat UDP-LISTEN:53,fork,reuseaddr,bind=172.17.0.1 UDP:127.0.0.53:53