2 Commits

5 changed files with 63 additions and 14 deletions

View File

@@ -1,5 +1,12 @@
# Changelog
## 2026-03-30 - 1.16.0 - feat(server)
add configurable client endpoint and allowed IPs for generated VPN configs
- adds serverEndpoint to generated SmartVPN and WireGuard client configs so remote clients can use a public address instead of the listen address
- adds clientAllowedIPs to generated WireGuard configs to support full-tunnel or split-tunnel routing
- updates TypeScript interfaces to expose the new server configuration options
## 2026-03-30 - 1.15.0 - feat(vpnserver)
add nftables-backed destination policy enforcement for TUN mode

View File

@@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartvpn",
"version": "1.15.0",
"version": "1.16.0",
"private": false,
"description": "A VPN solution with TypeScript control plane and Rust data plane daemon",
"type": "module",

View File

@@ -84,6 +84,13 @@ pub struct ServerConfig {
pub wg_listen_port: Option<u16>,
/// WireGuard: pre-configured peers.
pub wg_peers: Option<Vec<crate::wireguard::WgPeerConfig>>,
/// Public endpoint address for generated client configs (e.g. "vpn.example.com:51820").
/// Used as WireGuard `Endpoint` and SmartVPN `serverUrl` host.
/// Defaults to listen_addr.
pub server_endpoint: Option<String>,
/// AllowedIPs for generated WireGuard client configs.
/// Defaults to ["0.0.0.0/0"] (full tunnel).
pub client_allowed_ips: Option<Vec<String>>,
}
/// Information about a connected client.
@@ -587,9 +594,12 @@ impl VpnServer {
state.client_registry.write().await.add(entry.clone())?;
// Build SmartVPN client config
let smartvpn_server_url = format!("wss://{}",
state.config.server_endpoint.as_deref()
.unwrap_or(&state.config.listen_addr)
.replace("0.0.0.0", "localhost"));
let smartvpn_config = serde_json::json!({
"serverUrl": format!("wss://{}",
state.config.listen_addr.replace("0.0.0.0", "localhost")),
"serverUrl": smartvpn_server_url,
"serverPublicKey": state.config.public_key,
"clientPrivateKey": noise_priv,
"clientPublicKey": noise_pub,
@@ -599,15 +609,21 @@ impl VpnServer {
});
// Build WireGuard config string
let wg_endpoint = state.config.server_endpoint.as_deref()
.unwrap_or(&state.config.listen_addr);
let wg_allowed_ips = state.config.client_allowed_ips.as_ref()
.map(|ips| ips.join(", "))
.unwrap_or_else(|| "0.0.0.0/0".to_string());
let wg_config = format!(
"[Interface]\nPrivateKey = {}\nAddress = {}/24\n{}\n[Peer]\nPublicKey = {}\nAllowedIPs = 0.0.0.0/0\nEndpoint = {}\nPersistentKeepalive = 25\n",
"[Interface]\nPrivateKey = {}\nAddress = {}/24\n{}\n[Peer]\nPublicKey = {}\nAllowedIPs = {}\nEndpoint = {}\nPersistentKeepalive = 25\n",
wg_priv,
assigned_ip,
state.config.dns.as_ref()
.map(|d| format!("DNS = {}", d.join(", ")))
.unwrap_or_default(),
state.config.public_key,
state.config.listen_addr,
wg_allowed_ips,
wg_endpoint,
);
let entry_json = serde_json::to_value(&entry)?;
@@ -732,9 +748,12 @@ impl VpnServer {
.and_then(|v| v.as_str())
.unwrap_or("0.0.0.0");
let smartvpn_server_url = format!("wss://{}",
state.config.server_endpoint.as_deref()
.unwrap_or(&state.config.listen_addr)
.replace("0.0.0.0", "localhost"));
let smartvpn_config = serde_json::json!({
"serverUrl": format!("wss://{}",
state.config.listen_addr.replace("0.0.0.0", "localhost")),
"serverUrl": smartvpn_server_url,
"serverPublicKey": state.config.public_key,
"clientPrivateKey": noise_priv,
"clientPublicKey": noise_pub,
@@ -743,14 +762,20 @@ impl VpnServer {
"keepaliveIntervalSecs": state.config.keepalive_interval_secs,
});
let wg_endpoint = state.config.server_endpoint.as_deref()
.unwrap_or(&state.config.listen_addr);
let wg_allowed_ips = state.config.client_allowed_ips.as_ref()
.map(|ips| ips.join(", "))
.unwrap_or_else(|| "0.0.0.0/0".to_string());
let wg_config = format!(
"[Interface]\nPrivateKey = {}\nAddress = {}/24\n{}\n[Peer]\nPublicKey = {}\nAllowedIPs = 0.0.0.0/0\nEndpoint = {}\nPersistentKeepalive = 25\n",
"[Interface]\nPrivateKey = {}\nAddress = {}/24\n{}\n[Peer]\nPublicKey = {}\nAllowedIPs = {}\nEndpoint = {}\nPersistentKeepalive = 25\n",
wg_priv, assigned_ip,
state.config.dns.as_ref()
.map(|d| format!("DNS = {}", d.join(", ")))
.unwrap_or_default(),
state.config.public_key,
state.config.listen_addr,
wg_allowed_ips,
wg_endpoint,
);
Ok(serde_json::json!({
@@ -774,10 +799,13 @@ impl VpnServer {
match format {
"smartvpn" => {
let smartvpn_server_url = format!("wss://{}",
state.config.server_endpoint.as_deref()
.unwrap_or(&state.config.listen_addr)
.replace("0.0.0.0", "localhost"));
Ok(serde_json::json!({
"config": {
"serverUrl": format!("wss://{}",
state.config.listen_addr.replace("0.0.0.0", "localhost")),
"serverUrl": smartvpn_server_url,
"serverPublicKey": state.config.public_key,
"clientPublicKey": entry.public_key,
"dns": state.config.dns,
@@ -788,14 +816,20 @@ impl VpnServer {
}
"wireguard" => {
let assigned_ip = entry.assigned_ip.as_deref().unwrap_or("0.0.0.0");
let wg_endpoint = state.config.server_endpoint.as_deref()
.unwrap_or(&state.config.listen_addr);
let wg_allowed_ips = state.config.client_allowed_ips.as_ref()
.map(|ips| ips.join(", "))
.unwrap_or_else(|| "0.0.0.0/0".to_string());
let config = format!(
"[Interface]\nAddress = {}/24\n{}\n[Peer]\nPublicKey = {}\nAllowedIPs = 0.0.0.0/0\nEndpoint = {}\nPersistentKeepalive = 25\n",
"[Interface]\nAddress = {}/24\n{}\n[Peer]\nPublicKey = {}\nAllowedIPs = {}\nEndpoint = {}\nPersistentKeepalive = 25\n",
assigned_ip,
state.config.dns.as_ref()
.map(|d| format!("DNS = {}", d.join(", ")))
.unwrap_or_default(),
state.config.public_key,
state.config.listen_addr,
wg_allowed_ips,
wg_endpoint,
);
Ok(serde_json::json!({ "config": config }))
}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartvpn',
version: '1.15.0',
version: '1.16.0',
description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
}

View File

@@ -129,6 +129,14 @@ export interface IVpnServerConfig {
* Controls where decrypted traffic goes: allow through, block, or redirect to a target.
* Default: all traffic passes through (backward compatible). */
destinationPolicy?: IDestinationPolicy;
/** Public endpoint address for generated client configs (e.g. 'vpn.example.com:51820').
* Used as the WireGuard `Endpoint =` and SmartVPN `serverUrl` host.
* Defaults to listenAddr (which is typically wrong for remote clients). */
serverEndpoint?: string;
/** AllowedIPs for generated WireGuard client configs.
* Controls what traffic the client routes through the VPN tunnel.
* Defaults to ['0.0.0.0/0'] (full tunnel). Set to e.g. ['10.8.0.0/24'] for split tunnel. */
clientAllowedIPs?: string[];
}
/**