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,
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<Command>,
/// 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<HostsOpt> for Option<PathBuf> {
#[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<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 {
/// 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 <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
/// 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<Option<u16>, 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());

View File

@ -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<Option<Option<u16>>, 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<Endpoint, Error> {
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<Endpoint, Error> {
)?)
}
pub fn override_endpoint(listen_port: u16) -> Result<Option<Endpoint>, 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<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))
} else {
Ok(None)
}
}
pub fn unset_override_endpoint() -> Result<bool, Error> {
Ok(confirm(
"Unset external endpoint to enable automatic endpoint discovery?",
)?)
pub fn unset_override_endpoint(args: &OverrideEndpointOpts) -> Result<bool, Error> {
Ok(args.yes || confirm("Unset external endpoint to enable automatic endpoint discovery?")?)
}

View File

@ -387,6 +387,36 @@ pub struct AddAssociationOpts {
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)]
pub struct NatOpts {
#[structopt(long)]