Skip to content
Snippets Groups Projects
Commit 125338c3 authored by Zsombor Welker's avatar Zsombor Welker
Browse files

Create a dummy interface to intergate with systemd-resolved

This is required due to two things:

1. The docker0 interfaces is only "up" when there are containers on the
   default network.
2. systemd-resolved only takes into account interfaces which are up.

If containers were only running in non-default networks (for example
using docker-compose) the systemd-resolved DNS configuration would not
be used since the docker0 interface is down.

To work around this a dummy interface is created which is always up.
parent 97e67f72
Branches
Tags
No related merge requests found
......@@ -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,13 +86,17 @@ 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
If docker is configured to use the provided DNS server then the container domain names may also be 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` |
......
......@@ -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)
......
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()
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment