fix: allow reply traffic from connected cells through FORWARD chain

apply_cell_rules drops all traffic from a cell's subnet except specific
service ports. This also drops ICMP replies and TCP ACKs for connections
initiated by local peers to the connected cell, breaking cross-cell
routing (ping to 10.0.0.1 silently dropped by test's cell DROP rule).

Fix: ensure_forward_stateful() inserts a stateful ESTABLISHED,RELATED
ACCEPT at the top of FORWARD. Called from apply_cell_rules (every cell
add/update) and from _apply_startup_enforcement. Idempotent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 15:13:59 -04:00
parent c2d215ee2e
commit 5a4e292440
3 changed files with 80 additions and 0 deletions
+28
View File
@@ -399,6 +399,10 @@ def apply_cell_rules(cell_name: str, vpn_subnet: str, inbound_services: List[str
'-p', 'tcp', '--dport', '3000',
'-m', 'comment', '--comment', tag, '-j', 'ACCEPT'])
# Ensure reply traffic (e.g. ICMP, TCP ACKs) for connections initiated
# by local peers to this cell is not dropped by the cell's catch-all DROP.
ensure_forward_stateful()
logger.info(
f"Applied cell rules for {cell_name} ({vpn_subnet}): "
f"inbound={inbound_services} exit_relay={exit_relay}"
@@ -422,6 +426,30 @@ def apply_all_cell_rules(cell_links: List[Dict[str, Any]]) -> None:
apply_cell_rules(name, subnet, inbound, exit_relay=exit_relay)
def ensure_forward_stateful() -> bool:
"""Insert a stateful ESTABLISHED/RELATED ACCEPT at the top of FORWARD.
Cell rules DROP all traffic from a connected cell's subnet except specific
service ports. Without conntrack, ICMP replies and TCP ACKs for connections
initiated BY local peers to the connected cell are also dropped, making
cross-cell routing (peer → cell → remote cell) broken.
This rule is inserted once and does not carry a peer/cell comment tag, so it
is never removed by clear_peer_rules or clear_cell_rules.
"""
try:
check = ['-C', 'FORWARD', '-m', 'state', '--state', 'ESTABLISHED,RELATED', '-j', 'ACCEPT']
if _wg_exec(['iptables'] + check).returncode == 0:
return True # already present
_wg_exec(['iptables', '-I', 'FORWARD', '1', '-m', 'state',
'--state', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'])
logger.info('ensure_forward_stateful: inserted ESTABLISHED,RELATED ACCEPT into FORWARD')
return True
except Exception as e:
logger.error(f'ensure_forward_stateful: {e}')
return False
def ensure_cell_api_dnat() -> bool:
"""DNAT wg0:3000 → cell-api:3000 inside cell-wireguard.