Support override-endpoint with the unspecified address

dynamic-global-ip-resolution
Ryo Kawaguchi 2024-06-13 16:20:58 +09:00
parent 85c8cc37ec
commit f91551a109
3 changed files with 49 additions and 14 deletions

View File

@ -1,26 +1,40 @@
use shared::Peer; use std::net::SocketAddr;
use crate::Session; use crate::Session;
use shared::{Endpoint, Peer};
pub mod admin; pub mod admin;
pub mod user; pub mod user;
/// Inject the collected endpoints from the WG interface into a list of peers. /// Implements NAT traversal strategies.
/// This is essentially what adds NAT holepunching functionality. If a peer /// (1) NAT holepunching: Report the most recent wireguard endpoint as the peer's
/// already has an endpoint specified (by calling the override-endpoint) API, /// endpoint or add it to the list of NAT candidates if an override enpoint is
/// the relatively recent wireguard endpoint will be added to the list of NAT /// specified. Note that NAT traversal does not always work e.g. if the peer is
/// candidates, so other peers have a better chance of connecting. /// behind double NAT or address/port restricted cone NAT.
/// (2) Unspecified endpoint IP: A peer may report an override endpoint with
/// an unspecified IP. It typically indicates the peer does not have a fixed
/// global IP, and it needs help from the innernet server to resolve it.
/// Override the endpoint IP with what's most recently reported by wireguard.
pub fn inject_endpoints(session: &Session, peers: &mut Vec<Peer>) { pub fn inject_endpoints(session: &Session, peers: &mut Vec<Peer>) {
for peer in peers { for peer in peers {
let endpoints = session.context.endpoints.read(); let endpoints = session.context.endpoints.read();
if let Some(wg_endpoint) = endpoints.get(&peer.public_key) { if let Some(wg_endpoint) = endpoints.get(&peer.public_key) {
if peer.contents.endpoint.is_none() { let wg_endpoint_ip = wg_endpoint.ip();
peer.contents.endpoint = Some(wg_endpoint.to_owned().into()); let wg_endpoint: Endpoint = wg_endpoint.to_owned().into();
if let Some(endpoint) = &mut peer.contents.endpoint {
if endpoint.is_host_unspecified() {
// (2) Unspecified endpoint host
*endpoint = SocketAddr::new(wg_endpoint_ip, endpoint.port()).into();
} else if *endpoint != wg_endpoint {
// (1) NAT holepunching
// The peer already has an endpoint specified, but it might be stale.
// If there is an endpoint reported from wireguard, we should add it
// to the list of candidates so others can try to connect using it.
peer.contents.candidates.push(wg_endpoint);
}
} else { } else {
// The peer already has an endpoint specified, but it might be stale. // (1) NAT holepunching
// If there is an endpoint reported from wireguard, we should add it peer.contents.endpoint = Some(wg_endpoint);
// to the list of candidates so others can try to connect using it.
peer.contents.candidates.push(wg_endpoint.to_owned().into());
} }
} }
} }

View File

@ -15,7 +15,7 @@ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
fs::{File, OpenOptions}, fs::{File, OpenOptions},
io, io,
net::SocketAddr, net::{IpAddr, Ipv6Addr, SocketAddr},
str::FromStr, str::FromStr,
time::SystemTime, time::SystemTime,
}; };
@ -565,6 +565,12 @@ pub fn ask_endpoint(listen_port: u16) -> Result<Endpoint, Error> {
.interact()? .interact()?
{ {
publicip::get_any(Preference::Ipv4) publicip::get_any(Preference::Ipv4)
} else if Confirm::with_theme(&*THEME)
.wait_for_newline(true)
.with_prompt("You do not have a fixed global IP (use the unspecified address)?")
.interact()?
{
Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED))
} else { } else {
None None
}; };

View File

@ -146,6 +146,19 @@ impl Endpoint {
) )
}) })
} }
/// Returns true if the endpoint host is unspecified e.g. 0.0.0.0
pub fn is_host_unspecified(&self) -> bool {
match self.host {
Host::Ipv4(ip) => ip.is_unspecified(),
Host::Ipv6(ip) => ip.is_unspecified(),
Host::Domain(_) => false,
}
}
pub fn port(&self) -> u16 {
self.port
}
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
@ -437,7 +450,9 @@ pub struct ListenPortOpts {
#[derive(Debug, Clone, PartialEq, Eq, Args)] #[derive(Debug, Clone, PartialEq, Eq, Args)]
pub struct OverrideEndpointOpts { pub struct OverrideEndpointOpts {
/// The listen port you'd like to set for the interface /// The external endpoint that you'd like the innernet server to broadcast
/// to other peers. The IP address may be unspecified, in which case the
/// server will try to resolve it based on its most recent connection.
#[clap(short, long)] #[clap(short, long)]
pub endpoint: Option<Endpoint>, pub endpoint: Option<Endpoint>,