fix: wireguard port/subnet/domain propagate to peer configs and new peer IPs
Backend: - wireguard_manager: _get_configured_port/address/network() read from wg0.conf instead of module-level constants; get_split_tunnel_ips() derives VPN network from configured Address; get_server_config() returns configured port, dns_ip, split_tunnel_ips, vpn_network - add_peer() and get_peer_config() use configured port (not hardcoded 51820) - _next_peer_ip() derives subnet from wireguard_manager._get_configured_address() so new peers are allocated IPs from the correct VPN range after address change - refresh-ip and check-port API endpoints return configured port, not 51820 - PUT /api/config: when wireguard port/address changes, all peers are marked config_needs_reinstall so users know to re-download tunnel configs - get_peer_config endpoint: uses configured split tunnel IPs (not hardcoded) Frontend: - Peers.jsx: SERVICES domains use live domain from ConfigContext; generateConfig() uses serverConf.dns_ip and serverConf.split_tunnel_ips; vpn_network shown in peer-access description; DNS hint uses live domain; server config loaded at mount time so it is available without re-fetching on every peer action; handleUpdatePeer uses /32 for server-side AllowedIPs (was incorrectly using full/split tunnel CIDRs which the backend rejects) - WireGuard.jsx: generateWireGuardConfig() uses serverConfig.dns_ip, split_tunnel_ips from server-config API; split-tunnel description shows live IPs Tests: 9 new tests in TestWireGuardConfigReads verify all config reads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+19
-9
@@ -434,6 +434,13 @@ def update_config():
|
|||||||
'service': service,
|
'service': service,
|
||||||
'config': config
|
'config': config
|
||||||
})
|
})
|
||||||
|
# VPN port or subnet change → all peer client configs are stale
|
||||||
|
if service == 'wireguard' and ('port' in config or 'address' in config):
|
||||||
|
for p in peer_registry.list_peers():
|
||||||
|
peer_registry.update_peer(p['peer'], {'config_needs_reinstall': True})
|
||||||
|
n = len(peer_registry.list_peers())
|
||||||
|
if n:
|
||||||
|
all_warnings.append(f'WireGuard endpoint changed — {n} peer(s) must reinstall VPN config')
|
||||||
|
|
||||||
# Apply cell identity domain to network and email services
|
# Apply cell identity domain to network and email services
|
||||||
if identity_updates.get('domain'):
|
if identity_updates.get('domain'):
|
||||||
@@ -1028,7 +1035,7 @@ def get_peer_config():
|
|||||||
allowed_ips = data.get('allowed_ips') or None
|
allowed_ips = data.get('allowed_ips') or None
|
||||||
if not allowed_ips and registered:
|
if not allowed_ips and registered:
|
||||||
internet_access = registered.get('internet_access', True)
|
internet_access = registered.get('internet_access', True)
|
||||||
allowed_ips = wireguard_manager.FULL_TUNNEL_IPS if internet_access else wireguard_manager.SPLIT_TUNNEL_IPS
|
allowed_ips = wireguard_manager.FULL_TUNNEL_IPS if internet_access else wireguard_manager.get_split_tunnel_ips()
|
||||||
|
|
||||||
result = wireguard_manager.get_peer_config(
|
result = wireguard_manager.get_peer_config(
|
||||||
peer_name=peer_name,
|
peer_name=peer_name,
|
||||||
@@ -1055,10 +1062,11 @@ def get_server_config():
|
|||||||
def refresh_external_ip():
|
def refresh_external_ip():
|
||||||
try:
|
try:
|
||||||
ip = wireguard_manager.get_external_ip(force_refresh=True)
|
ip = wireguard_manager.get_external_ip(force_refresh=True)
|
||||||
|
port = wireguard_manager._get_configured_port()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'external_ip': ip,
|
'external_ip': ip,
|
||||||
'port': 51820,
|
'port': port,
|
||||||
'endpoint': f'{ip}:51820' if ip else None,
|
'endpoint': f'{ip}:{port}' if ip else None,
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error refreshing external IP: {e}")
|
logger.error(f"Error refreshing external IP: {e}")
|
||||||
@@ -1079,7 +1087,7 @@ def apply_wireguard_enforcement():
|
|||||||
def check_wireguard_port():
|
def check_wireguard_port():
|
||||||
try:
|
try:
|
||||||
port_open = wireguard_manager.check_port_open()
|
port_open = wireguard_manager.check_port_open()
|
||||||
return jsonify({'port_open': port_open, 'port': 51820})
|
return jsonify({'port_open': port_open, 'port': wireguard_manager._get_configured_port()})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
@@ -1095,17 +1103,19 @@ def get_peers():
|
|||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
def _next_peer_ip() -> str:
|
def _next_peer_ip() -> str:
|
||||||
"""Auto-assign the next free 10.0.0.x address (starts at .2, skips .1 = server)."""
|
"""Auto-assign the next free host address from the configured VPN subnet."""
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
server_addr = wireguard_manager._get_configured_address() # e.g. '10.0.0.1/24'
|
||||||
|
network = ipaddress.ip_network(server_addr, strict=False)
|
||||||
|
server_ip = str(ipaddress.ip_interface(server_addr).ip)
|
||||||
used = {p.get('ip', '').split('/')[0] for p in peer_registry.list_peers()}
|
used = {p.get('ip', '').split('/')[0] for p in peer_registry.list_peers()}
|
||||||
network = ipaddress.ip_network('10.0.0.0/24')
|
|
||||||
for host in network.hosts():
|
for host in network.hosts():
|
||||||
ip = str(host)
|
ip = str(host)
|
||||||
if ip == '10.0.0.1':
|
if ip == server_ip:
|
||||||
continue # server address
|
continue
|
||||||
if ip not in used:
|
if ip not in used:
|
||||||
return ip
|
return ip
|
||||||
raise ValueError('No free IPs left in 10.0.0.0/24')
|
raise ValueError(f'No free IPs left in {network}')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/peers', methods=['POST'])
|
@app.route('/api/peers', methods=['POST'])
|
||||||
|
|||||||
@@ -158,6 +158,49 @@ class WireGuardManager(BaseServiceManager):
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
self._syncconf()
|
self._syncconf()
|
||||||
|
|
||||||
|
# ── Config value readers (always read from wg0.conf, never hardcode) ─────
|
||||||
|
|
||||||
|
def _read_iface_field(self, key: str) -> Optional[str]:
|
||||||
|
"""Return the value of a field from the [Interface] section of wg0.conf."""
|
||||||
|
cf = self._config_file()
|
||||||
|
if not os.path.exists(cf):
|
||||||
|
return None
|
||||||
|
with open(cf) as f:
|
||||||
|
in_iface = False
|
||||||
|
for line in f:
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped == '[Interface]':
|
||||||
|
in_iface = True
|
||||||
|
elif stripped.startswith('[') and stripped.endswith(']'):
|
||||||
|
in_iface = False
|
||||||
|
elif in_iface and '=' in stripped:
|
||||||
|
k, _, v = stripped.partition('=')
|
||||||
|
if k.strip() == key:
|
||||||
|
return v.strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_configured_port(self) -> int:
|
||||||
|
val = self._read_iface_field('ListenPort')
|
||||||
|
try:
|
||||||
|
return int(val) if val else DEFAULT_PORT
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return DEFAULT_PORT
|
||||||
|
|
||||||
|
def _get_configured_address(self) -> str:
|
||||||
|
return self._read_iface_field('Address') or SERVER_ADDRESS
|
||||||
|
|
||||||
|
def _get_configured_network(self) -> str:
|
||||||
|
import ipaddress
|
||||||
|
addr = self._get_configured_address()
|
||||||
|
try:
|
||||||
|
return str(ipaddress.ip_network(addr, strict=False))
|
||||||
|
except Exception:
|
||||||
|
return SERVER_NETWORK
|
||||||
|
|
||||||
|
def get_split_tunnel_ips(self) -> str:
|
||||||
|
"""Return split-tunnel AllowedIPs: VPN subnet + Docker bridge."""
|
||||||
|
return f'{self._get_configured_network()}, 172.20.0.0/16'
|
||||||
|
|
||||||
def apply_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
def apply_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Update wg0.conf interface fields and restart cell-wireguard."""
|
"""Update wg0.conf interface fields and restart cell-wireguard."""
|
||||||
restarted = []
|
restarted = []
|
||||||
@@ -304,7 +347,7 @@ class WireGuardManager(BaseServiceManager):
|
|||||||
f'PersistentKeepalive = {persistent_keepalive}\n'
|
f'PersistentKeepalive = {persistent_keepalive}\n'
|
||||||
)
|
)
|
||||||
if endpoint_ip:
|
if endpoint_ip:
|
||||||
peer_block += f'Endpoint = {endpoint_ip}:{DEFAULT_PORT}\n'
|
peer_block += f'Endpoint = {endpoint_ip}:{self._get_configured_port()}\n'
|
||||||
self._write_config(content + peer_block)
|
self._write_config(content + peer_block)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -376,7 +419,7 @@ class WireGuardManager(BaseServiceManager):
|
|||||||
self._write_config('\n'.join(new_lines))
|
self._write_config('\n'.join(new_lines))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
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' # legacy fallback; use get_split_tunnel_ips()
|
||||||
FULL_TUNNEL_IPS = '0.0.0.0/0, ::/0'
|
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,
|
||||||
@@ -388,7 +431,8 @@ class WireGuardManager(BaseServiceManager):
|
|||||||
allowed_ips = self.FULL_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}'
|
port = self._get_configured_port()
|
||||||
|
endpoint = server_endpoint if ':' in server_endpoint else f'{server_endpoint}:{port}'
|
||||||
addr = peer_ip if '/' in peer_ip else f'{peer_ip}/32'
|
addr = peer_ip if '/' in peer_ip else f'{peer_ip}/32'
|
||||||
return (
|
return (
|
||||||
f'[Interface]\n'
|
f'[Interface]\n'
|
||||||
@@ -468,16 +512,20 @@ class WireGuardManager(BaseServiceManager):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_server_config(self) -> Dict[str, Any]:
|
def get_server_config(self) -> Dict[str, Any]:
|
||||||
"""Return server public key, external IP, endpoint, and port status."""
|
"""Return server public key, external IP, endpoint, port, and tunnel info."""
|
||||||
keys = self.get_keys()
|
keys = self.get_keys()
|
||||||
external_ip = self.get_external_ip()
|
external_ip = self.get_external_ip()
|
||||||
endpoint = f'{external_ip}:{DEFAULT_PORT}' if external_ip else None
|
port = self._get_configured_port()
|
||||||
|
endpoint = f'{external_ip}:{port}' if external_ip else None
|
||||||
return {
|
return {
|
||||||
'public_key': keys['public_key'],
|
'public_key': keys['public_key'],
|
||||||
'external_ip': external_ip,
|
'external_ip': external_ip,
|
||||||
'endpoint': endpoint,
|
'endpoint': endpoint,
|
||||||
'port': DEFAULT_PORT,
|
'port': port,
|
||||||
'port_open': None,
|
'port_open': None,
|
||||||
|
'dns_ip': _resolve_peer_dns(),
|
||||||
|
'split_tunnel_ips': self.get_split_tunnel_ips(),
|
||||||
|
'vpn_network': self._get_configured_network(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_peer_status(self, public_key: str) -> Dict[str, Any]:
|
def get_peer_status(self, public_key: str) -> Dict[str, Any]:
|
||||||
|
|||||||
@@ -322,5 +322,93 @@ PersistentKeepalive = 30
|
|||||||
success = self.wg_manager.update_peer_ip('non_existent_key', '10.0.0.9/32')
|
success = self.wg_manager.update_peer_ip('non_existent_key', '10.0.0.9/32')
|
||||||
self.assertFalse(success)
|
self.assertFalse(success)
|
||||||
|
|
||||||
|
|
||||||
|
class TestWireGuardConfigReads(unittest.TestCase):
|
||||||
|
"""Test that port/address/network are read from wg0.conf, not hardcoded."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.test_dir = tempfile.mkdtemp()
|
||||||
|
self.data_dir = os.path.join(self.test_dir, 'data')
|
||||||
|
self.config_dir = os.path.join(self.test_dir, 'config')
|
||||||
|
os.makedirs(self.data_dir, exist_ok=True)
|
||||||
|
os.makedirs(self.config_dir, exist_ok=True)
|
||||||
|
patcher = patch.object(WireGuardManager, '_syncconf', return_value=None)
|
||||||
|
self.mock_sync = patcher.start()
|
||||||
|
self.addCleanup(patcher.stop)
|
||||||
|
self.wg = WireGuardManager(self.data_dir, self.config_dir)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.test_dir)
|
||||||
|
|
||||||
|
def _write_wg_conf(self, port=51820, address='10.0.0.1/24', extra=''):
|
||||||
|
conf = (
|
||||||
|
f'[Interface]\n'
|
||||||
|
f'PrivateKey = dummykey\n'
|
||||||
|
f'Address = {address}\n'
|
||||||
|
f'ListenPort = {port}\n'
|
||||||
|
f'{extra}'
|
||||||
|
)
|
||||||
|
cf = self.wg._config_file()
|
||||||
|
os.makedirs(os.path.dirname(cf), exist_ok=True)
|
||||||
|
with open(cf, 'w') as f:
|
||||||
|
f.write(conf)
|
||||||
|
|
||||||
|
def test_get_configured_port_reads_from_wg_conf(self):
|
||||||
|
self._write_wg_conf(port=54321)
|
||||||
|
self.assertEqual(self.wg._get_configured_port(), 54321)
|
||||||
|
|
||||||
|
def test_get_configured_port_fallback_when_no_file(self):
|
||||||
|
# No wg0.conf exists — fall back to DEFAULT_PORT
|
||||||
|
self.assertEqual(self.wg._get_configured_port(), 51820)
|
||||||
|
|
||||||
|
def test_get_configured_address_reads_from_wg_conf(self):
|
||||||
|
self._write_wg_conf(address='10.1.0.1/24')
|
||||||
|
self.assertEqual(self.wg._get_configured_address(), '10.1.0.1/24')
|
||||||
|
|
||||||
|
def test_get_configured_network_derives_from_address(self):
|
||||||
|
self._write_wg_conf(address='10.1.0.1/24')
|
||||||
|
self.assertEqual(self.wg._get_configured_network(), '10.1.0.0/24')
|
||||||
|
|
||||||
|
def test_get_split_tunnel_ips_uses_configured_network(self):
|
||||||
|
self._write_wg_conf(address='10.1.0.1/24')
|
||||||
|
split = self.wg.get_split_tunnel_ips()
|
||||||
|
self.assertIn('10.1.0.0/24', split)
|
||||||
|
self.assertIn('172.20.0.0/16', split)
|
||||||
|
self.assertNotIn('10.0.0.0/24', split)
|
||||||
|
|
||||||
|
def test_get_server_config_uses_configured_port(self):
|
||||||
|
self._write_wg_conf(port=54321)
|
||||||
|
with patch.object(self.wg, 'get_external_ip', return_value='1.2.3.4'):
|
||||||
|
cfg = self.wg.get_server_config()
|
||||||
|
self.assertEqual(cfg['port'], 54321)
|
||||||
|
self.assertIn(':54321', cfg['endpoint'])
|
||||||
|
|
||||||
|
def test_get_server_config_includes_dns_and_split_tunnel(self):
|
||||||
|
self._write_wg_conf(address='10.2.0.1/24')
|
||||||
|
with patch.object(self.wg, 'get_external_ip', return_value='1.2.3.4'):
|
||||||
|
cfg = self.wg.get_server_config()
|
||||||
|
self.assertIn('dns_ip', cfg)
|
||||||
|
self.assertIn('split_tunnel_ips', cfg)
|
||||||
|
self.assertIn('10.2.0.0/24', cfg['split_tunnel_ips'])
|
||||||
|
|
||||||
|
def test_get_peer_config_uses_configured_port_in_endpoint(self):
|
||||||
|
self._write_wg_conf(port=54321)
|
||||||
|
result = self.wg.get_peer_config(
|
||||||
|
peer_name='alice',
|
||||||
|
peer_ip='10.0.0.2',
|
||||||
|
peer_private_key='privkeyalice=',
|
||||||
|
server_endpoint='5.6.7.8',
|
||||||
|
)
|
||||||
|
self.assertIn(':54321', result)
|
||||||
|
self.assertNotIn(':51820', result)
|
||||||
|
|
||||||
|
def test_add_peer_uses_configured_port_in_endpoint(self):
|
||||||
|
self._write_wg_conf(port=54321)
|
||||||
|
self.wg.add_peer('alice', 'pubkeyalice=', '5.6.7.8', '10.0.0.2/32')
|
||||||
|
content = self.wg._read_config()
|
||||||
|
self.assertIn('Endpoint = 5.6.7.8:54321', content)
|
||||||
|
self.assertNotIn(':51820', content)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
+31
-20
@@ -1,18 +1,10 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Plus, Trash2, Edit, Eye, Shield, Copy, Download, Key, AlertTriangle, CheckCircle, Globe, Lock, Users, Server } from 'lucide-react';
|
import { Plus, Trash2, Edit, Eye, Shield, Copy, Download, Key, AlertTriangle, CheckCircle, Globe, Lock, Users, Server } from 'lucide-react';
|
||||||
import { peerAPI, wireguardAPI } from '../services/api';
|
import { peerAPI, wireguardAPI } from '../services/api';
|
||||||
|
import { useConfig } from '../contexts/ConfigContext';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
|
|
||||||
const CELL_DNS = '172.20.0.3';
|
|
||||||
const FULL_TUNNEL_IPS = '0.0.0.0/0, ::/0';
|
const FULL_TUNNEL_IPS = '0.0.0.0/0, ::/0';
|
||||||
const SPLIT_TUNNEL_IPS = '10.0.0.0/24, 172.20.0.0/16';
|
|
||||||
|
|
||||||
const SERVICES = [
|
|
||||||
{ key: 'calendar', label: 'Calendar', domain: 'calendar.cell' },
|
|
||||||
{ key: 'files', label: 'Files', domain: 'files.cell' },
|
|
||||||
{ key: 'mail', label: 'Webmail', domain: 'mail.cell' },
|
|
||||||
{ key: 'webdav', label: 'WebDAV', domain: 'webdav.cell' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -53,7 +45,16 @@ function Toggle({ checked, onChange, label, description }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Peers() {
|
function Peers() {
|
||||||
|
const { domain = 'cell' } = useConfig();
|
||||||
|
const SERVICES = [
|
||||||
|
{ key: 'calendar', label: 'Calendar', domain: `calendar.${domain}` },
|
||||||
|
{ key: 'files', label: 'Files', domain: `files.${domain}` },
|
||||||
|
{ key: 'mail', label: 'Webmail', domain: `mail.${domain}` },
|
||||||
|
{ key: 'webdav', label: 'WebDAV', domain: `webdav.${domain}` },
|
||||||
|
];
|
||||||
|
|
||||||
const [peers, setPeers] = useState([]);
|
const [peers, setPeers] = useState([]);
|
||||||
|
const [serverConf, setServerConf] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [showAddModal, setShowAddModal] = useState(false);
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
@@ -77,9 +78,10 @@ function Peers() {
|
|||||||
|
|
||||||
const fetchPeers = async () => {
|
const fetchPeers = async () => {
|
||||||
try {
|
try {
|
||||||
const [regResp, statusResp] = await Promise.all([
|
const [regResp, statusResp, scResp] = await Promise.all([
|
||||||
peerAPI.getPeers(),
|
peerAPI.getPeers(),
|
||||||
wireguardAPI.getPeerStatuses().catch(() => ({ data: {} })),
|
wireguardAPI.getPeerStatuses().catch(() => ({ data: {} })),
|
||||||
|
fetch('/api/wireguard/server-config').then(r => r.ok ? r.json() : null).catch(() => null),
|
||||||
]);
|
]);
|
||||||
const regPeers = regResp.data || [];
|
const regPeers = regResp.data || [];
|
||||||
const statusMap = statusResp.data || {};
|
const statusMap = statusResp.data || {};
|
||||||
@@ -93,6 +95,7 @@ function Peers() {
|
|||||||
transfer_tx: statusMap[p.public_key]?.transfer_tx ?? 0,
|
transfer_tx: statusMap[p.public_key]?.transfer_tx ?? 0,
|
||||||
}));
|
}));
|
||||||
setPeers(merged);
|
setPeers(merged);
|
||||||
|
if (scResp) setServerConf(scResp);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch peers:', err);
|
console.error('Failed to fetch peers:', err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -101,23 +104,30 @@ function Peers() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getServerConfig = async () => {
|
const getServerConfig = async () => {
|
||||||
|
if (serverConf) return serverConf;
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/wireguard/server-config');
|
const r = await fetch('/api/wireguard/server-config');
|
||||||
if (r.ok) return await r.json();
|
if (r.ok) {
|
||||||
|
const sc = await r.json();
|
||||||
|
setServerConf(sc);
|
||||||
|
return sc;
|
||||||
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
return { public_key: 'SERVER_PUBLIC_KEY_PLACEHOLDER', endpoint: 'YOUR_SERVER_IP:51820' };
|
return { public_key: 'SERVER_PUBLIC_KEY_PLACEHOLDER', endpoint: 'YOUR_SERVER_IP:51820' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateConfig = (peer, serverConf) => {
|
const generateConfig = (peer, sc) => {
|
||||||
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
|
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
|
||||||
const serverPubKey = peer.server_public_key || serverConf?.public_key || 'SERVER_PUBLIC_KEY_PLACEHOLDER';
|
const serverPubKey = peer.server_public_key || sc?.public_key || 'SERVER_PUBLIC_KEY_PLACEHOLDER';
|
||||||
const endpoint = peer.server_endpoint || serverConf?.endpoint || 'YOUR_SERVER_IP:51820';
|
const endpoint = peer.server_endpoint || sc?.endpoint || 'YOUR_SERVER_IP:51820';
|
||||||
const address = peer.ip?.includes('/') ? peer.ip : `${peer.ip}/32`;
|
const address = peer.ip?.includes('/') ? peer.ip : `${peer.ip}/32`;
|
||||||
const allowedIPs = peer.internet_access !== false ? FULL_TUNNEL_IPS : SPLIT_TUNNEL_IPS;
|
const splitTunnelIPs = sc?.split_tunnel_ips || `10.0.0.0/24, 172.20.0.0/16`;
|
||||||
|
const allowedIPs = peer.internet_access !== false ? FULL_TUNNEL_IPS : splitTunnelIPs;
|
||||||
|
const dnsIp = sc?.dns_ip || '172.20.0.3';
|
||||||
return `[Interface]
|
return `[Interface]
|
||||||
PrivateKey = ${privateKey}
|
PrivateKey = ${privateKey}
|
||||||
Address = ${address}
|
Address = ${address}
|
||||||
DNS = ${CELL_DNS}
|
DNS = ${dnsIp}
|
||||||
|
|
||||||
[Peer]
|
[Peer]
|
||||||
PublicKey = ${serverPubKey}
|
PublicKey = ${serverPubKey}
|
||||||
@@ -216,11 +226,12 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
|||||||
});
|
});
|
||||||
const result = await r.json();
|
const result = await r.json();
|
||||||
|
|
||||||
// Also update WireGuard server-side AllowedIPs
|
// Server-side AllowedIPs for the peer must stay as /32 (host route only)
|
||||||
|
const existingIp = selectedPeer.ip?.includes('/') ? selectedPeer.ip : `${selectedPeer.ip}/32`;
|
||||||
await wireguardAPI.addPeer({
|
await wireguardAPI.addPeer({
|
||||||
name: selectedPeer.name,
|
name: selectedPeer.name,
|
||||||
public_key: formData.public_key || selectedPeer.public_key,
|
public_key: formData.public_key || selectedPeer.public_key,
|
||||||
allowed_ips: formData.internet_access ? FULL_TUNNEL_IPS : SPLIT_TUNNEL_IPS,
|
allowed_ips: existingIp,
|
||||||
persistent_keepalive: formData.persistent_keepalive,
|
persistent_keepalive: formData.persistent_keepalive,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -373,7 +384,7 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
|||||||
checked={data.peer_access !== false}
|
checked={data.peer_access !== false}
|
||||||
onChange={v => onChange({ peer_access: v })}
|
onChange={v => onChange({ peer_access: v })}
|
||||||
label="Allow peer-to-peer traffic"
|
label="Allow peer-to-peer traffic"
|
||||||
description="This peer can communicate with other VPN peers (10.0.0.0/24)"
|
description={`This peer can communicate with other VPN peers (${serverConf?.vpn_network || 'VPN subnet'})`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -675,7 +686,7 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-gray-500 mt-2">
|
<p className="text-xs text-gray-500 mt-2">
|
||||||
DNS is set to <code className="bg-gray-100 px-1 rounded">172.20.0.3</code> (PIC CoreDNS) — required to resolve <code className="bg-gray-100 px-1 rounded">.cell</code> domains.
|
DNS is set to <code className="bg-gray-100 px-1 rounded">{serverConf?.dns_ip || '172.20.0.3'}</code> (PIC CoreDNS) — required to resolve <code className="bg-gray-100 px-1 rounded">.{domain}</code> domains.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -181,21 +181,21 @@ function WireGuard() {
|
|||||||
return { public_key: '', endpoint: '<SERVER_IP>:51820' };
|
return { public_key: '', endpoint: '<SERVER_IP>:51820' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const CELL_DNS = '172.20.0.3';
|
|
||||||
const SPLIT_TUNNEL_IPS = '10.0.0.0/24, 172.20.0.0/16';
|
|
||||||
const FULL_TUNNEL_IPS = '0.0.0.0/0, ::/0';
|
const FULL_TUNNEL_IPS = '0.0.0.0/0, ::/0';
|
||||||
|
|
||||||
const generateWireGuardConfig = (peer, mode = tunnelMode) => {
|
const generateWireGuardConfig = (peer, mode = tunnelMode) => {
|
||||||
const serverPublicKey = peer.server_public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER";
|
const serverPublicKey = peer.server_public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER";
|
||||||
const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820";
|
const serverEndpoint = peer.server_endpoint || serverConfig?.endpoint || "YOUR_SERVER_IP:51820";
|
||||||
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
|
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
|
||||||
const peerAddress = peer.ip?.includes('/') ? peer.ip : `${peer.ip}/32`;
|
const peerAddress = peer.ip?.includes('/') ? peer.ip : `${peer.ip}/32`;
|
||||||
const allowedIPs = mode === 'full' ? FULL_TUNNEL_IPS : SPLIT_TUNNEL_IPS;
|
const splitTunnelIPs = serverConfig?.split_tunnel_ips || '10.0.0.0/24, 172.20.0.0/16';
|
||||||
|
const allowedIPs = mode === 'full' ? FULL_TUNNEL_IPS : splitTunnelIPs;
|
||||||
|
const dnsIp = serverConfig?.dns_ip || '172.20.0.3';
|
||||||
|
|
||||||
return `[Interface]
|
return `[Interface]
|
||||||
PrivateKey = ${privateKey}
|
PrivateKey = ${privateKey}
|
||||||
Address = ${peerAddress}
|
Address = ${peerAddress}
|
||||||
DNS = ${CELL_DNS}
|
DNS = ${dnsIp}
|
||||||
|
|
||||||
[Peer]
|
[Peer]
|
||||||
PublicKey = ${serverPublicKey}
|
PublicKey = ${serverPublicKey}
|
||||||
@@ -631,7 +631,7 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mb-3">
|
<p className="text-xs text-gray-500 mb-3">
|
||||||
{tunnelMode === 'split'
|
{tunnelMode === 'split'
|
||||||
? 'Split tunnel: only cell services (10.0.0.0/24, 172.20.0.0/16) route through VPN — local network & internet traffic stay direct.'
|
? `Split tunnel: only cell services (${serverConfig?.split_tunnel_ips || '10.0.0.0/24, 172.20.0.0/16'}) route through VPN — local network & internet traffic stay direct.`
|
||||||
: 'Full tunnel: all traffic (internet + local) routes through VPN server.'}
|
: 'Full tunnel: all traffic (internet + local) routes through VPN server.'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user