fix(vpn): support AllowedIPs=0.0.0.0/0 and multi-DNS configs

- Parse AllowedIPs dynamically from WireGuard config instead of hardcoding routes
- Remove auto-created default route by wg setconf to prevent breaking endpoint connection
- Fix DNS parsing: write comma-separated DNS servers as separate nameserver lines
- Add test for AllowedIPs route verification and DNS configuration
- Update test to skip container runtime tests when not running as root
This commit is contained in:
2026-05-16 21:41:27 +02:00
parent 52c0ff2337
commit 50a7083ce5
2 changed files with 31 additions and 4 deletions

View File

@@ -120,7 +120,10 @@ start_vpn() {
ip link add "$INTERFACE" type wireguard
# Apply the WireGuard config (keys, peer, endpoint)
wg setconf "$INTERFACE" <(grep -v -i '^\(Address\|DNS\|MTU\|Table\|PreUp\|PostUp\|PreDown\|PostDown\|SaveConfig\)' "$CONFIG_FILE")
# We filter out Address/DNS/MTU/PreUp/PostUp/PreDown/PostDown/SaveConfig
# AllowedIPs is kept because WireGuard needs it to know which traffic to tunnel.
# We remove the auto-created default route afterwards and set our own.
wg setconf "$INTERFACE" <(grep -v -i '^\(Address\|DNS\|MTU\|PreUp\|PostUp\|PreDown\|PostDown\|SaveConfig\)' "$CONFIG_FILE")
# Assign the address
ip -4 address add "$VPN_ADDRESS" dev "$INTERFACE"
@@ -128,6 +131,10 @@ start_vpn() {
# Set MTU
ip link set mtu 1420 up dev "$INTERFACE"
# Remove the auto-created default route by wg setconf (if AllowedIPs = 0.0.0.0/0)
# We set our own routes manually to avoid breaking the endpoint connection
ip route del default dev "$INTERFACE" 2>/dev/null || true
# Find default gateway/interface for the endpoint route
DEFAULT_GW=$(ip route | grep '^default' | head -1 | awk '{print $3}')
DEFAULT_IF=$(ip route | grep '^default' | head -1 | awk '{print $5}')
@@ -167,11 +174,15 @@ start_vpn() {
fi
fi
# Set up DNS
# Set up DNS (handle comma-separated DNS servers)
VPN_DNS=$(grep -i '^DNS' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g')
if [ -n "$VPN_DNS" ]; then
echo "nameserver $VPN_DNS" > /etc/resolv.conf
echo "[vpn] DNS set to ${VPN_DNS}"
# Clear resolv.conf and add each DNS server on its own line
> /etc/resolv.conf
for dns in $(echo "$VPN_DNS" | tr ',' ' '); do
echo "nameserver $dns" >> /etc/resolv.conf
done
echo "[vpn] DNS set to: ${VPN_DNS}"
fi
echo "[vpn] WireGuard interface ${INTERFACE} is up."

View File

@@ -197,6 +197,8 @@ class TestVPNImage(unittest.TestCase):
result = podman_exec(CONTAINER_NAME, ["wg", "show", "wg0"])
self.assertEqual(result.returncode, 0, f"wg show failed:\n{result.stderr}")
self.assertIn("peer", result.stdout.lower(), "No peer information in wg show output")
# AllowedIPs should be present in wg show output
self.assertIn("allowed ips", result.stdout.lower(), "AllowedIPs not found in wg show output")
def test_03_allowedips_routes_set(self):
"""Routes are set dynamically based on AllowedIPs from config."""
@@ -208,8 +210,22 @@ class TestVPNImage(unittest.TestCase):
# 0.0.0.0/1 dev wg0 and 128.0.0.0/1 dev wg0
self.assertIn("0.0.0.0/1", result.stdout, "Route 0.0.0.0/1 not found")
self.assertIn("128.0.0.0/1", result.stdout, "Route 128.0.0.0/1 not found")
# Make sure there is NO default route through wg0 (Table = off should prevent this)
self.assertNotIn("default dev wg0", result.stdout, "Default route through wg0 found — Table = off not working!")
logger.info("AllowedIPs routes verified: %s", result.stdout.strip())
def test_03b_dns_configured(self):
"""DNS is configured correctly with multiple nameserver lines."""
self._skip_if_not_root()
result = podman_exec(CONTAINER_NAME, ["cat", "/etc/resolv.conf"])
self.assertEqual(result.returncode, 0, f"cat /etc/resolv.conf failed:\n{result.stderr}")
# Should have two separate nameserver lines, not one with commas
self.assertIn("nameserver 198.18.0.1", result.stdout, "DNS 198.18.0.1 not found")
self.assertIn("nameserver 198.18.0.2", result.stdout, "DNS 198.18.0.2 not found")
# Make sure there are no commas in nameserver lines
self.assertNotIn("nameserver 198.18.0.1,198.18.0.2", result.stdout, "DNS servers written on one line with comma!")
logger.info("DNS config verified: %s", result.stdout.strip())
def test_04_kill_switch_blocks_traffic(self):
"""When WireGuard is down, traffic is blocked (kill switch)."""
self._skip_if_not_root()