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

Allow specifying both the listen IPs and ports

The configuration is also split to allow explicitly specifying the
listen address to use with systemd-resolved and the "other" listen
addresses.
parent f49ec30b
No related branches found
No related tags found
No related merge requests found
......@@ -115,6 +115,21 @@ Host test.docker not found: 3(NXDOMAIN)
If there are link-local, VPN or other DNS servers configured then those will also work within containers.
## Configuration
`systemd-resolved-docker` may be configured using environment variables. When installed using the RPM
`/etc/sysconfig/systemd-resolved-docker` may also be modified to update the environment variables.
| 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_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` |
| DEFAULT_DOMAIN | Domain to append to hostnames which are not allowed by `ALLOWED_DOMAINS`. | `docker` | `docker` |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------- |
## Install
### Fedora / COPR
......@@ -155,20 +170,6 @@ For Fedora and RPM based systems [COPR](https://copr.fedorainfracloud.org/coprs/
unmanaged-devices=interface-name:docker0
```
### Configuration
`systemd-resolved-docker` may be configured using environment variables. When installed using the RPM
`/etc/sysconfig/systemd-resolved-docker` may also be modified to update the environment variables.
| 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` |
| LISTEN_ADDRESS | IPs to listen on for queries from systemd-resolved and docker containers. | _ip of the default docker bridge_, often `172.17.0.1` | `172.17.0.1,127.0.0.153` |
| LISTEN_PORT | Port to listen on for queries from systemd-resolved and docker containers. | `53` | `1053` |
| 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` |
| DEFAULT_DOMAIN | Domain to append to hostnames which are not allowed by `ALLOWED_DOMAINS`. | `docker` | `docker` |
## Build
`setup.py` may be used to create a python package.
......
#!/usr/bin/env python3
import os
import docker
import signal
from systemd import daemon, journal
from systemd_resolved_docker.resolvedconnector import SystemdResolvedConnector
import docker
from systemd import daemon
from .dockerdnsconnector import DockerDNSConnector
from .utils import find_default_docker_bridge_gateway, find_docker_dns_servers
from .resolvedconnector import SystemdResolvedConnector
from .utils import find_default_docker_bridge_gateway, parse_ip_port, parse_listen_address
class Handler:
......@@ -16,8 +17,10 @@ class Handler:
self.log("Started daemon")
def on_update(self, hosts):
message = "Refreshed - %d items (%s)" % (
len(hosts), ' '.join(["%s/%s" % (host.ip, ','.join(host.host_names)) for host in hosts]))
if len(hosts) > 0:
message = "Refreshed - %d items\n\t%s" % (len(hosts), '\n\t'.join(map(lambda x: str(x), hosts)))
else:
message = "Refreshed - no running containers"
self.log(message)
......@@ -29,10 +32,10 @@ class Handler:
def main():
dns_server = os.environ.get("DNS_SERVER", "127.0.0.53")
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"))
default_domain = os.environ.get("DEFAULT_DOMAIN", "docker")
listen_port = int(os.environ.get("LISTEN_PORT", "53"))
listen_address = os.environ.get("LISTEN_ADDRESS", None)
tld = os.environ.get('ALLOWED_DOMAINS', None)
if tld is None or len(tld.strip()) == 0:
......@@ -41,25 +44,25 @@ def main():
domains = [item.strip() for item in tld.split(',')]
cli = docker.from_env()
docker_dns_servers = find_docker_dns_servers(cli)
docker_gateway = find_default_docker_bridge_gateway(cli)
if listen_address is None or len(listen_address) < 1:
listen_addresses = [entry['gateway'] for entry in docker_gateway]
else:
listen_addresses = listen_address.split(",")
interface = os.environ.get('DOCKER_INTERFACE', None)
if interface is None or len(interface) < 1:
interface = docker_gateway[0]['interface']
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)))
resolved = SystemdResolvedConnector(interface, listen_addresses, listen_port, domains)
systemd_resolved_listen_addresses = parse_listen_address(systemd_resolved_listen_address,
lambda: [parse_ip_port("127.0.0.153:53")])
docker_listen_addresses = parse_listen_address(docker_listen_address,
lambda: [parse_ip_port(entry['gateway']) for entry in
docker_gateway])
resolved = SystemdResolvedConnector(systemd_resolved_interface, systemd_resolved_listen_addresses, domains, handler)
dns_connector = DockerDNSConnector(listen_addresses, listen_port, dns_server, domains, default_domain, interface,
handler, cli)
dns_connector = DockerDNSConnector(systemd_resolved_listen_addresses + docker_listen_addresses, dns_server, domains,
default_domain, handler, cli)
dns_connector.start()
resolved.register()
......
import threading
from typing import List
from dnslib import A, CLASS, DNSLabel, QTYPE, RR
from dnslib.proxy import ProxyResolver
......@@ -6,19 +7,16 @@ from dnslib.server import DNSServer
from .dockerwatcher import DockerWatcher, DockerHost
from .interceptresolver import InterceptResolver
from .utils import IpAndPort
from .zoneresolver import ZoneResolver
class DockerDNSConnector:
def __init__(self, listen_addresses, listen_port, upstream_dns_server, dns_domains, default_domain,
docker_interface, handler, cli):
def __init__(self, listen_addresses: List[IpAndPort], upstream_dns_server: IpAndPort, dns_domains, default_domain,
handler, cli):
super().__init__()
self.listen_addresses = listen_addresses
self.upstream_dns_server = upstream_dns_server
self.default_domain = default_domain
self.dns_domains = dns_domains
self.docker_interface = docker_interface
self.handler = handler
self.dns_domains_globs = ['*%s' % domain if domain.startswith('.') else domain for domain in dns_domains]
......@@ -27,14 +25,15 @@ class DockerDNSConnector:
self.servers = []
resolver = InterceptResolver(self.dns_domains_globs, self.resolver,
ProxyResolver(upstream_dns_server, port=53, timeout=5))
self.handler.log("Unhandled DNS requests will be resolved using %s:53" % upstream_dns_server)
for address in listen_addresses:
server = DNSServer(resolver, address=address, port=listen_port)
server.thread_name = "%s:%s" % (address, listen_port)
ProxyResolver(upstream_dns_server.ip.exploded, port=upstream_dns_server.port,
timeout=5))
self.handler.log("Unhandled DNS requests will be resolved using %s" % upstream_dns_server)
self.handler.log("DNS server listening on %s" % ", ".join(map(lambda x: str(x), listen_addresses)))
for ip_and_port in listen_addresses:
server = DNSServer(resolver, address=ip_and_port.ip.exploded, port=ip_and_port.port)
server.thread_name = "%s:%s" % (ip_and_port.ip, ip_and_port.port)
self.servers.append(server)
self.handler.log("DNS server listening on %s:%s" % (address, listen_port))
self.watcher = DockerWatcher(self, cli)
......
......@@ -9,6 +9,9 @@ class DockerHost:
self.ip = ip
self.interface = interface
def __str__(self):
return "%s/%s" % (self.ip, ','.join(self.host_names))
class DockerWatcher(Thread):
"""
......
import ipaddress
from socket import AF_INET, AF_INET6
from typing import List
import dbus
from pyroute2 import IPRoute
from .utils import IpAndPort
class SystemdResolvedConnector:
def __init__(self, docker_interface, listen_addresses, listen_port, dns_domains):
def __init__(self, interface, listen_addresses: List[IpAndPort], dns_domains, handler):
super().__init__()
self.docker_interface = docker_interface
self.interface = interface
self.listen_addresses = listen_addresses
self.listen_port = listen_port
self.dns_domains = dns_domains
self.handler = handler
self.ifindex = self.resolve_ifindex(docker_interface)
self.ifindex = self.resolve_ifindex(interface)
@staticmethod
def resolve_ifindex(docker_interface):
def resolve_ifindex(interface):
with IPRoute() as ipr:
ifi = ipr.link_lookup(ifname=docker_interface)
ifi = ipr.link_lookup(ifname=interface)
if not ifi:
raise ValueError("Unknown interface '%s'" % docker_interface)
raise ValueError("Unknown interface '%s'" % interface)
return ifi[0]
......@@ -33,15 +36,18 @@ class SystemdResolvedConnector:
return dbus.Interface(proxy, 'org.freedesktop.resolve1.Manager')
def register(self):
self.handler.log("Registering with systemd-resolved - interface: %s, domains: %s, dns server: %s" % (
self.interface, self.dns_domains, ", ".join(map(lambda x: str(x), self.listen_addresses))))
domains = [[domain.strip("."), True] for domain in self.dns_domains]
ips = [
[
AF_INET if isinstance(ip, ipaddress.IPv4Address) else AF_INET6,
ip.packed,
self.listen_port,
AF_INET if isinstance(ip_port.ip, ipaddress.IPv4Address) else AF_INET6,
ip_port.ip.packed,
ip_port.port,
"",
]
for ip in [ipaddress.ip_address(ip) for ip in self.listen_addresses]
for ip_port in self.listen_addresses
]
manager = self.if_manager()
......@@ -50,5 +56,7 @@ class SystemdResolvedConnector:
manager.SetLinkDomains(self.ifindex, domains)
def unregister(self):
self.handler.log("Unregistering with systemd-resolved: %s" % self.interface)
manager = self.if_manager()
manager.RevertLink(self.ifindex)
def find_docker_dns_servers(cli):
return []
import ipaddress
import urllib.parse
from typing import List
class IpAndPort:
ip: ipaddress.ip_address
port: int
def __init__(self, ip: ipaddress.ip_address, port: int):
self.ip = ip
self.port = port
def __str__(self):
return "%s:%s" % (self.ip.compressed, self.port)
def parse_ip_port(entry, default_port=53) -> IpAndPort:
result = urllib.parse.urlsplit('//' + entry)
return IpAndPort(ip=ipaddress.ip_address(result.hostname), port=result.port or default_port)
def parse_listen_address(listen_addresses, default_value) -> List[IpAndPort]:
if listen_addresses is not None and len(listen_addresses) > 1:
return [parse_ip_port(item) for item in listen_addresses.split(",")]
else:
return default_value()
def find_default_docker_bridge_gateway(cli):
......
......@@ -6,13 +6,13 @@
## default: first docker network's interface
# DOCKER_INTERFACE=docker0
## IPs to listen on for queries from systemd-resolved and docker containers
## default: ip address of each defined docker network
# LISTEN_ADDRESS=172.17.0.1
## IPs (+port) to listen on for queries from systemd-resolved.
## default: 127.0.0.153
# SYSTEMD_RESOLVED_LISTEN_ADDRESS=127.0.0.153:53
## Port to listen on for queries from systemd-resolved and docker containers.
## default: 53
# LISTEN_PORT=53
## IPs (+port) to listen on for queries from docker containers in the default network.
## default: ip of the default docker bridge
# DOCKER_LISTEN_ADDRESS=172.17.0.1:53
## Domain to append to containers which don't have one set using `--domainname`
## or are not part of a network
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment