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:
@@ -120,7 +120,10 @@ start_vpn() {
|
|||||||
ip link add "$INTERFACE" type wireguard
|
ip link add "$INTERFACE" type wireguard
|
||||||
|
|
||||||
# Apply the WireGuard config (keys, peer, endpoint)
|
# 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
|
# Assign the address
|
||||||
ip -4 address add "$VPN_ADDRESS" dev "$INTERFACE"
|
ip -4 address add "$VPN_ADDRESS" dev "$INTERFACE"
|
||||||
@@ -128,6 +131,10 @@ start_vpn() {
|
|||||||
# Set MTU
|
# Set MTU
|
||||||
ip link set mtu 1420 up dev "$INTERFACE"
|
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
|
# Find default gateway/interface for the endpoint route
|
||||||
DEFAULT_GW=$(ip route | grep '^default' | head -1 | awk '{print $3}')
|
DEFAULT_GW=$(ip route | grep '^default' | head -1 | awk '{print $3}')
|
||||||
DEFAULT_IF=$(ip route | grep '^default' | head -1 | awk '{print $5}')
|
DEFAULT_IF=$(ip route | grep '^default' | head -1 | awk '{print $5}')
|
||||||
@@ -167,11 +174,15 @@ start_vpn() {
|
|||||||
fi
|
fi
|
||||||
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')
|
VPN_DNS=$(grep -i '^DNS' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g')
|
||||||
if [ -n "$VPN_DNS" ]; then
|
if [ -n "$VPN_DNS" ]; then
|
||||||
echo "nameserver $VPN_DNS" > /etc/resolv.conf
|
# Clear resolv.conf and add each DNS server on its own line
|
||||||
echo "[vpn] DNS set to ${VPN_DNS}"
|
> /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
|
fi
|
||||||
|
|
||||||
echo "[vpn] WireGuard interface ${INTERFACE} is up."
|
echo "[vpn] WireGuard interface ${INTERFACE} is up."
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ class TestVPNImage(unittest.TestCase):
|
|||||||
result = podman_exec(CONTAINER_NAME, ["wg", "show", "wg0"])
|
result = podman_exec(CONTAINER_NAME, ["wg", "show", "wg0"])
|
||||||
self.assertEqual(result.returncode, 0, f"wg show failed:\n{result.stderr}")
|
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")
|
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):
|
def test_03_allowedips_routes_set(self):
|
||||||
"""Routes are set dynamically based on AllowedIPs from config."""
|
"""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
|
# 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("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")
|
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())
|
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):
|
def test_04_kill_switch_blocks_traffic(self):
|
||||||
"""When WireGuard is down, traffic is blocked (kill switch)."""
|
"""When WireGuard is down, traffic is blocked (kill switch)."""
|
||||||
self._skip_if_not_root()
|
self._skip_if_not_root()
|
||||||
|
|||||||
Reference in New Issue
Block a user