client: non-interactive `set-listen-port` and `override-endpoint`

closes #158
pull/178/head
Jake McGinty 2021-11-16 18:46:45 +09:00
parent ae2c554b23
commit ec754e60c4
3 changed files with 116 additions and 75 deletions

View File

@ -9,9 +9,9 @@ use shared::{
prompts, prompts,
wg::{DeviceExt, PeerInfoExt}, wg::{DeviceExt, PeerInfoExt},
AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree, AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree,
DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext, NatOpts, DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext,
NetworkOpts, Peer, RedeemContents, RenamePeerOpts, State, WrappedIoError, ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer, RedeemContents,
REDEEM_TRANSITION_WAIT, RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT,
}; };
use std::{ use std::{
fmt, io, fmt, io,
@ -50,7 +50,7 @@ struct Opts {
#[structopt(subcommand)] #[structopt(subcommand)]
command: Option<Command>, command: Option<Command>,
/// Verbose output, use -vv for even higher verbositude. /// Verbose output, use -vv for even higher verbositude
#[structopt(short, long, parse(from_occurrences))] #[structopt(short, long, parse(from_occurrences))]
verbose: u64, verbose: u64,
@ -66,11 +66,11 @@ struct Opts {
#[derive(Clone, Debug, StructOpt)] #[derive(Clone, Debug, StructOpt)]
struct HostsOpt { struct HostsOpt {
/// The path to write hosts to. /// The path to write hosts to
#[structopt(long = "hosts-path", default_value = "/etc/hosts")] #[structopt(long = "hosts-path", default_value = "/etc/hosts")]
hosts_path: PathBuf, hosts_path: PathBuf,
/// Don't write to any hosts files. /// Don't write to any hosts files
#[structopt(long = "no-write-hosts", conflicts_with = "hosts-path")] #[structopt(long = "no-write-hosts", conflicts_with = "hosts-path")]
no_write_hosts: bool, no_write_hosts: bool,
} }
@ -83,7 +83,7 @@ impl From<HostsOpt> for Option<PathBuf> {
#[derive(Clone, Debug, StructOpt)] #[derive(Clone, Debug, StructOpt)]
enum Command { enum Command {
/// Install a new innernet config. /// Install a new innernet config
#[structopt(alias = "redeem")] #[structopt(alias = "redeem")]
Install { Install {
/// Path to the invitation file /// Path to the invitation file
@ -99,7 +99,7 @@ enum Command {
nat: NatOpts, nat: NatOpts,
}, },
/// Enumerate all innernet connections. /// Enumerate all innernet connections
#[structopt(alias = "list")] #[structopt(alias = "list")]
Show { Show {
/// One-line peer list /// One-line peer list
@ -113,15 +113,15 @@ enum Command {
interface: Option<Interface>, interface: Option<Interface>,
}, },
/// Bring up your local interface, and update it with latest peer list. /// Bring up your local interface, and update it with latest peer list
Up { Up {
/// Enable daemon mode i.e. keep the process running, while fetching /// Enable daemon mode i.e. keep the process running, while fetching
/// the latest peer list periodically. /// the latest peer list periodically
#[structopt(short, long)] #[structopt(short, long)]
daemon: bool, daemon: bool,
/// Keep fetching the latest peer list at the specified interval in /// Keep fetching the latest peer list at the specified interval in
/// seconds. Valid only in daemon mode. /// seconds. Valid only in daemon mode
#[structopt(long, default_value = "60")] #[structopt(long, default_value = "60")]
interval: u64, interval: u64,
@ -134,7 +134,7 @@ enum Command {
interface: Interface, interface: Interface,
}, },
/// Fetch and update your local interface with the latest peer list. /// Fetch and update your local interface with the latest peer list
Fetch { Fetch {
interface: Interface, interface: Interface,
@ -151,7 +151,7 @@ enum Command {
/// Bring down the interface (equivalent to 'wg-quick down <interface>') /// Bring down the interface (equivalent to 'wg-quick down <interface>')
Down { interface: Interface }, Down { interface: Interface },
/// Add a new peer. /// Add a new peer
/// ///
/// By default, you'll be prompted interactively to create a peer, but you can /// By default, you'll be prompted interactively to create a peer, but you can
/// also specify all the options in the command, eg: /// also specify all the options in the command, eg:
@ -164,7 +164,7 @@ enum Command {
sub_opts: AddPeerOpts, sub_opts: AddPeerOpts,
}, },
/// Rename a peer. /// Rename a peer
/// ///
/// By default, you'll be prompted interactively to select a peer, but you can /// By default, you'll be prompted interactively to select a peer, but you can
/// also specify all the options in the command, eg: /// also specify all the options in the command, eg:
@ -177,7 +177,7 @@ enum Command {
sub_opts: RenamePeerOpts, sub_opts: RenamePeerOpts,
}, },
/// Add a new CIDR. /// Add a new CIDR
AddCidr { AddCidr {
interface: Interface, interface: Interface,
@ -185,7 +185,7 @@ enum Command {
sub_opts: AddCidrOpts, sub_opts: AddCidrOpts,
}, },
/// Delete a CIDR. /// Delete a CIDR
DeleteCidr { DeleteCidr {
interface: Interface, interface: Interface,
@ -193,7 +193,7 @@ enum Command {
sub_opts: DeleteCidrOpts, sub_opts: DeleteCidrOpts,
}, },
/// List CIDRs. /// List CIDRs
ListCidrs { ListCidrs {
interface: Interface, interface: Interface,
@ -202,13 +202,13 @@ enum Command {
tree: bool, tree: bool,
}, },
/// Disable an enabled peer. /// Disable an enabled peer
DisablePeer { interface: Interface }, DisablePeer { interface: Interface },
/// Enable a disabled peer. /// Enable a disabled peer
EnablePeer { interface: Interface }, EnablePeer { interface: Interface },
/// Add an association between CIDRs. /// Add an association between CIDRs
AddAssociation { AddAssociation {
interface: Interface, interface: Interface,
@ -216,28 +216,26 @@ enum Command {
sub_opts: AddAssociationOpts, sub_opts: AddAssociationOpts,
}, },
/// Delete an association between CIDRs. /// Delete an association between CIDRs
DeleteAssociation { interface: Interface }, DeleteAssociation { interface: Interface },
/// List existing assocations between CIDRs. /// List existing assocations between CIDRs
ListAssociations { interface: Interface }, ListAssociations { interface: Interface },
/// Set the local listen port. /// Set the local listen port.
SetListenPort { SetListenPort {
interface: Interface, interface: Interface,
/// Unset the local listen port to use a randomized port. #[structopt(flatten)]
#[structopt(short, long)] sub_opts: ListenPortOpts,
unset: bool,
}, },
/// Override your external endpoint that the server sends to other peers. /// Override your external endpoint that the server sends to other peers
OverrideEndpoint { OverrideEndpoint {
interface: Interface, interface: Interface,
/// Unset an existing override to use the automatic endpoint discovery. #[structopt(flatten)]
#[structopt(short, long)] sub_opts: OverrideEndpointOpts,
unset: bool,
}, },
/// Generate shell completion scripts /// Generate shell completion scripts
@ -535,7 +533,7 @@ fn fetch(
.with_str(interface.to_string())?; .with_str(interface.to_string())?;
} }
log::info!("fetching state from server."); log::info!("fetching state from server...");
let mut store = DataStore::open_or_create(&opts.data_dir, interface)?; let mut store = DataStore::open_or_create(&opts.data_dir, interface)?;
let api = Api::new(&config.server); let api = Api::new(&config.server);
let State { peers, cidrs } = api.http("GET", "/user/state")?; let State { peers, cidrs } = api.http("GET", "/user/state")?;
@ -563,7 +561,7 @@ fn fetch(
println!(); println!();
log::info!("updated interface {}\n", interface.as_str_lossy().yellow()); log::info!("updated interface {}\n", interface.as_str_lossy().yellow());
} else { } else {
log::info!("{}", "peers are already up to date.".green()); log::info!("{}", "peers are already up to date".green());
} }
let interface_updated_time = Instant::now(); let interface_updated_time = Instant::now();
@ -890,11 +888,11 @@ fn list_associations(interface: &InterfaceName, opts: &Opts) -> Result<(), Error
fn set_listen_port( fn set_listen_port(
interface: &InterfaceName, interface: &InterfaceName,
opts: &Opts, opts: &Opts,
unset: bool, sub_opts: ListenPortOpts,
) -> Result<Option<u16>, Error> { ) -> Result<Option<u16>, Error> {
let mut config = InterfaceConfig::from_interface(&opts.config_dir, interface)?; let mut config = InterfaceConfig::from_interface(&opts.config_dir, interface)?;
let listen_port = prompts::set_listen_port(&config.interface, unset)?; let listen_port = prompts::set_listen_port(&config.interface, sub_opts)?;
if let Some(listen_port) = listen_port { if let Some(listen_port) = listen_port {
wg::set_listen_port(interface, listen_port, opts.network.backend)?; wg::set_listen_port(interface, listen_port, opts.network.backend)?;
log::info!("the interface is updated"); log::info!("the interface is updated");
@ -909,33 +907,33 @@ fn set_listen_port(
Ok(listen_port.flatten()) Ok(listen_port.flatten())
} }
fn override_endpoint(interface: &InterfaceName, opts: &Opts, unset: bool) -> Result<(), Error> { fn override_endpoint(
interface: &InterfaceName,
opts: &Opts,
sub_opts: OverrideEndpointOpts,
) -> Result<(), Error> {
let config = InterfaceConfig::from_interface(&opts.config_dir, interface)?; let config = InterfaceConfig::from_interface(&opts.config_dir, interface)?;
let endpoint_contents = if unset { let port = match config.interface.listen_port {
prompts::unset_override_endpoint()?.then(|| EndpointContents::Unset) Some(port) => port,
None => bail!("you need to set a listen port with set-listen-port before overriding the endpoint (otherwise port randomization on the interface would make it useless).")
};
let endpoint_contents = if sub_opts.unset {
prompts::unset_override_endpoint(&sub_opts)?.then(|| EndpointContents::Unset)
} else { } else {
let listen_port = if let Some(listen_port) = config.interface.listen_port { let endpoint = prompts::override_endpoint(&sub_opts, port)?;
Some(listen_port)
} else {
println!(
"{}: you need to set a listen port for your interface first.",
"note".bold().yellow()
);
set_listen_port(interface, opts, unset)?
};
let endpoint = if let Some(port) = listen_port {
prompts::override_endpoint(port)?
} else {
None
};
endpoint.map(EndpointContents::Set) endpoint.map(EndpointContents::Set)
}; };
if let Some(contents) = endpoint_contents { if let Some(contents) = endpoint_contents {
log::info!("Updating endpoint."); log::info!("requesting endpoint update...");
Api::new(&config.server).http_form("PUT", "/user/endpoint", contents)?; Api::new(&config.server).http_form("PUT", "/user/endpoint", contents)?;
log::info!(
"endpoint override {}",
if sub_opts.unset { "unset" } else { "set" }
);
} else { } else {
log::info!("exiting without overriding endpoint."); log::info!("exiting without overriding endpoint");
} }
Ok(()) Ok(())
@ -1193,11 +1191,17 @@ fn run(opts: &Opts) -> Result<(), Error> {
} => add_association(&interface, opts, sub_opts)?, } => add_association(&interface, opts, sub_opts)?,
Command::DeleteAssociation { interface } => delete_association(&interface, opts)?, Command::DeleteAssociation { interface } => delete_association(&interface, opts)?,
Command::ListAssociations { interface } => list_associations(&interface, opts)?, Command::ListAssociations { interface } => list_associations(&interface, opts)?,
Command::SetListenPort { interface, unset } => { Command::SetListenPort {
set_listen_port(&interface, opts, unset)?; interface,
sub_opts,
} => {
set_listen_port(&interface, opts, sub_opts)?;
}, },
Command::OverrideEndpoint { interface, unset } => { Command::OverrideEndpoint {
override_endpoint(&interface, opts, unset)?; interface,
sub_opts,
} => {
override_endpoint(&interface, opts, sub_opts)?;
}, },
Command::Completions { shell } => { Command::Completions { shell } => {
Opts::clap().gen_completions_to("innernet", shell, &mut std::io::stdout()); Opts::clap().gen_completions_to("innernet", shell, &mut std::io::stdout());

View File

@ -1,7 +1,8 @@
use crate::{ use crate::{
interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo}, interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo},
AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint, AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint,
Error, Hostname, Peer, PeerContents, RenamePeerOpts, PERSISTENT_KEEPALIVE_INTERVAL_SECS, Error, Hostname, ListenPortOpts, OverrideEndpointOpts, Peer, PeerContents, RenamePeerOpts,
PERSISTENT_KEEPALIVE_INTERVAL_SECS,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use colored::*; use colored::*;
@ -426,16 +427,18 @@ pub fn write_peer_invitation(
pub fn set_listen_port( pub fn set_listen_port(
interface: &InterfaceInfo, interface: &InterfaceInfo,
unset: bool, args: ListenPortOpts,
) -> Result<Option<Option<u16>>, Error> { ) -> Result<Option<Option<u16>>, Error> {
let listen_port = (!unset) let listen_port = if let Some(listen_port) = args.listen_port {
.then(|| { Some(listen_port)
input( } else if !args.unset {
"Listen port", Some(input(
Prefill::Default(interface.listen_port.unwrap_or(51820)), "Listen port",
) Prefill::Default(interface.listen_port.unwrap_or(51820)),
}) )?)
.transpose()?; } else {
None
};
let mut confirmation = Confirm::with_theme(&*THEME); let mut confirmation = Confirm::with_theme(&*THEME);
confirmation confirmation
@ -452,7 +455,7 @@ pub fn set_listen_port(
if listen_port == interface.listen_port { if listen_port == interface.listen_port {
println!("No change necessary - interface already has this setting."); println!("No change necessary - interface already has this setting.");
Ok(None) Ok(None)
} else if confirmation.interact()? { } else if args.yes || confirmation.interact()? {
Ok(Some(listen_port)) Ok(Some(listen_port))
} else { } else {
Ok(None) Ok(None)
@ -464,7 +467,7 @@ pub fn ask_endpoint(listen_port: u16) -> Result<Endpoint, Error> {
let external_ip = if Confirm::with_theme(&*THEME) let external_ip = if Confirm::with_theme(&*THEME)
.wait_for_newline(true) .wait_for_newline(true)
.with_prompt("Auto-fill public IP address (using a DNS query to 1.1.1.1)?") .with_prompt("Auto-fill public IP address (via a DNS query to 1.1.1.1)?")
.interact()? .interact()?
{ {
publicip::get_any(Preference::Ipv4) publicip::get_any(Preference::Ipv4)
@ -481,17 +484,21 @@ pub fn ask_endpoint(listen_port: u16) -> Result<Endpoint, Error> {
)?) )?)
} }
pub fn override_endpoint(listen_port: u16) -> Result<Option<Endpoint>, Error> { pub fn override_endpoint(
let endpoint = ask_endpoint(listen_port)?; args: &OverrideEndpointOpts,
if confirm(&format!("Set external endpoint to {}?", endpoint))? { listen_port: u16,
) -> Result<Option<Endpoint>, Error> {
let endpoint = match &args.endpoint {
Some(endpoint) => endpoint.clone(),
None => ask_endpoint(listen_port)?,
};
if args.yes || confirm(&format!("Set external endpoint to {}?", endpoint))? {
Ok(Some(endpoint)) Ok(Some(endpoint))
} else { } else {
Ok(None) Ok(None)
} }
} }
pub fn unset_override_endpoint() -> Result<bool, Error> { pub fn unset_override_endpoint(args: &OverrideEndpointOpts) -> Result<bool, Error> {
Ok(confirm( Ok(args.yes || confirm("Unset external endpoint to enable automatic endpoint discovery?")?)
"Unset external endpoint to enable automatic endpoint discovery?",
)?)
} }

View File

@ -387,6 +387,36 @@ pub struct AddAssociationOpts {
pub cidr2: Option<String>, pub cidr2: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, StructOpt)]
pub struct ListenPortOpts {
/// The listen port you'd like to set for the interface
#[structopt(short, long)]
pub listen_port: Option<u16>,
/// Unset the local listen port to use a randomized port
#[structopt(short, long, conflicts_with = "listen-port")]
pub unset: bool,
/// Bypass confirmation
#[structopt(long)]
pub yes: bool,
}
#[derive(Debug, Clone, PartialEq, StructOpt)]
pub struct OverrideEndpointOpts {
/// The listen port you'd like to set for the interface
#[structopt(short, long)]
pub endpoint: Option<Endpoint>,
/// Unset an existing override to use the automatic endpoint discovery
#[structopt(short, long, conflicts_with = "endpoint")]
pub unset: bool,
/// Bypass confirmation
#[structopt(long)]
pub yes: bool,
}
#[derive(Debug, Clone, StructOpt)] #[derive(Debug, Clone, StructOpt)]
pub struct NatOpts { pub struct NatOpts {
#[structopt(long)] #[structopt(long)]