fix: full-tunnel default, real host routing table, peer config tunnel mode
- WireGuard default changed to full tunnel (0.0.0.0/0) — all peer traffic routes through PIC server so internet latency matches server's clean 41ms - UI tunnel toggle now defaults to Full tunnel - API /peers/config accepts allowed_ips param so UI toggle wires through - Routing page reads real host routes via /proc/1/net/route (pid: host) instead of mock data; shows ens18/192.168.31.1 correctly - Add iproute2 + util-linux to API Dockerfile Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ WORKDIR /app/api
|
|||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
wireguard-tools \
|
wireguard-tools \
|
||||||
iptables \
|
iptables \
|
||||||
|
iproute2 \
|
||||||
|
util-linux \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
gnupg \
|
gnupg \
|
||||||
|
|||||||
@@ -902,11 +902,13 @@ def get_peer_config():
|
|||||||
srv = wireguard_manager.get_server_config()
|
srv = wireguard_manager.get_server_config()
|
||||||
server_endpoint = srv.get('endpoint') or '<SERVER_IP>'
|
server_endpoint = srv.get('endpoint') or '<SERVER_IP>'
|
||||||
|
|
||||||
|
allowed_ips = data.get('allowed_ips') or None
|
||||||
result = wireguard_manager.get_peer_config(
|
result = wireguard_manager.get_peer_config(
|
||||||
peer_name=peer_name,
|
peer_name=peer_name,
|
||||||
peer_ip=peer_ip,
|
peer_ip=peer_ip,
|
||||||
peer_private_key=peer_private_key,
|
peer_private_key=peer_private_key,
|
||||||
server_endpoint=server_endpoint,
|
server_endpoint=server_endpoint,
|
||||||
|
allowed_ips=allowed_ips,
|
||||||
)
|
)
|
||||||
return jsonify({"config": result})
|
return jsonify({"config": result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
+50
-28
@@ -862,37 +862,59 @@ class RoutingManager(BaseServiceManager):
|
|||||||
logger.error(f"Failed to apply firewall rule: {e}")
|
logger.error(f"Failed to apply firewall rule: {e}")
|
||||||
|
|
||||||
def _get_routing_table(self) -> List[Dict]:
|
def _get_routing_table(self) -> List[Dict]:
|
||||||
"""Get current routing table"""
|
"""Get host routing table from /proc/1/net/route (host PID namespace)."""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(['ip', 'route', 'show'],
|
return self._parse_proc_net_route('/proc/1/net/route')
|
||||||
capture_output=True, text=True, timeout=10)
|
except Exception:
|
||||||
|
pass
|
||||||
routes = []
|
# Fallback: WireGuard container routing table
|
||||||
for line in result.stdout.strip().split('\n'):
|
try:
|
||||||
if line.strip():
|
result = subprocess.run(
|
||||||
routes.append({
|
['docker', 'exec', 'cell-wireguard', 'ip', 'route', 'show'],
|
||||||
'route': line.strip(),
|
capture_output=True, text=True, timeout=10,
|
||||||
'parsed': self._parse_route(line.strip())
|
)
|
||||||
})
|
if result.returncode == 0:
|
||||||
|
routes = []
|
||||||
return routes
|
for line in result.stdout.strip().split('\n'):
|
||||||
|
if line.strip():
|
||||||
except FileNotFoundError:
|
routes.append({'route': line.strip(), 'parsed': self._parse_route(line.strip())})
|
||||||
# System tools not available (development environment)
|
return routes
|
||||||
# Return mock routing table for development
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'route': 'default via 192.168.1.1 dev en0',
|
|
||||||
'parsed': {'destination': 'default', 'via': '192.168.1.1', 'dev': 'en0', 'metric': ''}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'route': '10.0.0.0/24 dev wg0',
|
|
||||||
'parsed': {'destination': '10.0.0.0/24', 'via': '', 'dev': 'wg0', 'metric': ''}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get routing table: {e}")
|
logger.error(f"Failed to get routing table: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def _parse_proc_net_route(self, path: str) -> List[Dict]:
|
||||||
|
"""Parse /proc/net/route hex table into human-readable routes."""
|
||||||
|
import socket, struct
|
||||||
|
routes = []
|
||||||
|
with open(path) as f:
|
||||||
|
lines = f.readlines()[1:] # skip header
|
||||||
|
for line in lines:
|
||||||
|
parts = line.strip().split()
|
||||||
|
if len(parts) < 8:
|
||||||
|
continue
|
||||||
|
iface, dest_hex, gw_hex, mask_hex = parts[0], parts[1], parts[2], parts[7]
|
||||||
|
|
||||||
|
def hex_to_ip(h):
|
||||||
|
return socket.inet_ntoa(struct.pack('<I', int(h, 16)))
|
||||||
|
|
||||||
|
dest = hex_to_ip(dest_hex)
|
||||||
|
gw = hex_to_ip(gw_hex)
|
||||||
|
mask = hex_to_ip(mask_hex)
|
||||||
|
prefix = bin(struct.unpack('>I', socket.inet_aton(mask))[0]).count('1')
|
||||||
|
|
||||||
|
if dest == '0.0.0.0' and mask == '0.0.0.0':
|
||||||
|
dest_str = 'default'
|
||||||
|
route_str = f'default via {gw} dev {iface}'
|
||||||
|
else:
|
||||||
|
dest_str = f'{dest}/{prefix}'
|
||||||
|
route_str = f'{dest}/{prefix} dev {iface}' + (f' via {gw}' if gw != '0.0.0.0' else '')
|
||||||
|
|
||||||
|
routes.append({
|
||||||
|
'route': route_str,
|
||||||
|
'parsed': {'destination': dest_str, 'via': gw if gw != '0.0.0.0' else '', 'dev': iface, 'metric': ''},
|
||||||
|
})
|
||||||
|
return routes
|
||||||
|
|
||||||
def _parse_route(self, route_line: str) -> Dict:
|
def _parse_route(self, route_line: str) -> Dict:
|
||||||
"""Parse route line into components"""
|
"""Parse route line into components"""
|
||||||
|
|||||||
@@ -237,18 +237,16 @@ class WireGuardManager(BaseServiceManager):
|
|||||||
self._write_config('\n'.join(new_lines))
|
self._write_config('\n'.join(new_lines))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Split-tunnel: only route cell VPN + Docker subnets through WireGuard.
|
|
||||||
# This keeps the client's local LAN traffic (e.g. 192.168.x.x) off the tunnel,
|
|
||||||
# avoiding the internet RTT penalty when pinging local devices.
|
|
||||||
SPLIT_TUNNEL_IPS = '10.0.0.0/24, 172.20.0.0/16'
|
SPLIT_TUNNEL_IPS = '10.0.0.0/24, 172.20.0.0/16'
|
||||||
|
FULL_TUNNEL_IPS = '0.0.0.0/0, ::/0'
|
||||||
|
|
||||||
def get_peer_config(self, peer_name: str, peer_ip: str,
|
def get_peer_config(self, peer_name: str, peer_ip: str,
|
||||||
peer_private_key: str,
|
peer_private_key: str,
|
||||||
server_endpoint: str = '<SERVER_IP>',
|
server_endpoint: str = '<SERVER_IP>',
|
||||||
allowed_ips: str = None) -> str:
|
allowed_ips: str = None) -> str:
|
||||||
"""Generate a WireGuard client config string (split-tunnel by default)."""
|
"""Generate a WireGuard client config string (full-tunnel by default)."""
|
||||||
if allowed_ips is None:
|
if allowed_ips is None:
|
||||||
allowed_ips = self.SPLIT_TUNNEL_IPS
|
allowed_ips = self.FULL_TUNNEL_IPS
|
||||||
server_keys = self.get_keys()
|
server_keys = self.get_keys()
|
||||||
peer_dns = _resolve_peer_dns()
|
peer_dns = _resolve_peer_dns()
|
||||||
endpoint = server_endpoint if ':' in server_endpoint else f'{server_endpoint}:{DEFAULT_PORT}'
|
endpoint = server_endpoint if ':' in server_endpoint else f'{server_endpoint}:{DEFAULT_PORT}'
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ services:
|
|||||||
- ./config/api:/app/config
|
- ./config/api:/app/config
|
||||||
- ./config/wireguard:/app/config/wireguard
|
- ./config/wireguard:/app/config/wireguard
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
pid: host
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
cell-network:
|
cell-network:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function WireGuard() {
|
|||||||
const [peerConfig, setPeerConfig] = useState('');
|
const [peerConfig, setPeerConfig] = useState('');
|
||||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
|
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
|
||||||
const [peerStatuses, setPeerStatuses] = useState({});
|
const [peerStatuses, setPeerStatuses] = useState({});
|
||||||
const [tunnelMode, setTunnelMode] = useState('split'); // 'split' or 'full'
|
const [tunnelMode, setTunnelMode] = useState('full'); // 'split' or 'full'
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchWireGuardData();
|
fetchWireGuardData();
|
||||||
|
|||||||
Reference in New Issue
Block a user