Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ services:
image: "wireguard.wireguard.dnp.dappnode.eth:0.1.1"
container_name: DAppNodeCore-wireguard.wireguard.dnp.dappnode.eth
restart: unless-stopped
# Keep Docker aliases while using Dappnode DNS for external lookups.
dns:
- 172.33.1.2
- 10.20.0.2
cap_add:
- NET_ADMIN
- SYS_MODULE
Expand Down
1 change: 1 addition & 0 deletions readme-vars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ init_diagram: |
"wireguard:latest" <- Base Images
# changelog
changelogs:
- {date: "24.10.26:", desc: "Use Dappnode DNS upstreams and ignore outgoing DNS sockets when detecting CoreDNS port conflicts."}
- {date: "24.01.26:", desc: "Rebase to Alpine 3.23 again as openresolv alpine 3.23 package has now been updated."}
- {date: "22.01.26:", desc: "Revert to Alpine 3.22 due to resolvconf bug."}
- {date: "04.01.26:", desc: "Rebase to Alpine 3.23."}
Expand Down
2 changes: 1 addition & 1 deletion root/etc/s6-overlay/s6-rc.d/svc-coredns/run
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash

if netstat -apn | grep -q ":53 " && [[ -z "${USE_COREDNS}" ]]; then
if netstat -lntup | grep -q ":53 " && [[ -z "${USE_COREDNS}" ]]; then
USE_COREDNS="false"
fi

Expand Down
59 changes: 59 additions & 0 deletions tests/test_coredns_port_detection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Exercise the image's listener guard with real sockets and no VPN state."""
import argparse
import json
from pathlib import Path
import subprocess
import time
import uuid

parser = argparse.ArgumentParser()
parser.add_argument('--image', required=True)
parser.add_argument('--helper-image', default='node:22.23.4-alpine3.24')
parser.add_argument('--output')
args = parser.parse_args()
name = 'wireguard-port-check-' + uuid.uuid4().hex[:10]
holder = name + '-udp'

def docker(*cmd, check=True):
result = subprocess.run(['docker', *cmd], capture_output=True, text=True, timeout=30)
if check and result.returncode:
raise RuntimeError('Docker ' + cmd[0] + ' failed: ' + result.stderr[-500:])
return result

try:
docker('run', '-d', '--name', name, '--network', 'none', '--cpus', '1', '--memory', '256m',
'--entrypoint', 'sh', args.image, '-c', 'sleep infinity')
script = docker('exec', name, 'cat', '/etc/s6-overlay/s6-rc.d/svc-coredns/run').stdout
line = next(line.strip() for line in script.splitlines() if line.startswith('if netstat '))
guard = line.removeprefix('if ').split(' && [[', 1)[0]
docker('run', '-d', '--name', holder, '--network', 'container:' + name, '--cpus', '1', '--memory', '128m',
'--entrypoint', 'node', args.helper_image, '-e',
"const s=require('dgram').createSocket('udp4');s.connect(53,'127.0.0.1',()=>console.log('connected'));setInterval(()=>{},10000)")
for _ in range(30):
if 'connected' in docker('logs', holder).stdout:
break
time.sleep(.1)
else:
raise RuntimeError('UDP fixture did not become ready')
# Reproduce the original false positive while there is no listening server.
assert docker('exec', name, 'sh', '-c', 'netstat -apn | grep -q ":53 "', check=False).returncode == 0
assert docker('exec', name, 'sh', '-c', guard, check=False).returncode == 1, 'Outgoing DNS socket was mistaken for a listener'
# Then start a real listener. The same image-provided guard must detect it.
docker('exec', name, 'sh', '-c', "printf '.:53 {\\n errors\\n}\\n' > /tmp/port-check-Corefile")
docker('exec', '-d', name, '/usr/bin/coredns', '-conf', '/tmp/port-check-Corefile')
for _ in range(30):
if docker('exec', name, 'sh', '-c', guard, check=False).returncode == 0:
break
time.sleep(.1)
else:
raise AssertionError('Actual DNS listener was not detected')
result = {'success': True, 'oldCheckFalsePositiveReproduced': True,
'outgoingDnsSocketIgnored': True, 'actualDnsListenerDetected': True,
'productionStateMounted': False, 'network': 'none'}
finally:
docker('rm', '-f', '-v', holder, check=False)
docker('rm', '-f', '-v', name, check=False)

if args.output:
Path(args.output).write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(result, indent=2))