Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dangoncalves committed Dec 6, 2017
0 parents commit e242eb7
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 0 deletions.
14 changes: 14 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004

Copyright (C) 2004 Sam Hocevar <[email protected]>

Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. You just DO WHAT THE FUCK YOU WANT TO.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Docker DNS

Docker DNS is a DNS server that resolve Docker's container name into
A record to retrieve IPv4 associated.

## Installation configuration and execution

First you need to clone this repository

```
git clone https://github.com/dangoncalves/docker-dns
```

Then change directory and install dependencies

```
cd docker-dns
pip3 install requirements.txt
```

Execute the script as root (to bind on port 53)

```
python3 docker-dns.py
```

Finally you can edit `/etc/resolv.conf` and put that line at the file's begining
(don't remove other nameserver entries)

```
nameserver 127.0.0.1
```

## Options

There are three options you can use to customize execution:

* `--port` customize the port docker-dns will listen on (default 53)
* `--listen-address` customize the address docker-dns will listen on
(default 127.0.0.1)
* `--forwarders` dns forwarders' list (coma separated list)

## License

This project is under WTFPL

## TODO - Roadmap

The project's goal is just to resolve docker containers' name into IP address.
This first version do the job, but there are still some things to do like:
* add tests
* add AAAA queries support
* add PTR queries support
* automate installation
* improve documentation
* add Windows and Mac OS support (tested only on Linux for now)
133 changes: 133 additions & 0 deletions docker-dns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""
Resolve docker container's name into IPv4 address
python3 docker-dns.py
"""

import os
import sys
import signal
import docker
import argparse
from threading import Thread
from twisted.internet import reactor, defer
from twisted.names import client, dns, server, error


class DockerResolver(client.Resolver):
"""Resolve container name into IP address."""
def __init__(self, dockerClient, servers=[]):
super().__init__(resolv=None, servers=servers)
self.dockerClient = dockerClient
self.runningContainers = {}
for c in dockerClient.containers.list():
containerName = c.attrs["Name"][1:]
containerBridge = c.attrs["NetworkSettings"]["Networks"]["bridge"]
containerIPv4 = containerBridge["IPAddress"]
self.addContainer(containerName, containerIPv4)

def addContainer(self, containerName, containerIPv4):
self.runningContainers[containerName] = containerIPv4

def removeContainer(self, containerName):
self.runningContainers.pop(containerName, None)

def lookupAddress(self, query, timeout=None):
domain = query.decode()
if domain in self.runningContainers:
p = dns.Record_A(address=self.runningContainers[domain].encode())
answer = dns.RRHeader(name=query, payload=p)
answers = [answer]
authority = []
additional = []
return defer.succeed((answers, authority, additional))
else:
return super().lookupAddress(query, timeout)


class EventsListener(Thread):
"""Listen on start and die events."""
def __init__(self, resolver):
super().__init__()
self.resolver = resolver

def run(self):
eventListener = self.resolver.dockerClient.events(
filters={"event": ["start", "die"]},
decode=True)
for e in eventListener:
callback = getattr(self, e["Action"] + "Callback")
callback(e)

def startCallback(self, event):
containerName = event["Actor"]["Attributes"]["name"]
api = self.resolver.dockerClient.api
container = api.inspect_container(containerName)
containerIPv4 = container["NetworkSettings"]["IPAddress"]
self.resolver.addContainer(containerName, containerIPv4)

def dieCallback(self, event):
containerName = event["Actor"]["Attributes"]["name"]
self.resolver.removeContainer(containerName)


def getForwarders(forwarders="", listenAddress="127.0.0.1"):
"""
Reads forwarders from arguments or from resolv.conf and create a list of
tuples containing the forwarders' IP and the port.
"""
if not forwarders:
forwarders = []
resolvconf = open("/etc/resolv.conf", "r")
for line in resolvconf:
if line.startswith("nameserver") and line[11:-1] == listenAddress:
continue
if line.startswith("nameserver"):
forwarders.append((line[11:-1], 53))
else:
forwarders = forwarders.split(",")
forwarders = [(address, 53) for address in forwarders]
return forwarders


def dockerDns(port=53, listenAddress="127.0.0.1", forwarders=[]):
"""Configure and execute the DNS server."""
dockerClient = docker.from_env()
resolver = DockerResolver(dockerClient=dockerClient,
servers=forwarders)
eventsListener = EventsListener(resolver)
eventsListener.start()
factory = server.DNSServerFactory(clients=[resolver])
protocol = dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(port=port, protocol=protocol, interface=listenAddress)
reactor.listenTCP(port=port, factory=factory, interface=listenAddress)
reactor.run()
eventsListener.join(1)
# For an unknown reason sys.exit() does not work
# so we use this hack.
os._exit(0)


if __name__ == "__main__":
description = "Resolve docker container's name into IPv4 address"
parser = argparse.ArgumentParser(description=description)
parser.add_argument("--port",
action="store",
dest="port",
type=int,
default=53)
parser.add_argument("--listen-address",
action="store",
dest="listenAddress",
default="127.0.0.1")
parser.add_argument("--forwarders",
action="store",
dest="forwarders",
default="")
options = parser.parse_args()
forwarders = getForwarders(forwarders=options.forwarders,
listenAddress=options.listenAddress)
dockerDns(port=options.port,
listenAddress=options.listenAddress,
forwarders=forwarders)
16 changes: 16 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
attrs==17.3.0
Automat==0.6.0
certifi==2017.11.5
chardet==3.0.4
constantly==15.1.0
docker==2.6.1
docker-pycreds==0.2.1
hyperlink==17.3.1
idna==2.6
incremental==17.5.0
requests==2.18.4
six==1.11.0
Twisted==17.9.0
urllib3==1.22
websocket-client==0.44.0
zope.interface==4.4.3

0 comments on commit e242eb7

Please sign in to comment.