fix(vpn): refresh a stored peer's WireGuard endpoint to the node's current IP
vpn.peer-config returned the config exactly as stored at creation time, so after a node moves networks the reused companion peer's QR encodes the old location's address (seen on .116: Endpoint = 10.125.9.0 from the previous LAN) and the tunnel can never connect. Rewrite the Endpoint line with the node's current address before rendering the QR, and persist it back so the downloadable .conf matches. Endpoint detection is shared with vpn.create-peer via current_wg_endpoint_host(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
f13fdc6451
commit
f25febf3bb
@ -437,6 +437,23 @@ impl RpcHandler {
|
|||||||
Ok(serde_json::json!({ "added": true, "npub": npub }))
|
Ok(serde_json::json!({ "added": true, "npub": npub }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The host address a WireGuard peer should dial — prefer the configured
|
||||||
|
/// host IP, then public-IP lookup, then first local address.
|
||||||
|
async fn current_wg_endpoint_host(&self) -> String {
|
||||||
|
if self.config.host_ip != "127.0.0.1" {
|
||||||
|
return self.config.host_ip.clone();
|
||||||
|
}
|
||||||
|
tokio::process::Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg("curl -s --connect-timeout 5 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}'")
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.unwrap_or_else(|| self.config.host_ip.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// vpn.create-peer — Generate a WireGuard peer config + QR code for mobile devices.
|
/// vpn.create-peer — Generate a WireGuard peer config + QR code for mobile devices.
|
||||||
pub(super) async fn handle_vpn_create_peer(
|
pub(super) async fn handle_vpn_create_peer(
|
||||||
&self,
|
&self,
|
||||||
@ -501,22 +518,7 @@ impl RpcHandler {
|
|||||||
.ok_or_else(|| anyhow::anyhow!("Cannot read server public key"))?
|
.ok_or_else(|| anyhow::anyhow!("Cannot read server public key"))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect host IP — prefer config, then nvpn, then system detection
|
let endpoint = format!("{}:51820", self.current_wg_endpoint_host().await);
|
||||||
let host_ip = if self.config.host_ip != "127.0.0.1" {
|
|
||||||
self.config.host_ip.clone()
|
|
||||||
} else {
|
|
||||||
// Fallback: get public IP via external service
|
|
||||||
tokio::process::Command::new("sh")
|
|
||||||
.arg("-c")
|
|
||||||
.arg("curl -s --connect-timeout 5 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}'")
|
|
||||||
.output()
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.unwrap_or_else(|| self.config.host_ip.clone())
|
|
||||||
};
|
|
||||||
let endpoint = format!("{}:51820", host_ip);
|
|
||||||
|
|
||||||
// Allocate a peer IP (simple: hash the peer name)
|
// Allocate a peer IP (simple: hash the peer name)
|
||||||
let peer_num = (name.bytes().map(|b| b as u32).sum::<u32>() % 253) + 2;
|
let peer_num = (name.bytes().map(|b| b as u32).sum::<u32>() % 253) + 2;
|
||||||
@ -667,15 +669,41 @@ impl RpcHandler {
|
|||||||
let content = tokio::fs::read_to_string(&peer_file)
|
let content = tokio::fs::read_to_string(&peer_file)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| anyhow::anyhow!("Peer '{}' not found", name))?;
|
.map_err(|_| anyhow::anyhow!("Peer '{}' not found", name))?;
|
||||||
let peer: serde_json::Value = serde_json::from_str(&content)?;
|
let mut peer: serde_json::Value = serde_json::from_str(&content)?;
|
||||||
|
|
||||||
let config = peer.get("config").and_then(|v| v.as_str()).ok_or_else(|| {
|
let stored = peer.get("config").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"No config stored for peer '{}' — recreate the device to get a new QR code",
|
"No config stored for peer '{}' — recreate the device to get a new QR code",
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// The stored Endpoint is the node's address at creation time; after
|
||||||
|
// the node moves networks it points at a dead IP and the QR produces
|
||||||
|
// a tunnel that can never connect. Refresh it to the current address.
|
||||||
|
let endpoint = format!("{}:51820", self.current_wg_endpoint_host().await);
|
||||||
|
let config: String = stored
|
||||||
|
.lines()
|
||||||
|
.map(|l| {
|
||||||
|
if l.trim_start().starts_with("Endpoint") {
|
||||||
|
format!("Endpoint = {}", endpoint)
|
||||||
|
} else {
|
||||||
|
l.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
if config != stored {
|
||||||
|
if let Some(obj) = peer.as_object_mut() {
|
||||||
|
obj.insert("config".to_string(), config.clone().into());
|
||||||
|
}
|
||||||
|
if let Ok(json) = serde_json::to_string_pretty(&peer) {
|
||||||
|
if tokio::fs::write(&peer_file, json).await.is_ok() {
|
||||||
|
info!("VPN peer '{}' endpoint refreshed to {}", name, endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let qr = qrcode::QrCode::new(config.as_bytes())
|
let qr = qrcode::QrCode::new(config.as_bytes())
|
||||||
.map_err(|e| anyhow::anyhow!("QR generation failed: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("QR generation failed: {}", e))?;
|
||||||
let svg = qr
|
let svg = qr
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user