Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17af7ab289 | |||
| b98006e792 | |||
| fbfbe0db51 | |||
| 67542f0be7 |
14
changelog.md
14
changelog.md
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-31 - 1.17.1 - fix(readme)
|
||||
document per-transport metrics and handshake-driven WireGuard connection state
|
||||
|
||||
- Add README examples for getStatistics() per-transport active client and total connection counters
|
||||
- Clarify that WireGuard peers are marked connected only after a successful handshake and disconnect after idle timeout
|
||||
- Refresh API and project structure documentation to reflect newly documented stats fields and source files
|
||||
|
||||
## 2026-03-31 - 1.17.0 - feat(wireguard)
|
||||
track per-transport server statistics and make WireGuard clients active only after handshake
|
||||
|
||||
- add websocket, quic, and wireguard active-client and total-connection counters to server statistics
|
||||
- register WireGuard peers without marking them active until handshake/data is received, and remove them from active clients on expiration or idle timeout
|
||||
- sync WireGuard byte counters into aggregate server stats independently of active client presence and expose new statistics fields in TypeScript interfaces
|
||||
|
||||
## 2026-03-31 - 1.16.5 - fix(rust-userspace-nat)
|
||||
improve TCP session backpressure, buffering, and idle cleanup in userspace NAT
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartvpn",
|
||||
"version": "1.16.5",
|
||||
"version": "1.17.1",
|
||||
"private": false,
|
||||
"description": "A VPN solution with TypeScript control plane and Rust data plane daemon",
|
||||
"type": "module",
|
||||
|
||||
46
readme.md
46
readme.md
@@ -6,11 +6,12 @@ A high-performance VPN solution with a **TypeScript control plane** and a **Rust
|
||||
🚀 **Triple transport**: WebSocket (Cloudflare-friendly), raw **QUIC** (datagrams), and **WireGuard** (standard protocol)
|
||||
🛡️ **ACL engine** — deny-overrides-allow IP filtering, aligned with SmartProxy conventions
|
||||
🔀 **PROXY protocol v2** — real client IPs behind reverse proxies (HAProxy, SmartProxy, Cloudflare Spectrum)
|
||||
📊 **Adaptive QoS**: per-client rate limiting, priority queues, connection quality tracking
|
||||
📊 **Per-transport metrics**: active clients and total connections broken down by websocket, QUIC, and WireGuard
|
||||
🔄 **Hub API**: one `createClient()` call generates keys, assigns IP, returns both SmartVPN + WireGuard configs
|
||||
📡 **Real-time telemetry**: RTT, jitter, loss ratio, link health — all via typed APIs
|
||||
🌐 **Unified forwarding pipeline**: all transports share the same engine — TUN (kernel), userspace NAT (no root), or testing mode
|
||||
🎯 **Destination routing policy**: force-target, block, or allow traffic per destination with nftables integration
|
||||
⚡ **Handshake-driven WireGuard state**: peers appear as "connected" only after a successful WireGuard handshake, and auto-disconnect on idle timeout
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
@@ -140,6 +141,30 @@ Every client authenticates with a **Noise IK handshake** (`Noise_IK_25519_ChaCha
|
||||
|
||||
The server runs **all three simultaneously** by default with `transportMode: 'all'`. All transports share the same unified forwarding pipeline (`ForwardingEngine`), IP pool, client registry, and stats — so WireGuard peers get the same userspace NAT, rate limiting, and monitoring as WS/QUIC clients. Clients auto-negotiate with `transport: 'auto'` (tries QUIC first, falls back to WS).
|
||||
|
||||
### 📊 Per-Transport Metrics
|
||||
|
||||
Server statistics include per-transport breakdowns so you can see exactly how many clients use each protocol:
|
||||
|
||||
```typescript
|
||||
const stats = await server.getStatistics();
|
||||
|
||||
// Aggregate
|
||||
console.log(stats.activeClients); // total connected clients
|
||||
console.log(stats.totalConnections); // total connections since start
|
||||
|
||||
// Per-transport active clients
|
||||
console.log(stats.activeClientsWebsocket); // currently connected via WS
|
||||
console.log(stats.activeClientsQuic); // currently connected via QUIC
|
||||
console.log(stats.activeClientsWireguard); // currently connected via WireGuard
|
||||
|
||||
// Per-transport total connections
|
||||
console.log(stats.totalConnectionsWebsocket);
|
||||
console.log(stats.totalConnectionsQuic);
|
||||
console.log(stats.totalConnectionsWireguard);
|
||||
```
|
||||
|
||||
**WireGuard connection state is handshake-driven** — registered WireGuard peers do NOT appear as "connected" until their first successful WireGuard handshake completes. They automatically disconnect after 180 seconds of inactivity or when boringtun reports `ConnectionExpired`. This matches how WebSocket/QUIC clients behave: they appear on connection and disappear on disconnect.
|
||||
|
||||
### 🛡️ ACL Engine (SmartProxy-Aligned)
|
||||
|
||||
Security policies per client, using the same `ipAllowList` / `ipBlockList` naming convention as `@push.rocks/smartproxy`:
|
||||
@@ -256,8 +281,9 @@ The userspace NAT mode extracts destination IP/port from IP packets, opens a rea
|
||||
- **Connection quality**: Smoothed RTT, jitter, min/max RTT, loss ratio, link health (`healthy` / `degraded` / `critical`)
|
||||
- **Adaptive keepalives**: Interval adjusts based on link health (60s → 30s → 10s)
|
||||
- **Per-client rate limiting**: Token bucket with configurable bytes/sec and burst
|
||||
- **Dead-peer detection**: 180s inactivity timeout
|
||||
- **Dead-peer detection**: 180s inactivity timeout (all transports)
|
||||
- **MTU management**: Automatic overhead calculation (IP+TCP+WS+Noise = 79 bytes)
|
||||
- **Per-transport stats**: Active client and total connection counts broken down by websocket, QUIC, and WireGuard
|
||||
|
||||
### 🏷️ Client Tags (Trusted vs Informational)
|
||||
|
||||
@@ -425,6 +451,7 @@ server.on('reconnected', () => { /* socket transport reconnected */ });
|
||||
| `IClientRateLimit` | Rate limiting config (bytesPerSec, burstBytes) |
|
||||
| `IClientConfigBundle` | Full config bundle returned by `createClient()` — includes SmartVPN config, WireGuard .conf, and secrets |
|
||||
| `IVpnClientInfo` | Connected client info (IP, stats, authenticated key, remote addr, transport type) |
|
||||
| `IVpnServerStatistics` | Server stats with per-transport breakdowns (activeClientsWebsocket/Quic/Wireguard, totalConnections*) |
|
||||
| `IVpnConnectionQuality` | RTT, jitter, loss ratio, link health |
|
||||
| `IVpnMtuInfo` | TUN MTU, effective MTU, overhead bytes, oversized packet stats |
|
||||
| `IVpnKeypair` | Base64-encoded public/private key pair |
|
||||
@@ -443,7 +470,7 @@ server.on('reconnected', () => { /* socket transport reconnected */ });
|
||||
| `exportClientConfig` | Re-export as SmartVPN config or WireGuard `.conf` |
|
||||
| `listClients` / `disconnectClient` | Manage live connections |
|
||||
| `setClientRateLimit` / `removeClientRateLimit` | Runtime rate limit adjustments |
|
||||
| `getStatus` / `getStatistics` / `getClientTelemetry` | Monitoring |
|
||||
| `getStatus` / `getStatistics` / `getClientTelemetry` | Monitoring (stats include per-transport breakdowns) |
|
||||
| `generateKeypair` / `generateWgKeypair` / `generateClientKeypair` | Key generation |
|
||||
| `addWgPeer` / `removeWgPeer` / `listWgPeers` | WireGuard peer management |
|
||||
|
||||
@@ -541,6 +568,7 @@ smartvpn/
|
||||
│ ├── index.ts # All exports
|
||||
│ ├── smartvpn.interfaces.ts # Interfaces, types, IPC command maps
|
||||
│ ├── smartvpn.plugins.ts # Dependency imports
|
||||
│ ├── smartvpn.paths.ts # Binary path resolution
|
||||
│ ├── smartvpn.classes.vpnserver.ts
|
||||
│ ├── smartvpn.classes.vpnclient.ts
|
||||
│ ├── smartvpn.classes.vpnbridge.ts
|
||||
@@ -558,13 +586,19 @@ smartvpn/
|
||||
│ ├── proxy_protocol.rs # PROXY protocol v2 parser
|
||||
│ ├── management.rs # JSON-lines IPC
|
||||
│ ├── transport.rs # WebSocket transport
|
||||
│ ├── transport_trait.rs # Transport abstraction (Sink/Stream)
|
||||
│ ├── quic_transport.rs # QUIC transport
|
||||
│ ├── wireguard.rs # WireGuard (boringtun)
|
||||
│ ├── codec.rs # Binary frame protocol
|
||||
│ ├── keepalive.rs # Adaptive keepalives
|
||||
│ ├── ratelimit.rs # Token bucket
|
||||
│ ├── userspace_nat.rs # Userspace TCP/UDP NAT proxy
|
||||
│ └── ... # tunnel, network, telemetry, qos, mtu, reconnect
|
||||
│ ├── tunnel.rs # TUN device management
|
||||
│ ├── network.rs # IP pool + networking
|
||||
│ ├── telemetry.rs # RTT/jitter/loss tracking
|
||||
│ ├── qos.rs # Priority queues + smart dropping
|
||||
│ ├── mtu.rs # MTU + ICMP too-big
|
||||
│ └── reconnect.rs # Exponential backoff + session tokens
|
||||
├── test/ # Test files
|
||||
├── dist_ts/ # Compiled TypeScript
|
||||
└── dist_rust/ # Cross-compiled binaries (linux amd64 + arm64)
|
||||
@@ -572,7 +606,7 @@ smartvpn/
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license.md) file.
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
@@ -584,7 +618,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
@@ -132,6 +132,14 @@ pub struct ServerStatistics {
|
||||
pub uptime_seconds: u64,
|
||||
pub active_clients: u64,
|
||||
pub total_connections: u64,
|
||||
/// Per-transport active client counts.
|
||||
pub active_clients_websocket: u64,
|
||||
pub active_clients_quic: u64,
|
||||
pub active_clients_wireguard: u64,
|
||||
/// Per-transport total connection counts.
|
||||
pub total_connections_websocket: u64,
|
||||
pub total_connections_quic: u64,
|
||||
pub total_connections_wireguard: u64,
|
||||
}
|
||||
|
||||
/// The forwarding engine determines how decrypted IP packets are routed.
|
||||
@@ -450,7 +458,21 @@ impl VpnServer {
|
||||
if let Some(ref state) = self.state {
|
||||
let mut stats = state.stats.read().await.clone();
|
||||
stats.uptime_seconds = state.started_at.elapsed().as_secs();
|
||||
stats.active_clients = state.clients.read().await.len() as u64;
|
||||
let clients = state.clients.read().await;
|
||||
stats.active_clients = clients.len() as u64;
|
||||
// Compute per-transport active counts
|
||||
stats.active_clients_websocket = 0;
|
||||
stats.active_clients_quic = 0;
|
||||
stats.active_clients_wireguard = 0;
|
||||
for info in clients.values() {
|
||||
match info.transport_type.as_str() {
|
||||
"websocket" => stats.active_clients_websocket += 1,
|
||||
"quic" => stats.active_clients_quic += 1,
|
||||
"wireguard" => stats.active_clients_wireguard += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
drop(clients);
|
||||
stats
|
||||
} else {
|
||||
ServerStatistics::default()
|
||||
@@ -1303,6 +1325,11 @@ async fn handle_client_connection(
|
||||
{
|
||||
let mut stats = state.stats.write().await;
|
||||
stats.total_connections += 1;
|
||||
match transport_type {
|
||||
"websocket" => stats.total_connections_websocket += 1,
|
||||
"quic" => stats.total_connections_quic += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Send assigned IP info (encrypted), include effective MTU
|
||||
|
||||
@@ -220,6 +220,15 @@ struct PeerState {
|
||||
#[allow(dead_code)]
|
||||
persistent_keepalive: Option<u16>,
|
||||
stats: WgPeerStats,
|
||||
/// Whether this peer has completed a WireGuard handshake and is in state.clients.
|
||||
is_connected: bool,
|
||||
/// Last time we received data or handshake activity from this peer.
|
||||
last_activity_at: Option<tokio::time::Instant>,
|
||||
/// VPN IP assigned during registration (used for connect/disconnect).
|
||||
vpn_ip: Option<Ipv4Addr>,
|
||||
/// Previous synced byte counts for aggregate stats delta tracking.
|
||||
prev_synced_bytes_sent: u64,
|
||||
prev_synced_bytes_received: u64,
|
||||
}
|
||||
|
||||
impl PeerState {
|
||||
@@ -276,6 +285,11 @@ fn add_peer_to_loop(
|
||||
endpoint,
|
||||
persistent_keepalive: config.persistent_keepalive,
|
||||
stats: WgPeerStats::default(),
|
||||
is_connected: false,
|
||||
last_activity_at: None,
|
||||
vpn_ip: None,
|
||||
prev_synced_bytes_sent: 0,
|
||||
prev_synced_bytes_received: 0,
|
||||
});
|
||||
|
||||
info!("Added WireGuard peer: {}", config.public_key);
|
||||
@@ -323,8 +337,9 @@ fn wg_timestamp_now() -> String {
|
||||
format!("{}", duration.as_secs())
|
||||
}
|
||||
|
||||
/// Register a WG peer in ServerState (tun_routes, clients, ip_pool).
|
||||
/// Returns the VPN IP and the per-peer return-packet receiver.
|
||||
/// Register a WG peer in ServerState (tun_routes + ip_pool only).
|
||||
/// Does NOT add to state.clients — peers appear there only after handshake.
|
||||
/// Returns the VPN IP.
|
||||
async fn register_wg_peer(
|
||||
state: &Arc<ServerState>,
|
||||
peer: &PeerState,
|
||||
@@ -366,13 +381,23 @@ async fn register_wg_peer(
|
||||
});
|
||||
}
|
||||
|
||||
// Insert ClientInfo
|
||||
info!("WG peer {} registered with IP {} (not yet connected)", peer.public_key_b64, vpn_ip);
|
||||
Ok(Some(vpn_ip))
|
||||
}
|
||||
|
||||
/// Add a WG peer to state.clients on first successful handshake (data received).
|
||||
async fn connect_wg_peer(
|
||||
state: &Arc<ServerState>,
|
||||
peer: &PeerState,
|
||||
vpn_ip: Ipv4Addr,
|
||||
) {
|
||||
let client_id = format!("wg-{}", &peer.public_key_b64[..8.min(peer.public_key_b64.len())]);
|
||||
let client_info = ClientInfo {
|
||||
client_id: client_id.clone(),
|
||||
assigned_ip: vpn_ip.to_string(),
|
||||
connected_since: wg_timestamp_now(),
|
||||
bytes_sent: 0,
|
||||
bytes_received: 0,
|
||||
bytes_sent: peer.stats.bytes_sent,
|
||||
bytes_received: peer.stats.bytes_received,
|
||||
packets_dropped: 0,
|
||||
bytes_dropped: 0,
|
||||
last_keepalive_at: None,
|
||||
@@ -380,13 +405,31 @@ async fn register_wg_peer(
|
||||
rate_limit_bytes_per_sec: None,
|
||||
burst_bytes: None,
|
||||
authenticated_key: peer.public_key_b64.clone(),
|
||||
registered_client_id: client_id,
|
||||
registered_client_id: client_id.clone(),
|
||||
remote_addr: peer.endpoint.map(|e| e.to_string()),
|
||||
transport_type: "wireguard".to_string(),
|
||||
};
|
||||
state.clients.write().await.insert(client_info.client_id.clone(), client_info);
|
||||
|
||||
Ok(Some(vpn_ip))
|
||||
// Increment total_connections
|
||||
{
|
||||
let mut stats = state.stats.write().await;
|
||||
stats.total_connections += 1;
|
||||
stats.total_connections_wireguard += 1;
|
||||
}
|
||||
|
||||
info!("WG peer {} connected (IP: {})", peer.public_key_b64, vpn_ip);
|
||||
}
|
||||
|
||||
/// Remove a WG peer from state.clients (disconnect without unregistering).
|
||||
async fn disconnect_wg_peer(
|
||||
state: &Arc<ServerState>,
|
||||
pubkey: &str,
|
||||
) {
|
||||
let client_id = format!("wg-{}", &pubkey[..8.min(pubkey.len())]);
|
||||
if state.clients.write().await.remove(&client_id).is_some() {
|
||||
info!("WG peer {} disconnected (removed from active clients)", pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unregister a WG peer from ServerState.
|
||||
@@ -460,6 +503,11 @@ pub async fn run_wg_listener(
|
||||
endpoint,
|
||||
persistent_keepalive: peer_config.persistent_keepalive,
|
||||
stats: WgPeerStats::default(),
|
||||
is_connected: false,
|
||||
last_activity_at: None,
|
||||
vpn_ip: None,
|
||||
prev_synced_bytes_sent: 0,
|
||||
prev_synced_bytes_received: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -470,11 +518,12 @@ pub async fn run_wg_listener(
|
||||
// Merged return-packet channel: all per-peer channels feed into this
|
||||
let (wg_return_tx, mut wg_return_rx) = mpsc::channel::<(String, Vec<u8>)>(1024);
|
||||
|
||||
// Register initial peers in ServerState and track their VPN IPs
|
||||
// Register initial peers in ServerState (IP reservation + tun_routes only, NOT state.clients)
|
||||
let mut peer_vpn_ips: HashMap<String, Ipv4Addr> = HashMap::new();
|
||||
for peer in &peers {
|
||||
for peer in peers.iter_mut() {
|
||||
if let Ok(Some(ip)) = register_wg_peer(&state, peer, &wg_return_tx).await {
|
||||
peer_vpn_ips.insert(peer.public_key_b64.clone(), ip);
|
||||
peer.vpn_ip = Some(ip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,6 +532,7 @@ pub async fn run_wg_listener(
|
||||
let mut dst_buf = vec![0u8; WG_BUFFER_SIZE];
|
||||
let mut timer = tokio::time::interval(std::time::Duration::from_millis(TIMER_TICK_MS));
|
||||
let mut stats_timer = tokio::time::interval(std::time::Duration::from_secs(1));
|
||||
let mut idle_check_timer = tokio::time::interval(std::time::Duration::from_secs(10));
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -506,6 +556,8 @@ pub async fn run_wg_listener(
|
||||
}
|
||||
}
|
||||
peer.endpoint = Some(src_addr);
|
||||
// Handshake response counts as activity
|
||||
peer.last_activity_at = Some(tokio::time::Instant::now());
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
@@ -530,6 +582,15 @@ pub async fn run_wg_listener(
|
||||
peer.stats.packets_received += 1;
|
||||
}
|
||||
peer.endpoint = Some(src_addr);
|
||||
// Track activity and detect handshake completion
|
||||
peer.last_activity_at = Some(tokio::time::Instant::now());
|
||||
if !peer.is_connected {
|
||||
peer.is_connected = true;
|
||||
peer.stats.last_handshake_time = Some(wg_timestamp_now());
|
||||
if let Some(vpn_ip) = peer.vpn_ip {
|
||||
connect_wg_peer(&state, peer, vpn_ip).await;
|
||||
}
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
@@ -553,6 +614,15 @@ pub async fn run_wg_listener(
|
||||
peer.stats.packets_received += 1;
|
||||
}
|
||||
peer.endpoint = Some(src_addr);
|
||||
// Track activity and detect handshake completion
|
||||
peer.last_activity_at = Some(tokio::time::Instant::now());
|
||||
if !peer.is_connected {
|
||||
peer.is_connected = true;
|
||||
peer.stats.last_handshake_time = Some(wg_timestamp_now());
|
||||
if let Some(vpn_ip) = peer.vpn_ip {
|
||||
connect_wg_peer(&state, peer, vpn_ip).await;
|
||||
}
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
@@ -603,6 +673,10 @@ pub async fn run_wg_listener(
|
||||
}
|
||||
TunnResult::Err(WireGuardError::ConnectionExpired) => {
|
||||
warn!("WG peer {} connection expired", peer.public_key_b64);
|
||||
if peer.is_connected {
|
||||
peer.is_connected = false;
|
||||
disconnect_wg_peer(&state, &peer.public_key_b64).await;
|
||||
}
|
||||
}
|
||||
TunnResult::Err(e) => {
|
||||
debug!("Timer error for WG peer {}: {:?}",
|
||||
@@ -617,19 +691,39 @@ pub async fn run_wg_listener(
|
||||
_ = stats_timer.tick() => {
|
||||
let mut clients = state.clients.write().await;
|
||||
let mut stats = state.stats.write().await;
|
||||
for peer in peers.iter() {
|
||||
for peer in peers.iter_mut() {
|
||||
// Always update aggregate stats (regardless of connection state)
|
||||
let delta_sent = peer.stats.bytes_sent.saturating_sub(peer.prev_synced_bytes_sent);
|
||||
let delta_recv = peer.stats.bytes_received.saturating_sub(peer.prev_synced_bytes_received);
|
||||
if delta_sent > 0 || delta_recv > 0 {
|
||||
stats.bytes_sent += delta_sent;
|
||||
stats.bytes_received += delta_recv;
|
||||
peer.prev_synced_bytes_sent = peer.stats.bytes_sent;
|
||||
peer.prev_synced_bytes_received = peer.stats.bytes_received;
|
||||
}
|
||||
|
||||
// Only update ClientInfo if peer is connected (in state.clients)
|
||||
let client_id = format!("wg-{}", &peer.public_key_b64[..8.min(peer.public_key_b64.len())]);
|
||||
if let Some(info) = clients.get_mut(&client_id) {
|
||||
// Update stats delta
|
||||
let prev_sent = info.bytes_sent;
|
||||
let prev_recv = info.bytes_received;
|
||||
info.bytes_sent = peer.stats.bytes_sent;
|
||||
info.bytes_received = peer.stats.bytes_received;
|
||||
info.remote_addr = peer.endpoint.map(|e| e.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update aggregate stats
|
||||
stats.bytes_sent += peer.stats.bytes_sent.saturating_sub(prev_sent);
|
||||
stats.bytes_received += peer.stats.bytes_received.saturating_sub(prev_recv);
|
||||
// --- Idle timeout check (every 10s) ---
|
||||
_ = idle_check_timer.tick() => {
|
||||
let now = tokio::time::Instant::now();
|
||||
for peer in peers.iter_mut() {
|
||||
if peer.is_connected {
|
||||
if let Some(last) = peer.last_activity_at {
|
||||
if now.duration_since(last) > std::time::Duration::from_secs(180) {
|
||||
info!("WG peer {} idle timeout (180s), disconnecting", peer.public_key_b64);
|
||||
peer.is_connected = false;
|
||||
disconnect_wg_peer(&state, &peer.public_key_b64).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -646,11 +740,12 @@ pub async fn run_wg_listener(
|
||||
&config.private_key,
|
||||
);
|
||||
if result.is_ok() {
|
||||
// Register new peer in ServerState
|
||||
let peer = peers.last().unwrap();
|
||||
// Register new peer in ServerState (IP + tun_routes only)
|
||||
let peer = peers.last_mut().unwrap();
|
||||
match register_wg_peer(&state, peer, &wg_return_tx).await {
|
||||
Ok(Some(ip)) => {
|
||||
peer_vpn_ips.insert(peer_config.public_key.clone(), ip);
|
||||
peer.vpn_ip = Some(ip);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
@@ -1239,7 +1334,7 @@ mod tests {
|
||||
let _ = server_tunn.decapsulate(None, &pkt_copy, &mut buf_b);
|
||||
}
|
||||
TunnResult::Done => {}
|
||||
other => {
|
||||
_other => {
|
||||
// Drain
|
||||
loop {
|
||||
match client_tunn.decapsulate(None, &[], &mut buf_a) {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartvpn',
|
||||
version: '1.16.5',
|
||||
version: '1.17.1',
|
||||
description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
|
||||
}
|
||||
|
||||
@@ -217,6 +217,14 @@ export interface IVpnClientInfo {
|
||||
export interface IVpnServerStatistics extends IVpnStatistics {
|
||||
activeClients: number;
|
||||
totalConnections: number;
|
||||
/** Per-transport active client counts. */
|
||||
activeClientsWebsocket: number;
|
||||
activeClientsQuic: number;
|
||||
activeClientsWireguard: number;
|
||||
/** Per-transport total connection counts. */
|
||||
totalConnectionsWebsocket: number;
|
||||
totalConnectionsQuic: number;
|
||||
totalConnectionsWireguard: number;
|
||||
}
|
||||
|
||||
export interface IVpnKeypair {
|
||||
|
||||
Reference in New Issue
Block a user