feat(server): add configurable client endpoint and allowed IPs for generated VPN configs
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# 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)
|
## 2026-03-30 - 1.15.0 - feat(vpnserver)
|
||||||
add nftables-backed destination policy enforcement for TUN mode
|
add nftables-backed destination policy enforcement for TUN mode
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,13 @@ pub struct ServerConfig {
|
|||||||
pub wg_listen_port: Option<u16>,
|
pub wg_listen_port: Option<u16>,
|
||||||
/// WireGuard: pre-configured peers.
|
/// WireGuard: pre-configured peers.
|
||||||
pub wg_peers: Option<Vec<crate::wireguard::WgPeerConfig>>,
|
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.
|
/// Information about a connected client.
|
||||||
@@ -587,9 +594,12 @@ impl VpnServer {
|
|||||||
state.client_registry.write().await.add(entry.clone())?;
|
state.client_registry.write().await.add(entry.clone())?;
|
||||||
|
|
||||||
// Build SmartVPN client config
|
// 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!({
|
let smartvpn_config = serde_json::json!({
|
||||||
"serverUrl": format!("wss://{}",
|
"serverUrl": smartvpn_server_url,
|
||||||
state.config.listen_addr.replace("0.0.0.0", "localhost")),
|
|
||||||
"serverPublicKey": state.config.public_key,
|
"serverPublicKey": state.config.public_key,
|
||||||
"clientPrivateKey": noise_priv,
|
"clientPrivateKey": noise_priv,
|
||||||
"clientPublicKey": noise_pub,
|
"clientPublicKey": noise_pub,
|
||||||
@@ -599,15 +609,21 @@ impl VpnServer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Build WireGuard config string
|
// 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!(
|
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,
|
wg_priv,
|
||||||
assigned_ip,
|
assigned_ip,
|
||||||
state.config.dns.as_ref()
|
state.config.dns.as_ref()
|
||||||
.map(|d| format!("DNS = {}", d.join(", ")))
|
.map(|d| format!("DNS = {}", d.join(", ")))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
state.config.public_key,
|
state.config.public_key,
|
||||||
state.config.listen_addr,
|
wg_allowed_ips,
|
||||||
|
wg_endpoint,
|
||||||
);
|
);
|
||||||
|
|
||||||
let entry_json = serde_json::to_value(&entry)?;
|
let entry_json = serde_json::to_value(&entry)?;
|
||||||
@@ -732,9 +748,12 @@ impl VpnServer {
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("0.0.0.0");
|
.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!({
|
let smartvpn_config = serde_json::json!({
|
||||||
"serverUrl": format!("wss://{}",
|
"serverUrl": smartvpn_server_url,
|
||||||
state.config.listen_addr.replace("0.0.0.0", "localhost")),
|
|
||||||
"serverPublicKey": state.config.public_key,
|
"serverPublicKey": state.config.public_key,
|
||||||
"clientPrivateKey": noise_priv,
|
"clientPrivateKey": noise_priv,
|
||||||
"clientPublicKey": noise_pub,
|
"clientPublicKey": noise_pub,
|
||||||
@@ -743,14 +762,20 @@ impl VpnServer {
|
|||||||
"keepaliveIntervalSecs": state.config.keepalive_interval_secs,
|
"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!(
|
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,
|
wg_priv, assigned_ip,
|
||||||
state.config.dns.as_ref()
|
state.config.dns.as_ref()
|
||||||
.map(|d| format!("DNS = {}", d.join(", ")))
|
.map(|d| format!("DNS = {}", d.join(", ")))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
state.config.public_key,
|
state.config.public_key,
|
||||||
state.config.listen_addr,
|
wg_allowed_ips,
|
||||||
|
wg_endpoint,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
@@ -774,10 +799,13 @@ impl VpnServer {
|
|||||||
|
|
||||||
match format {
|
match format {
|
||||||
"smartvpn" => {
|
"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!({
|
Ok(serde_json::json!({
|
||||||
"config": {
|
"config": {
|
||||||
"serverUrl": format!("wss://{}",
|
"serverUrl": smartvpn_server_url,
|
||||||
state.config.listen_addr.replace("0.0.0.0", "localhost")),
|
|
||||||
"serverPublicKey": state.config.public_key,
|
"serverPublicKey": state.config.public_key,
|
||||||
"clientPublicKey": entry.public_key,
|
"clientPublicKey": entry.public_key,
|
||||||
"dns": state.config.dns,
|
"dns": state.config.dns,
|
||||||
@@ -788,14 +816,20 @@ impl VpnServer {
|
|||||||
}
|
}
|
||||||
"wireguard" => {
|
"wireguard" => {
|
||||||
let assigned_ip = entry.assigned_ip.as_deref().unwrap_or("0.0.0.0");
|
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!(
|
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,
|
assigned_ip,
|
||||||
state.config.dns.as_ref()
|
state.config.dns.as_ref()
|
||||||
.map(|d| format!("DNS = {}", d.join(", ")))
|
.map(|d| format!("DNS = {}", d.join(", ")))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
state.config.public_key,
|
state.config.public_key,
|
||||||
state.config.listen_addr,
|
wg_allowed_ips,
|
||||||
|
wg_endpoint,
|
||||||
);
|
);
|
||||||
Ok(serde_json::json!({ "config": config }))
|
Ok(serde_json::json!({ "config": config }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartvpn',
|
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'
|
description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,14 @@ export interface IVpnServerConfig {
|
|||||||
* Controls where decrypted traffic goes: allow through, block, or redirect to a target.
|
* Controls where decrypted traffic goes: allow through, block, or redirect to a target.
|
||||||
* Default: all traffic passes through (backward compatible). */
|
* Default: all traffic passes through (backward compatible). */
|
||||||
destinationPolicy?: IDestinationPolicy;
|
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[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user