feat(cells): fix PIC-to-PIC connection + add service-sharing permissions
Phase 1 — connection fixes:
- routing_manager.stop(): remove iptables -F / -t nat -F nuclear flush that
would wipe WireGuard MASQUERADE and all peer rules on any UI stop action
- wireguard_manager.add_cell_peer(): reject vpn_subnet that overlaps the local
WG network (routing blackhole — was the root cause of no handshake)
- wireguard_manager._syncconf(): pass Endpoint to 'wg set' so cell peers with
static endpoints are synced to the kernel (not just AllowedIPs)
Phase 2 — service-sharing permissions backend:
- firewall_manager: add _cell_tag(), clear_cell_rules(), apply_cell_rules(),
apply_all_cell_rules() — iptables FORWARD rules for cell-to-cell traffic
using 'pic-cell-<name>' comment tags, distinct from 'pic-peer-*'
- app.py startup enforcement: call apply_all_cell_rules(cell_links) so rules
survive API restarts
- cell_link_manager: permissions schema {inbound, outbound} per service;
lazy migration for existing entries; update_permissions(), get_permissions();
apply_cell_rules wired into add_connection/remove_connection
- routes/cells.py: GET /api/cells/services, GET+PUT /api/cells/<n>/permissions;
RuntimeError now returns 400 (not 500) from add_connection
Removed broken 'test' cell (subnet 10.0.0.0/24 collided with local WG network).
Second PIC must use a distinct subnet (e.g. 10.0.1.0/24) before reconnecting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+7
-23
@@ -1074,33 +1074,17 @@ class RoutingManager(BaseServiceManager):
|
||||
return False
|
||||
|
||||
def stop(self) -> bool:
|
||||
"""Stop routing service"""
|
||||
"""Stop routing service (state only — iptables rules are NOT flushed).
|
||||
|
||||
Flushing iptables here would destroy WireGuard MASQUERADE and all peer
|
||||
FORWARD rules applied by firewall_manager. Individual rule removal is
|
||||
handled by remove_nat_rule() / remove_firewall_rule().
|
||||
"""
|
||||
try:
|
||||
# Set internal state to stopped
|
||||
self._service_running = False
|
||||
self._save_service_state()
|
||||
|
||||
# Try to clear all iptables rules (may fail in Docker without privileges)
|
||||
try:
|
||||
subprocess.run(['iptables', '-t', 'nat', '-F'],
|
||||
check=True, timeout=10)
|
||||
subprocess.run(['iptables', '-F'],
|
||||
check=True, timeout=10)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
||||
logger.warning(f"Could not clear iptables rules: {e}")
|
||||
# Continue anyway - service is considered stopped
|
||||
|
||||
# Try to disable IP forwarding (may fail in Docker without privileges)
|
||||
try:
|
||||
subprocess.run(['sysctl', '-w', 'net.ipv4.ip_forward=0'],
|
||||
check=True, timeout=10)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
||||
logger.warning(f"Could not disable IP forwarding: {e}")
|
||||
# Continue anyway - service is considered stopped
|
||||
|
||||
logger.info("Routing service stopped successfully")
|
||||
logger.info("Routing service stopped (state only; iptables untouched)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to stop routing service: {e}")
|
||||
# Even if system commands fail, we consider the service stopped
|
||||
|
||||
Reference in New Issue
Block a user