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:
+31
-20
@@ -1,18 +1,10 @@
|
||||
import { useState, useEffect } from '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 { useConfig } from '../contexts/ConfigContext';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
const CELL_DNS = '172.20.0.3';
|
||||
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 = () => ({
|
||||
name: '',
|
||||
@@ -53,7 +45,16 @@ function Toggle({ checked, onChange, label, description }) {
|
||||
}
|
||||
|
||||
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 [serverConf, setServerConf] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
@@ -77,9 +78,10 @@ function Peers() {
|
||||
|
||||
const fetchPeers = async () => {
|
||||
try {
|
||||
const [regResp, statusResp] = await Promise.all([
|
||||
const [regResp, statusResp, scResp] = await Promise.all([
|
||||
peerAPI.getPeers(),
|
||||
wireguardAPI.getPeerStatuses().catch(() => ({ data: {} })),
|
||||
fetch('/api/wireguard/server-config').then(r => r.ok ? r.json() : null).catch(() => null),
|
||||
]);
|
||||
const regPeers = regResp.data || [];
|
||||
const statusMap = statusResp.data || {};
|
||||
@@ -93,6 +95,7 @@ function Peers() {
|
||||
transfer_tx: statusMap[p.public_key]?.transfer_tx ?? 0,
|
||||
}));
|
||||
setPeers(merged);
|
||||
if (scResp) setServerConf(scResp);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch peers:', err);
|
||||
} finally {
|
||||
@@ -101,23 +104,30 @@ function Peers() {
|
||||
};
|
||||
|
||||
const getServerConfig = async () => {
|
||||
if (serverConf) return serverConf;
|
||||
try {
|
||||
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 {}
|
||||
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 serverPubKey = peer.server_public_key || serverConf?.public_key || 'SERVER_PUBLIC_KEY_PLACEHOLDER';
|
||||
const endpoint = peer.server_endpoint || serverConf?.endpoint || 'YOUR_SERVER_IP:51820';
|
||||
const serverPubKey = peer.server_public_key || sc?.public_key || 'SERVER_PUBLIC_KEY_PLACEHOLDER';
|
||||
const endpoint = peer.server_endpoint || sc?.endpoint || 'YOUR_SERVER_IP:51820';
|
||||
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]
|
||||
PrivateKey = ${privateKey}
|
||||
Address = ${address}
|
||||
DNS = ${CELL_DNS}
|
||||
DNS = ${dnsIp}
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${serverPubKey}
|
||||
@@ -216,11 +226,12 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
});
|
||||
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({
|
||||
name: selectedPeer.name,
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -373,7 +384,7 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
checked={data.peer_access !== false}
|
||||
onChange={v => onChange({ peer_access: v })}
|
||||
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>
|
||||
@@ -675,7 +686,7 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
</div>
|
||||
)}
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -181,21 +181,21 @@ function WireGuard() {
|
||||
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 generateWireGuardConfig = (peer, mode = tunnelMode) => {
|
||||
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 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]
|
||||
PrivateKey = ${privateKey}
|
||||
Address = ${peerAddress}
|
||||
DNS = ${CELL_DNS}
|
||||
DNS = ${dnsIp}
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${serverPublicKey}
|
||||
@@ -631,7 +631,7 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mb-3">
|
||||
{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.'}
|
||||
</p>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user