Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

Commit c8924c9

Browse files
committed
Add support for resolving IPv6 container hostnames
1 parent 620df94 commit c8924c9

File tree

6 files changed

+116
-25
lines changed

6 files changed

+116
-25
lines changed

src/systemd_resolved_docker/dockerdnsconnector.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import threading
33
from typing import List
44

5-
from dnslib import A, CLASS, DNSLabel, QTYPE, RR
5+
from dnslib import A, AAAA, CLASS, DNSLabel, QTYPE, RR
66
from dnslib.proxy import ProxyResolver
77
from dnslib.server import DNSServer
88

@@ -72,7 +72,10 @@ def handle_hosts(self, hosts):
7272
hn = self.as_allowed_hostname(host_name)
7373
mh.host_names.append(hn)
7474

75-
rr = RR(hn, QTYPE.A, CLASS.IN, 1, A(host.ip))
75+
if isinstance(host.ip, ipaddress.IPv4Address):
76+
rr = RR(hn, QTYPE.A, CLASS.IN, 1, A(host.ip.exploded))
77+
else:
78+
rr = RR(hn, QTYPE.AAAA, CLASS.IN, 1, AAAA(host.ip.exploded))
7679
zone.append(rr)
7780
host_names.append(hn)
7881

src/systemd_resolved_docker/dockerwatcher.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import ipaddress
2+
from typing import List, Union
3+
14
import docker
25

36
from threading import Thread
47

58

69
class DockerHost:
7-
def __init__(self, host_names, ip, interface=None):
10+
def __init__(self, host_names: List[str], ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address], interface=None):
811
self.host_names = host_names
912
self.ip = ip
1013
self.interface = interface
@@ -85,22 +88,25 @@ def collect_from_containers(self):
8588
name = c.attrs['Name'][1:]
8689
settings = c.attrs['NetworkSettings']
8790
for netname, network in settings.get('Networks', {}).items():
88-
ip = network.get('IPAddress', False)
89-
if not ip or ip == "":
91+
ips = [network[field] for field in ['IPAddress', 'GlobalIPv6Address'] if
92+
field in network and network[field] != ""]
93+
if not ips:
9094
if netname == 'host':
91-
ip = self.default_host_ip
95+
ips = [self.default_host_ip]
9296
else:
9397
continue
9498

9599
# record the container name DOT network
96100
# eg. container is named "foo", and network is "demo",
97101
# so create "foo.demo" domain name
98102
# (avoiding default network named "bridge")
99-
record = domain_records.get(ip, [*common_hostnames])
100-
if netname != "bridge":
101-
record.append('%s.%s' % (name, netname))
103+
for ip in ips:
104+
ipr = ipaddress.ip_address(ip)
105+
record = domain_records.get(ipr, [*common_hostnames])
106+
if netname != "bridge":
107+
record.append('%s.%s' % (name, netname))
102108

103-
domain_records[ip] = record
109+
domain_records[ipr] = record
104110

105111
for ip, hosts in domain_records.items():
106112
domain_records[ip] = list(filter(lambda h: h not in duplicate_hostnames, hosts))

src/systemd_resolved_docker/utils.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import ipaddress
22
import urllib.parse
33
from pyroute2 import NDB
4-
from typing import List
4+
from typing import List, Union
55

66

77
class IpAndPort:
8-
ip: ipaddress.ip_address
9-
port: int
10-
11-
def __init__(self, ip: ipaddress.ip_address, port: int):
8+
def __init__(self, ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address], port: int):
129
self.ip = ip
1310
self.port = port
1411

test/integration/functions.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ docker_ip() {
4444
docker inspect --format '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $container_id
4545
}
4646

47+
docker_ipv6() {
48+
local container_id=$1
49+
shift;
50+
51+
docker inspect --format '{{range.NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' $container_id
52+
}
53+
4754
docker_name() {
4855
local container_id=$1
4956
shift;

test/integration/test_ipv6.sh

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env bash
2+
3+
. ./functions.sh
4+
5+
exec 10<<EOF
6+
version: "2.1"
7+
services:
8+
webserver:
9+
image: nginx
10+
labels:
11+
- $TEST_LABEL
12+
networks:
13+
- network
14+
broker:
15+
image: redis
16+
labels:
17+
- $TEST_LABEL
18+
networks:
19+
- network
20+
21+
networks:
22+
network:
23+
driver: bridge
24+
enable_ipv6: true
25+
labels:
26+
- $TEST_LABEL
27+
ipam:
28+
driver: default
29+
config:
30+
- subnet: 2001:db8:a::/64
31+
gateway: 2001:db8:a::1
32+
EOF
33+
34+
exec 20<<EOF
35+
version: "2.1"
36+
services:
37+
broker:
38+
image: redis
39+
labels:
40+
- $TEST_LABEL
41+
networks:
42+
- network
43+
44+
networks:
45+
network:
46+
driver: bridge
47+
enable_ipv6: true
48+
labels:
49+
- $TEST_LABEL
50+
ipam:
51+
driver: default
52+
config:
53+
- subnet: 2001:db8:b::/64
54+
gateway: 2001:db8:b::1
55+
EOF
56+
57+
ALLOWED_DOMAINS=.docker,.$TEST_PREFIX start_systemd_resolved_docker
58+
59+
docker-compose --file /dev/fd/10 --project-name $TEST_PREFIX up --detach --scale webserver=2
60+
61+
broker1_ip=$(docker_ipv6 ${TEST_PREFIX}_broker_1)
62+
webserver1_ip=$(docker_ipv6 ${TEST_PREFIX}_webserver_1)
63+
webserver2_ip=$(docker_ipv6 ${TEST_PREFIX}_webserver_2)
64+
65+
query_ok broker.$TEST_PREFIX $broker1_ip
66+
query_ok 1.broker.$TEST_PREFIX $broker1_ip
67+
68+
query_ok webserver.$TEST_PREFIX $webserver1_ip
69+
query_ok webserver.$TEST_PREFIX $webserver2_ip
70+
query_ok 1.webserver.$TEST_PREFIX $webserver1_ip
71+
query_ok 2.webserver.$TEST_PREFIX $webserver2_ip
72+
73+
query_ok broker.docker $broker1_ip
74+
75+
docker-compose --file /dev/fd/20 --project-name ${TEST_PREFIX}_2 up --detach
76+
query_fail broker.docker

test/integration/test_proxy.sh

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ docker network create --label $TEST_LABEL $NETWORK > /dev/null
1010
container_id=$(docker_run resolvetest1 --hostname resolvetest1)
1111
container_ip=$(docker_ip ${container_id})
1212

13-
dns_ip=$(docker network inspect bridge --format '{{ range .IPAM.Config }}{{ .Gateway }}{{ end }}')
14-
15-
query_ok resolvetest1.docker $container_ip
16-
17-
# Case 1: generated domains are resolved in containers on the default network
18-
# The DNS server is provided explicitly, since it was not provided to the daemon
19-
docker run --dns $dns_ip --rm alpine sh -c "apk add bind-tools && host resolvetest1.docker"
20-
21-
# Case 2: generated domains are resolved in containers on other networks
22-
docker run --network $NETWORK --rm alpine sh -c "apk add bind-tools && host resolvetest1.docker"
13+
# The default bridge may have multiple ips/gateways, for example if IPv6 is enabled
14+
for gateway_ip in $(docker network inspect bridge --format '{{ range .IPAM.Config }}{{ .Gateway }} {{ end }}');
15+
do
16+
query_ok resolvetest1.docker $container_ip
17+
18+
# Case 1: generated domains are resolved in containers on the default network
19+
# The DNS server is provided explicitly, since it was not provided to the daemon
20+
docker run --dns $gateway_ip --rm alpine sh -c "apk add bind-tools && host resolvetest1.docker"
21+
22+
# Case 2: generated domains are resolved in containers on other networks
23+
docker run --network $NETWORK --rm alpine sh -c "apk add bind-tools && host resolvetest1.docker"
24+
done

0 commit comments

Comments
 (0)