fix: split-tunnel default for peers, port check via wg interface, tunnel mode toggle in UI

- check_port_open now checks if wg0 interface is actually listening (via
  'wg show wg0') instead of requiring a live peer handshake. This means
  the port shows 'Open' whenever WireGuard is running, not only when a
  peer has connected recently.

- get_peer_config defaults to split-tunnel AllowedIPs (10.0.0.0/24,
  172.20.0.0/16) so VPN clients only route cell service traffic through
  the tunnel. Local LAN traffic (192.168.x.x etc.) stays direct, fixing
  the 60-120ms penalty when pinging local hosts while on VPN.

- Peer config modal now uses cell DNS (172.20.0.2) so .cell domains
  resolve correctly with both split and full tunnel.

- Added split/full tunnel toggle in the peer config modal so users can
  download either config variant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 14:18:43 -04:00
parent d3294552f0
commit 0b5a5b23e8
2 changed files with 63 additions and 56 deletions
+42 -35
View File
@@ -16,6 +16,7 @@ function WireGuard() {
const [peerConfig, setPeerConfig] = useState('');
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
const [peerStatuses, setPeerStatuses] = useState({});
const [tunnelMode, setTunnelMode] = useState('split'); // 'split' or 'full'
useEffect(() => {
fetchWireGuardData();
@@ -119,28 +120,12 @@ function WireGuard() {
await fetchWireGuardData();
};
const handleViewPeerConfig = async (peer) => {
const handleViewPeerConfig = async (peer, mode = tunnelMode) => {
setSelectedPeer(peer);
try {
// Try to get existing config first
const response = await wireguardAPI.getPeerConfig({ name: peer.name });
let config = response.data.config;
// If no config exists, generate a complete one with real server config
if (!config || config === 'Configuration not available') {
// Get server configuration first
const serverConfig = await getServerConfig();
// Create peer with server config
const peerWithServerConfig = {
...peer,
server_public_key: serverConfig.public_key,
server_endpoint: serverConfig.endpoint
};
config = generateWireGuardConfig(peerWithServerConfig);
}
const sc = await getServerConfig();
const peerWithServerConfig = { ...peer, server_public_key: sc.public_key, server_endpoint: sc.endpoint };
const config = generateWireGuardConfig(peerWithServerConfig, mode);
setPeerConfig(config);
// Generate QR code for the config
@@ -196,25 +181,26 @@ function WireGuard() {
return { public_key: '', endpoint: '<SERVER_IP>:51820' };
};
const generateWireGuardConfig = (peer) => {
// Use real keys from the peer data
const CELL_DNS = '172.20.0.2';
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 serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0";
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
// Check if IP already has a subnet mask, if not add /32
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;
return `[Interface]
PrivateKey = ${privateKey}
Address = ${peerAddress}
DNS = 8.8.8.8, 1.1.1.1
DNS = ${CELL_DNS}
[Peer]
PublicKey = ${serverPublicKey}
Endpoint = ${serverEndpoint}
AllowedIPs = ${serverAllowedIPs}
AllowedIPs = ${allowedIPs}
PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
};
@@ -620,13 +606,34 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
{selectedPeer.name} Configuration
</h3>
</div>
<button
onClick={() => setShowPeerConfig(false)}
className="text-gray-400 hover:text-gray-600"
>
</button>
<div className="flex items-center space-x-3">
<div className="flex items-center bg-gray-100 rounded-lg p-1 text-xs">
<button
onClick={() => { setTunnelMode('split'); handleViewPeerConfig(selectedPeer, 'split'); }}
className={`px-2 py-1 rounded ${tunnelMode === 'split' ? 'bg-white shadow text-primary-700 font-medium' : 'text-gray-500'}`}
>
Split tunnel
</button>
<button
onClick={() => { setTunnelMode('full'); handleViewPeerConfig(selectedPeer, 'full'); }}
className={`px-2 py-1 rounded ${tunnelMode === 'full' ? 'bg-white shadow text-primary-700 font-medium' : 'text-gray-500'}`}
>
Full tunnel
</button>
</div>
<button
onClick={() => setShowPeerConfig(false)}
className="text-gray-400 hover:text-gray-600"
>
</button>
</div>
</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.'
: 'Full tunnel: all traffic (internet + local) routes through VPN server.'}
</p>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">