From 5d65eafc65b713d9e13ce38a8ff771e1f1cede5c Mon Sep 17 00:00:00 2001 From: xtcnet Date: Tue, 17 Mar 2026 20:14:33 +0700 Subject: [PATCH] fix: allow client-to-client traffic when isolation is disabled The wg+ wildcard in the server isolation DROP rule was also matching same-interface traffic (wg0->wg0), blocking clients from pinging each other even with Client Isolation turned off. Fix: always insert an explicit same-interface ACCEPT (or REJECT if isolated) rule AFTER the wg+ DROP, so it lands at position 1 in the chain and is evaluated before the DROP. Also update syncIptablesRules to apply the ACCEPT rule (not just remove the REJECT) when isolation is toggled off at runtime. Co-Authored-By: Claude Sonnet 4.6 --- backend/internal/wireguard.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/backend/internal/wireguard.js b/backend/internal/wireguard.js index adcf202..5a278de 100644 --- a/backend/internal/wireguard.js +++ b/backend/internal/wireguard.js @@ -65,17 +65,22 @@ const internalWireguard = { basePostUp.push("iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE"); basePostDown.push("iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE"); - // Client Isolation: Prevent clients on this interface from communicating with each other + // Server Isolation: drop cross-server traffic by default. + // Uses -I to insert at position 1, before the ACCEPT rules above. + basePostUp.push("iptables -I FORWARD -i %i -o wg+ -j DROP"); + basePostDown.push("iptables -D FORWARD -i %i -o wg+ -j DROP"); + + // Same-interface rule: inserted AFTER the DROP above so it lands at position 1, + // placing it BEFORE the wg+ DROP in the chain. + // This ensures client-to-client traffic on this interface is evaluated first. if (iface.isolate_clients) { basePostUp.push("iptables -I FORWARD -i %i -o %i -j REJECT"); basePostDown.push("iptables -D FORWARD -i %i -o %i -j REJECT"); + } else { + basePostUp.push("iptables -I FORWARD -i %i -o %i -j ACCEPT"); + basePostDown.push("iptables -D FORWARD -i %i -o %i -j ACCEPT"); } - // Server Isolation (Default DROP) & Server Peering - // 1. By default, prevent this interface from talking to ANY OTHER wg+ interfaces - basePostUp.push("iptables -I FORWARD -i %i -o wg+ -j DROP"); - basePostDown.push("iptables -D FORWARD -i %i -o wg+ -j DROP"); - // 2. Fetch linked servers to punch holes in the DROP rule // wg_server_link has interface_id_1 and interface_id_2 const links = await knex("wg_server_link") @@ -646,12 +651,15 @@ const internalWireguard = { */ async syncIptablesRules(iface) { const name = iface.name; - // Remove existing rule first (idempotent — suppress error if rule doesn't exist) + // Remove both possible same-interface rules first (idempotent) await execAsync(`iptables -D FORWARD -i ${name} -o ${name} -j REJECT 2>/dev/null || true`); + await execAsync(`iptables -D FORWARD -i ${name} -o ${name} -j ACCEPT 2>/dev/null || true`); + // Re-insert at position 1 so it appears before the wg+ DROP rule in the chain if (iface.isolate_clients) { await execAsync(`iptables -I FORWARD -i ${name} -o ${name} -j REJECT`); logger.info(`Client isolation enabled for ${name}`); } else { + await execAsync(`iptables -I FORWARD -i ${name} -o ${name} -j ACCEPT`); logger.info(`Client isolation disabled for ${name}`); } },