Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support override-endpoint with the unspecified address #315

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use shared::{
RedeemContents, RenameCidrOpts, RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT,
};
use std::{
fmt, io,
io,
net::SocketAddr,
path::{Path, PathBuf},
thread,
Expand Down Expand Up @@ -281,22 +281,6 @@ enum Command {
},
}

/// Application-level error.
strohel marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, Clone)]
pub(crate) struct ClientError(String);

impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl std::error::Error for ClientError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}

fn update_hosts_file(
interface: &InterfaceName,
hosts_path: PathBuf,
Expand Down
38 changes: 26 additions & 12 deletions server/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
use shared::Peer;
use std::net::SocketAddr;

use crate::Session;
use shared::{Endpoint, Peer};

pub mod admin;
pub mod user;

/// Inject the collected endpoints from the WG interface into a list of peers.
/// This is essentially what adds NAT holepunching functionality. If a peer
/// already has an endpoint specified (by calling the override-endpoint) API,
/// the relatively recent wireguard endpoint will be added to the list of NAT
/// candidates, so other peers have a better chance of connecting.
/// Implements NAT traversal strategies.
/// (1) NAT holepunching: Report the most recent wireguard endpoint as the peer's
/// endpoint or add it to the list of NAT candidates if an override enpoint is
/// specified. Note that NAT traversal does not always work e.g. if the peer is
/// 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>) {
for peer in peers {
let endpoints = session.context.endpoints.read();
if let Some(wg_endpoint) = endpoints.get(&peer.public_key) {
if peer.contents.endpoint.is_none() {
peer.contents.endpoint = Some(wg_endpoint.to_owned().into());
let wg_endpoint_ip = wg_endpoint.ip();
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 {
// 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.to_owned().into());
// (1) NAT holepunching
peer.contents.endpoint = Some(wg_endpoint);
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions shared/src/prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::{
fmt::{Debug, Display},
fs::{File, OpenOptions},
io,
net::SocketAddr,
net::{IpAddr, Ipv6Addr, SocketAddr},
str::FromStr,
time::SystemTime,
};
Expand Down Expand Up @@ -138,7 +138,7 @@ pub fn rename_cidr(
};

let mut new_cidr = old_cidr;
new_cidr.contents.name = new_name.clone();
new_cidr.contents.name.clone_from(&new_name);

Ok(
if args.yes
Expand Down Expand Up @@ -565,6 +565,14 @@ pub fn ask_endpoint(listen_port: u16) -> Result<Endpoint, Error> {
.interact()?
{
publicip::get_any(Preference::Ipv4)
} else if Confirm::with_theme(&*THEME)
.wait_for_newline(true)
.with_prompt(
"Use an unspecified IP address? (this can occur if you do not have a fixed global IP)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Use an unspecified IP address? (this can occur if you do not have a fixed global IP)",
"Use an unspecified IP address and override just the port? (use if you do not have \
a fixed global IP)",

)
.interact()?
{
Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it does not really make a difference whether the unspecified IP address is IPv4 or IPv6? Still, we generally default to IPv4, so we may as well follow suit here.

} else {
None
};
Comment on lines 565 to 578
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub won't let me comment on the whole span, but should we first ask whether to use unspecified address, and only if not ask where to auto-detect the address? Otherwise the "use unspecified" prompt may be easily missed.

Expand Down
19 changes: 18 additions & 1 deletion shared/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -437,7 +450,11 @@ pub struct ListenPortOpts {

#[derive(Debug, Clone, PartialEq, Eq, Args)]
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
strohel marked this conversation as resolved.
Show resolved Hide resolved
/// server will try to resolve it based on its most recent connection.
/// The port will still be used even if you decide to use the unspecified
/// IP address.
#[clap(short, long)]
pub endpoint: Option<Endpoint>,
Comment on lines +453 to 459
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this I didn't even know what "unspecified IP address" exactly means. Shall we give an example like 0.0.0.0:1234?


Expand Down
Loading