Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
flaktack committed Mar 19, 2021
0 parents commit e784925
Show file tree
Hide file tree
Showing 18 changed files with 753 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/dist
/*.egg-info
/rpm/*.rpm
/rpm/*.tar.gz
__pycache__
3 changes: 3 additions & 0 deletions .tito/packages/.readme
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
the .tito/packages directory contains metadata files
named after their packages. Each file has the latest tagged
version and the project's relative directory.
1 change: 1 addition & 0 deletions .tito/templates/__init__.py.template
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '$version'
9 changes: 9 additions & 0 deletions .tito/tito.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[buildconfig]
builder = tito.builder.Builder
tagger = tito.tagger.VersionTagger
changelog_do_not_remove_cherrypick = 0
changelog_format = %s (%ae)

[version_template]
destination_file = src/systemd_resolved_docker/__init__.py
template_file = .tito/templates/__init__.py.template
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright 2020-2021 Zsombor Welker
Copyright 2018-2020 Patrice Ferlet

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# systemd-resolved-docker

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. expose the systemd-resolved DNS service (`127.0.0.53`) to docker containers by proxying DNS requests, since the
systems loopback IPs can't be accessed from containers.
2. adds the created DNS servers to the docker interface using systemd-resolved so that docker containers may
be referenced by hostname. This uses `--hostname` and `--domainname`, `--network` or a default of `.docker` to
create the domains.

## Install

### Fedora / COPR

For Fedora and RPM based systems [COPR](https://copr.fedorainfracloud.org/coprs/flaktack/systemd-resolved-docker/) contains pre-built packages.

1. Enabled the COPR repository

dnf copr enable flaktack/systemd-resolved-docker

1. Install the package

dnf install systemd-resolved-docker

1. Start and optionally enable the services

systemctl start systemd-resolved-docker
systemctl enable systemd-resolved-docker

1. Docker should be updated to use the DNS server provided by `systemd-docker-resolved.` This may be done
globally by editing the docker daemon's configuration (`daemon.json`) or per-container using the `--dns`
flag.

```js
"dns": [
"172.17.0.1" // docker0 interface's IP address
]
```

### 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` |
| DEFAULT_DOMAIN | Domain to append to containers which don't have one set using `--domainname` or are not part of a network `--network`. | `.docker` | `.docker` |
| ALLOWED_DOMAINS | Domain globs which will be handled by the DNS server. | `.docker` | `.docker,local` |


## Usage

Start a container with a specified hostname:
`docker run --hostname test python:3.9 python -m http.server 3000`

If configured correctly then `resolvectl status` should show the configured link-specific DNS server, while the url
should load: http://test.docker:3000/

$ resolvectl status
...
Link 7 (docker0)
Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6
Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
DNS Servers: 172.17.0.1
DNS Domain: ~docker
...

If docker is configured to use the provided DNS server then the container domain names may also be resolved within containers:

$ docker run --dns 1.1.1.1 --rm -it alpine
/ # apk add bind
/ # host test.docker
Host test.docker not found: 3(NXDOMAIN)

```
$ docker run --dns 172.17.0.1 --rm -it alpine
/ # apk add bind
/ # host test.docker
/ # host test.docker
test.docker has address 172.17.0.3
Host test.docker not found: 3(NXDOMAIN)
Host test.docker not found: 3(NXDOMAIN)
```
If there are link-local, VPN or other DNS servers configured than those will also work within containers.
## Build
`setup.py` may be used to create a python package.
`tito` may be used to create RPMs.
## Links
Portions are based on [docker-auto-dnsmasq](https://github.com/metal3d/docker-auto-dnsmasq).
79 changes: 79 additions & 0 deletions python-systemd-resolved-docker.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
%global srcname systemd-resolved-docker
%global eggname systemd_resolved_docker

Name: python-%{srcname}
Version: 0.0.0
Release: 0%{?dist}
Summary: systemd-resolved and docker DNS integration

License: BSD
URL: https://pypi.python.org/pypi/systemd_resolved_docker
#Source0: ${pypi_source}
Source0: %{srcname}-%{version}.tar.gz
Source1: %{srcname}.service
Source2: %{srcname}.sysconfig

BuildArch: noarch

%global _description %{expand:
systemd-resolved and docker DNS integration}

%description %_description

%package -n python3-%{srcname}
Summary: %{summary}
%if 0%{?el6}
BuildRequires: python34-devel
BuildRequires: python34-setuptools
%else
BuildRequires: python3-devel
BuildRequires: python3-setuptools
%endif
BuildRequires: systemd-rpm-macros

%description -n python3-%{srcname} %_description

#-- PREP, BUILD & INSTALL -----------------------------------------------------#
%prep
%autosetup -n %{srcname}-%{version}

%build
%py3_build

%install
%py3_install

# SystemdD services
install -dp %{buildroot}%{_unitdir}
install -p -m 644 %{SOURCE1} %{buildroot}%{_unitdir}

# Sysconfig
install -dp %{buildroot}%{_sysconfdir}/sysconfig
install -p -m 644 %{SOURCE2} %{buildroot}%{_sysconfdir}/sysconfig/%{srcname}

# %%check
# %%{python3} setup.py test

%post
%systemd_post %{srcname}.service

%preun
%systemd_preun %{srcname}.service

%postun
%systemd_postun_with_restart %{srcname}.service


#-- FILES ---------------------------------------------------------------------#
# Note that there is no %%files section for the unversioned python module
%files -n python3-%{srcname}
%doc README.md
%{python3_sitelib}/%{eggname}-*.egg-info/
%{python3_sitelib}/%{eggname}/
%{_bindir}/%{srcname}
%{_unitdir}/%{srcname}.service
%config(noreplace) %{_sysconfdir}/sysconfig/%{srcname}

#-- CHANGELOG -----------------------------------------------------------------#

%changelog
43 changes: 43 additions & 0 deletions rpms/python-dnslib/python-dnslib.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
%global srcname dnslib

Name: python-%{srcname}
Version: 0.9.14
Release: 1%{?dist}
Summary: dnslib python module

License: BSD
URL: https://pypi.python.org/pypi/dnslib
Source0: %{pypi_source}

BuildArch: noarch

%global _description %{expand:
A library to encode/decode DNS wire-format packets supporting both
Python 2.7 and Python 3.2+.}

%description %_description

%package -n python3-%{srcname}
Summary: %{summary}
BuildRequires: python3-devel

%description -n python3-%{srcname} %_description

%prep
%autosetup -n %{srcname}-%{version}

%build
%py3_build

%install
%py3_install

# %check
# %{python3} setup.py test

# Note that there is no %%files section for the unversioned python module
%files -n python3-%{srcname}
%license LICENSE
%doc README
%{python3_sitelib}/%{srcname}-*.egg-info/
%{python3_sitelib}/%{srcname}/
50 changes: 50 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import codecs, os.path
from setuptools import setup, find_packages


# use README.md as readme
def readme():
with open('README.md') as f:
return f.read()


# get __version__ from a file
def read(rel_path):
here = os.path.abspath(os.path.dirname(__file__))
with codecs.open(os.path.join(here, rel_path), 'r') as fp:
return fp.read()


def get_version(rel_path):
for line in read(rel_path).splitlines():
if line.startswith('__version__'):
delim = '"' if '"' in line else "'"
return line.split(delim)[1]
else:
raise RuntimeError("Unable to find version string.")


setup(
name='systemd-resolved-docker',
url='https://github.com/flaktack/systemd-resolved-docker',
license='MIT',
author='Zsombor Welker',
author_email='flaktack@flaktack.net',
install_requires=["docker", "dnslib", "systemd-python", "dbus-python", "pyroute2"],
description='systemd-resolved and docker DNS integration',
long_description=readme(),
long_description_content_type="text/markdown",
package_dir={
'': 'src',
},
packages=find_packages('src'),
entry_points={
'console_scripts': [
'systemd-resolved-docker=systemd_resolved_docker.cli:main',
],
},
excluded=['rpms/*'],

# extract version from source
version=get_version("src/systemd_resolved_docker/__init__.py"),
)
1 change: 1 addition & 0 deletions src/systemd_resolved_docker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.0.0'
73 changes: 73 additions & 0 deletions src/systemd_resolved_docker/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3

import os
import docker
import signal
from systemd import daemon, journal

from .dockerdnsconnector import DockerDNSConnector
from .utils import find_default_docker_bridge_gateway, find_docker_dns_servers


class Handler:
def on_start(self):
daemon.notify('READY=1')
self.log("Started daemon")

def on_update(self, hosts):
os.system('resolvectl flush-caches')

message = "Refreshed - %d items (%s)" % (
len(hosts), ', '.join(["%s/%s" % (host.ip, ','.join(host.host_names)) for host in hosts]))
self.log(message)

def on_stop(self):
self.log("Stopped daemon")

def log(self, message):
print(message)


def main():
dns_server = os.environ.get("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:
domains = [".docker"]
else:
domains = tld.split(',') if tld and len(tld) > 0 else []

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']

handler = Handler()

connector = DockerDNSConnector(listen_addresses, listen_port, dns_server, domains, default_domain, interface,
handler, cli)
connector.start()

def sig_handler(signum, frame):
handler.log("Stopping - %s" % signal.Signals(signum))
connector.stop()

signal.signal(signal.SIGTERM, sig_handler)
signal.signal(signal.SIGINT, sig_handler)

signal.pause()


if __name__ == '__main__':
main()
Loading

0 comments on commit e784925

Please sign in to comment.