diff --git a/README.md b/README.md index 8aa7d8bb839731d1edda48b060596ade2f09e3a4..e3e2e1a02a295e00dbe70a26a02ba5679db4ce1e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ Provides systemd-resolved and docker DNS integration. -A DNS server is configured to listen on each docker interface's IP address. This is used to: -1. allows containers to be referenced by hostname by adding the created DNS servers to the docker interface using - the systemd-resolved D-Bus API. -1. expose the systemd-resolved DNS service (`127.0.0.53`) to docker containers by proxying DNS requests, which doesn't - work by default due to the differing network namespaces. +1. A DNS server is configured to listen on the docker interface's IP address. This is used to expose the systemd-resolved + DNS service (`127.0.0.53`) to docker containers by proxying DNS requests, which doesn't work by default due to the + differing network namespaces. + +2. Allows containers to be referenced by hostname by adding a DNS servers to a dummy interface using the systemd-resolved + D-Bus API. ## Features @@ -70,7 +71,7 @@ an exact match is required. If a generated domain address doesn't match the list 4. `<service>.<project>.<default_domain>`, `<service>.<project>`, `<container_number>.<service>.<project>.<default_domain>`, `<container_number>.<service>.<project>` - If `docker-compose` is then names will be generated based on the service and project's names. If a service has + If `docker-compose` is used then names will be generated based on the service and project names. If a service has multiple containers then the reply will contain all instances: ```sh host webserver.someproject.docker # webserver.someproject.docker has address 172.16.238.3 @@ -85,12 +86,16 @@ If configured correctly then `resolvectl status` should show the configured link $ resolvectl status ... - Link 7 (docker0) + Link 7 (srd-dummy) Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6 Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported - DNS Servers: 172.17.0.1 + DNS Servers: 127.0.0.153 DNS Domain: ~docker - ... + ... + +A dummy interface (`srd-dummy` by default) is created to add the custom DNS server to systemd-resolved. This is required +because the lifecycle of the `docker0` depends on there being running containers on the default network, even if there +are running containers on other networks. ### 127.0.0.53 / systemd-resolved within containers @@ -123,7 +128,7 @@ If there are link-local, VPN or other DNS servers configured then those will als | Name | Description | Default Value | Example | |-----------------------------------|-------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|-----------------------------------| | DNS_SERVER | DNS server to use when resolving queries from docker containers. | `127.0.0.53` - systemd-resolved DNS server | `127.0.0.53` | -| DOCKER_INTERFACE | Docker interface name | The first docker network's interface | `docker0` | +| SYSTEMD_RESOLVED_INTERFACE | Dummy interface name which will be created to interface with systemd-resolved | `srd-dummy` | `srd-dummy` | | SYSTEMD_RESOLVED_LISTEN_ADDRESS | IPs (+port) to listen on for queries from systemd-resolved. | `127.0.0.153` | `127.0.0.153:1053` | | DOCKER_LISTEN_ADDRESS | IPs (+port) to listen on for queries from docker containers in the default network. | _ip of the default docker bridge_, often `172.17.0.1` | `172.17.0.1` or `172.17.0.1:53` | | ALLOWED_DOMAINS | Domain which will be handled by the DNS server. If a domain starts with `.` then all subdomains will also be allowed. | `.docker` | `.docker,.local` | diff --git a/src/systemd_resolved_docker/cli.py b/src/systemd_resolved_docker/cli.py index d662766aec3942e512bc3319aded40c8fe35d0fd..e25c2c7910b37bbd6c8d6d0bcd6e3d8322c758ec 100644 --- a/src/systemd_resolved_docker/cli.py +++ b/src/systemd_resolved_docker/cli.py @@ -8,7 +8,8 @@ from systemd import daemon from .dockerdnsconnector import DockerDNSConnector from .resolvedconnector import SystemdResolvedConnector -from .utils import find_default_docker_bridge_gateway, parse_ip_port, parse_listen_address +from .utils import find_default_docker_bridge_gateway, parse_ip_port, parse_listen_address, remove_dummy_interface, \ + create_dummy_interface class Handler: @@ -32,6 +33,7 @@ class Handler: def main(): + systemd_resolved_interface = os.environ.get("SYSTEMD_RESOLVED_INTERFACE", "srd-dummy") systemd_resolved_listen_address = os.environ.get("SYSTEMD_RESOLVED_LISTEN_ADDRESS", None) docker_listen_address = os.environ.get("DOCKER_LISTEN_ADDRESS", None) dns_server = parse_ip_port(os.environ.get("UPSTREAM_DNS_SERVER", "127.0.0.53")) @@ -46,10 +48,6 @@ def main(): cli = docker.from_env() docker_gateway = find_default_docker_bridge_gateway(cli) - systemd_resolved_interface = os.environ.get('DOCKER_INTERFACE', None) - if systemd_resolved_interface is None or len(systemd_resolved_interface) < 1: - systemd_resolved_interface = docker_gateway[0]['interface'] - handler = Handler() handler.log("Default domain: %s, allowed domains: %s" % (default_domain, ", ".join(domains))) @@ -59,6 +57,10 @@ def main(): lambda: [parse_ip_port(entry['gateway']) for entry in docker_gateway]) + handler.log("Creating interface %s" % systemd_resolved_interface) + remove_dummy_interface(systemd_resolved_interface) + create_dummy_interface(systemd_resolved_interface, systemd_resolved_listen_addresses) + resolved = SystemdResolvedConnector(systemd_resolved_interface, systemd_resolved_listen_addresses, domains, handler) dns_connector = DockerDNSConnector(systemd_resolved_listen_addresses + docker_listen_addresses, dns_server, domains, @@ -72,6 +74,9 @@ def main(): resolved.unregister() dns_connector.stop() + handler.log("Removing interface %s" % systemd_resolved_interface) + remove_dummy_interface(systemd_resolved_interface) + signal.signal(signal.SIGTERM, sig_handler) signal.signal(signal.SIGINT, sig_handler) diff --git a/src/systemd_resolved_docker/utils.py b/src/systemd_resolved_docker/utils.py index 2ce72d5c228afd4a8df21d50662ce76beed5512a..7c07a4505c221662e1bd01669e0392725f968dbe 100644 --- a/src/systemd_resolved_docker/utils.py +++ b/src/systemd_resolved_docker/utils.py @@ -1,5 +1,6 @@ import ipaddress import urllib.parse +from pyroute2 import NDB from typing import List @@ -56,3 +57,20 @@ def find_default_docker_bridge_gateway(cli): addresses.append({'gateway': gateway, 'interface': name}) return addresses + + +def create_dummy_interface(interface, ip_addresses): + with NDB(log='on') as ndb: + nbd_if = ndb.interfaces.create(ifname=interface, kind="dummy").set(state="up") + for ip_address in ip_addresses: + nbd_if = nbd_if.add_ip("%s/%s" % ( + ip_address.ip.exploded, "32" if isinstance(ip_address.ip, ipaddress.IPv4Address) else "128")) + + nbd_if.commit() + + +def remove_dummy_interface(interface): + with NDB(log='on') as ndb: + ndbif = ndb.interfaces.get(interface) + if ndbif is not None: + ndbif.remove().commit() diff --git a/systemd-resolved-docker.sysconfig b/systemd-resolved-docker.sysconfig index c6afc322e621350659074425372147c2ef727a65..a6845c3289cbaa9ae3cfe6a38ce98229e3d204e1 100644 --- a/systemd-resolved-docker.sysconfig +++ b/systemd-resolved-docker.sysconfig @@ -2,9 +2,9 @@ ## default: 127.0.0.53 # DNS_SERVER=127.0.0.53 -## Docker interface name -## default: first docker network's interface -# DOCKER_INTERFACE=docker0 +## Dummy interface name which will be created to interface with systemd-resolved. +## default: srd-dummy +# SYSTEMD_RESOLVED_INTERFACE=srd-dummy ## IPs (+port) to listen on for queries from systemd-resolved. ## default: 127.0.0.153