From ec754e60c411ac6c53a30549e0b03fdab27f3197 Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Tue, 16 Nov 2021 18:46:45 +0900 Subject: [PATCH] client: non-interactive `set-listen-port` and `override-endpoint` closes #158 --- client/src/main.rs | 116 ++++++++++++++++++++++-------------------- shared/src/prompts.rs | 45 +++++++++------- shared/src/types.rs | 30 +++++++++++ 3 files changed, 116 insertions(+), 75 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index e459194..82011c5 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -9,9 +9,9 @@ use shared::{ prompts, wg::{DeviceExt, PeerInfoExt}, AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree, - DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext, NatOpts, - NetworkOpts, Peer, RedeemContents, RenamePeerOpts, State, WrappedIoError, - REDEEM_TRANSITION_WAIT, + DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext, + ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer, RedeemContents, + RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT, }; use std::{ fmt, io, @@ -50,7 +50,7 @@ struct Opts { #[structopt(subcommand)] command: Option, - /// Verbose output, use -vv for even higher verbositude. + /// Verbose output, use -vv for even higher verbositude #[structopt(short, long, parse(from_occurrences))] verbose: u64, @@ -66,11 +66,11 @@ struct Opts { #[derive(Clone, Debug, StructOpt)] struct HostsOpt { - /// The path to write hosts to. + /// The path to write hosts to #[structopt(long = "hosts-path", default_value = "/etc/hosts")] 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")] no_write_hosts: bool, } @@ -83,7 +83,7 @@ impl From for Option { #[derive(Clone, Debug, StructOpt)] enum Command { - /// Install a new innernet config. + /// Install a new innernet config #[structopt(alias = "redeem")] Install { /// Path to the invitation file @@ -99,7 +99,7 @@ enum Command { nat: NatOpts, }, - /// Enumerate all innernet connections. + /// Enumerate all innernet connections #[structopt(alias = "list")] Show { /// One-line peer list @@ -113,15 +113,15 @@ enum Command { interface: Option, }, - /// 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 { /// Enable daemon mode i.e. keep the process running, while fetching - /// the latest peer list periodically. + /// the latest peer list periodically #[structopt(short, long)] daemon: bool, /// 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")] interval: u64, @@ -134,7 +134,7 @@ enum Command { 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 { interface: Interface, @@ -151,7 +151,7 @@ enum Command { /// Bring down the interface (equivalent to 'wg-quick down ') 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 /// also specify all the options in the command, eg: @@ -164,7 +164,7 @@ enum Command { sub_opts: AddPeerOpts, }, - /// Rename a peer. + /// Rename a peer /// /// By default, you'll be prompted interactively to select a peer, but you can /// also specify all the options in the command, eg: @@ -177,7 +177,7 @@ enum Command { sub_opts: RenamePeerOpts, }, - /// Add a new CIDR. + /// Add a new CIDR AddCidr { interface: Interface, @@ -185,7 +185,7 @@ enum Command { sub_opts: AddCidrOpts, }, - /// Delete a CIDR. + /// Delete a CIDR DeleteCidr { interface: Interface, @@ -193,7 +193,7 @@ enum Command { sub_opts: DeleteCidrOpts, }, - /// List CIDRs. + /// List CIDRs ListCidrs { interface: Interface, @@ -202,13 +202,13 @@ enum Command { tree: bool, }, - /// Disable an enabled peer. + /// Disable an enabled peer DisablePeer { interface: Interface }, - /// Enable a disabled peer. + /// Enable a disabled peer EnablePeer { interface: Interface }, - /// Add an association between CIDRs. + /// Add an association between CIDRs AddAssociation { interface: Interface, @@ -216,28 +216,26 @@ enum Command { sub_opts: AddAssociationOpts, }, - /// Delete an association between CIDRs. + /// Delete an association between CIDRs DeleteAssociation { interface: Interface }, - /// List existing assocations between CIDRs. + /// List existing assocations between CIDRs ListAssociations { interface: Interface }, /// Set the local listen port. SetListenPort { interface: Interface, - /// Unset the local listen port to use a randomized port. - #[structopt(short, long)] - unset: bool, + #[structopt(flatten)] + sub_opts: ListenPortOpts, }, - /// Override your external endpoint that the server sends to other peers. + /// Override your external endpoint that the server sends to other peers OverrideEndpoint { interface: Interface, - /// Unset an existing override to use the automatic endpoint discovery. - #[structopt(short, long)] - unset: bool, + #[structopt(flatten)] + sub_opts: OverrideEndpointOpts, }, /// Generate shell completion scripts @@ -535,7 +533,7 @@ fn fetch( .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 api = Api::new(&config.server); let State { peers, cidrs } = api.http("GET", "/user/state")?; @@ -563,7 +561,7 @@ fn fetch( println!(); log::info!("updated interface {}\n", interface.as_str_lossy().yellow()); } 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(); @@ -890,11 +888,11 @@ fn list_associations(interface: &InterfaceName, opts: &Opts) -> Result<(), Error fn set_listen_port( interface: &InterfaceName, opts: &Opts, - unset: bool, + sub_opts: ListenPortOpts, ) -> Result, Error> { 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 { wg::set_listen_port(interface, listen_port, opts.network.backend)?; log::info!("the interface is updated"); @@ -909,33 +907,33 @@ fn set_listen_port( 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 endpoint_contents = if unset { - prompts::unset_override_endpoint()?.then(|| EndpointContents::Unset) + let port = match config.interface.listen_port { + 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 { - let listen_port = if let Some(listen_port) = config.interface.listen_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 - }; + let endpoint = prompts::override_endpoint(&sub_opts, port)?; endpoint.map(EndpointContents::Set) }; 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)?; + log::info!( + "endpoint override {}", + if sub_opts.unset { "unset" } else { "set" } + ); } else { - log::info!("exiting without overriding endpoint."); + log::info!("exiting without overriding endpoint"); } Ok(()) @@ -1193,11 +1191,17 @@ fn run(opts: &Opts) -> Result<(), Error> { } => add_association(&interface, opts, sub_opts)?, Command::DeleteAssociation { interface } => delete_association(&interface, opts)?, Command::ListAssociations { interface } => list_associations(&interface, opts)?, - Command::SetListenPort { interface, unset } => { - set_listen_port(&interface, opts, unset)?; + Command::SetListenPort { + interface, + sub_opts, + } => { + set_listen_port(&interface, opts, sub_opts)?; }, - Command::OverrideEndpoint { interface, unset } => { - override_endpoint(&interface, opts, unset)?; + Command::OverrideEndpoint { + interface, + sub_opts, + } => { + override_endpoint(&interface, opts, sub_opts)?; }, Command::Completions { shell } => { Opts::clap().gen_completions_to("innernet", shell, &mut std::io::stdout()); diff --git a/shared/src/prompts.rs b/shared/src/prompts.rs index 5ee98ca..334bd19 100644 --- a/shared/src/prompts.rs +++ b/shared/src/prompts.rs @@ -1,7 +1,8 @@ use crate::{ interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo}, 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 colored::*; @@ -426,16 +427,18 @@ pub fn write_peer_invitation( pub fn set_listen_port( interface: &InterfaceInfo, - unset: bool, + args: ListenPortOpts, ) -> Result>, Error> { - let listen_port = (!unset) - .then(|| { - input( - "Listen port", - Prefill::Default(interface.listen_port.unwrap_or(51820)), - ) - }) - .transpose()?; + let listen_port = if let Some(listen_port) = args.listen_port { + Some(listen_port) + } else if !args.unset { + Some(input( + "Listen port", + Prefill::Default(interface.listen_port.unwrap_or(51820)), + )?) + } else { + None + }; let mut confirmation = Confirm::with_theme(&*THEME); confirmation @@ -452,7 +455,7 @@ pub fn set_listen_port( if listen_port == interface.listen_port { println!("No change necessary - interface already has this setting."); Ok(None) - } else if confirmation.interact()? { + } else if args.yes || confirmation.interact()? { Ok(Some(listen_port)) } else { Ok(None) @@ -464,7 +467,7 @@ pub fn ask_endpoint(listen_port: u16) -> Result { let external_ip = if Confirm::with_theme(&*THEME) .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()? { publicip::get_any(Preference::Ipv4) @@ -481,17 +484,21 @@ pub fn ask_endpoint(listen_port: u16) -> Result { )?) } -pub fn override_endpoint(listen_port: u16) -> Result, Error> { - let endpoint = ask_endpoint(listen_port)?; - if confirm(&format!("Set external endpoint to {}?", endpoint))? { +pub fn override_endpoint( + args: &OverrideEndpointOpts, + listen_port: u16, +) -> Result, 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)) } else { Ok(None) } } -pub fn unset_override_endpoint() -> Result { - Ok(confirm( - "Unset external endpoint to enable automatic endpoint discovery?", - )?) +pub fn unset_override_endpoint(args: &OverrideEndpointOpts) -> Result { + Ok(args.yes || confirm("Unset external endpoint to enable automatic endpoint discovery?")?) } diff --git a/shared/src/types.rs b/shared/src/types.rs index 1a1fb35..c022b33 100644 --- a/shared/src/types.rs +++ b/shared/src/types.rs @@ -387,6 +387,36 @@ pub struct AddAssociationOpts { pub cidr2: Option, } +#[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, + + /// 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, + + /// 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)] pub struct NatOpts { #[structopt(long)]