server: non-interactive network creation

pull/54/head
Jake McGinty 2021-04-18 01:12:15 +09:00
parent b92ad65b17
commit c4e369ee54
7 changed files with 272 additions and 66 deletions

162
Cargo.lock generated
View File

@ -89,6 +89,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "bumpalo"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -666,6 +672,15 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
dependencies = [
"wasm-bindgen",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -686,9 +701,9 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.20.1" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d31059f22935e6c31830db5249ba2b7ecd54fd73a9909286f0a67aa55c2fbd" checksum = "2f6332d94daa84478d55a6aa9dbb3b305ed6500fb0cb9400cb9e1525d0e0e188"
dependencies = [ dependencies = [
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
@ -1027,10 +1042,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "rusqlite" name = "ring"
version = "0.24.2" version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112" checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rusqlite"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48381bf52627e7b0e02c4c0e4c0c88fc1cf2228a2eb7461d9499b1372399f1da"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"fallible-iterator", "fallible-iterator",
@ -1047,6 +1077,19 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -1065,6 +1108,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"ring",
"untrusted",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.125" version = "1.0.125"
@ -1137,6 +1190,7 @@ dependencies = [
"thiserror", "thiserror",
"tokio", "tokio",
"toml", "toml",
"ureq",
"warp", "warp",
"wgctrl", "wgctrl",
] ]
@ -1199,6 +1253,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
@ -1496,6 +1556,12 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "ureq" name = "ureq"
version = "2.1.0" version = "2.1.0"
@ -1506,9 +1572,12 @@ dependencies = [
"chunked_transfer", "chunked_transfer",
"log", "log",
"once_cell", "once_cell",
"rustls",
"serde", "serde",
"serde_json", "serde_json",
"url", "url",
"webpki",
"webpki-roots",
] ]
[[package]] [[package]]
@ -1583,6 +1652,89 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
[[package]]
name = "web-sys"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
dependencies = [
"webpki",
]
[[package]] [[package]]
name = "wgctrl" name = "wgctrl"
version = "1.1.0" version = "1.1.0"

View File

@ -52,7 +52,8 @@ enum Command {
/// Install a new innernet config. /// Install a new innernet config.
#[structopt(alias = "redeem")] #[structopt(alias = "redeem")]
Install { Install {
config: PathBuf, /// Path to the invitation file
invite: PathBuf,
#[structopt(flatten)] #[structopt(flatten)]
hosts: HostsOpt, hosts: HostsOpt,
@ -64,9 +65,11 @@ enum Command {
/// Enumerate all innernet connections. /// Enumerate all innernet connections.
#[structopt(alias = "list")] #[structopt(alias = "list")]
Show { Show {
/// One-line peer list
#[structopt(short, long)] #[structopt(short, long)]
short: bool, short: bool,
/// Display peers in a tree based on the CIDRs
#[structopt(short, long)] #[structopt(short, long)]
tree: bool, tree: bool,
@ -841,10 +844,10 @@ fn run(opt: Opt) -> Result<(), Error> {
match command { match command {
Command::Install { Command::Install {
config, invite,
hosts, hosts,
opts, opts,
} => install(&config, hosts.into(), opts)?, } => install(&invite, hosts.into(), opts)?,
Command::Show { Command::Show {
short, short,
tree, tree,

View File

@ -21,18 +21,19 @@ hyper = "0.14"
indoc = "1" indoc = "1"
ipnetwork = { git = "https://github.com/mcginty/ipnetwork" } # pending https://github.com/achanda/ipnetwork/pull/129 ipnetwork = { git = "https://github.com/mcginty/ipnetwork" } # pending https://github.com/achanda/ipnetwork/pull/129
libc = "0.2" libc = "0.2"
libsqlite3-sys = "0.20" libsqlite3-sys = "0.22"
log = "0.4" log = "0.4"
parking_lot = "0.11" parking_lot = "0.11"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
regex = { version = "1", default-features = false, features = ["std"] } regex = { version = "1", default-features = false, features = ["std"] }
rusqlite = "0.24" rusqlite = "0.25"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
shared = { path = "../shared" } shared = { path = "../shared" }
subtle = "2" subtle = "2"
structopt = "0.3" structopt = "0.3"
thiserror = "1" thiserror = "1"
ureq = "2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
toml = "0.5" toml = "0.5"
warp = { git = "https://github.com/tonarino/warp", default-features = false } # pending https://github.com/seanmonstar/warp/issues/830 warp = { git = "https://github.com/tonarino/warp", default-features = false } # pending https://github.com/seanmonstar/warp/issues/830

View File

@ -20,9 +20,32 @@ fn create_database<P: AsRef<Path>>(
Ok(conn) Ok(conn)
} }
#[derive(Debug, Default, Clone, PartialEq, StructOpt)]
pub struct InitializeOpts {
/// The network name (ex: evilcorp)
#[structopt(long)]
pub network_name: Option<String>,
/// The network CIDR (ex: 10.42.0.0/16)
#[structopt(long)]
pub network_cidr: Option<IpNetwork>,
/// This server's external endpoint (ex: 100.100.100.100:51820)
#[structopt(long, conflicts_with = "auto-external-endpoint")]
pub external_endpoint: Option<SocketAddr>,
/// Auto-resolve external endpoint
#[structopt(long = "auto-external-endpoint")]
pub auto_external_endpoint: bool,
/// Port to listen on (for the WireGuard interface)
#[structopt(long)]
pub listen_port: Option<u16>,
}
struct DbInitData { struct DbInitData {
root_cidr_name: String, network_name: String,
root_cidr: IpNetwork, network_cidr: IpNetwork,
server_cidr: IpNetwork, server_cidr: IpNetwork,
our_ip: IpAddr, our_ip: IpAddr,
public_key_base64: String, public_key_base64: String,
@ -35,8 +58,8 @@ fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(),
let root_cidr = DatabaseCidr::create( let root_cidr = DatabaseCidr::create(
&conn, &conn,
CidrContents { CidrContents {
name: db_init_data.root_cidr_name.clone(), name: db_init_data.network_name.clone(),
cidr: db_init_data.root_cidr, cidr: db_init_data.network_cidr,
parent: None, parent: None,
}, },
) )
@ -71,7 +94,7 @@ fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(),
Ok(()) Ok(())
} }
pub fn init_wizard(conf: &ServerConfig) -> Result<(), Error> { pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Error> {
let theme = ColorfulTheme::default(); let theme = ColorfulTheme::default();
shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(|_| { shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(|_| {
@ -81,42 +104,57 @@ pub fn init_wizard(conf: &ServerConfig) -> Result<(), Error> {
) )
})?; })?;
let (name, root_cidr) = conf.root_cidr.clone().unwrap_or_else(|| { let name: String = if let Some(name) = opts.network_name {
println!("Please specify a root CIDR, which will define the entire network."); name.clone()
let name: String = Input::with_theme(&theme) } else {
println!("Here you'll specify the network CIDR, which will encompass the entire network.");
Input::with_theme(&theme)
.with_prompt("Network name") .with_prompt("Network name")
.validate_with(hostname_validator) .validate_with(hostname_validator)
.interact() .interact()?
.map_err(|_| println!("failed to get name.")) };
.unwrap();
let root_cidr: IpNetwork = Input::with_theme(&theme) let root_cidr: IpNetwork = if let Some(cidr) = opts.network_cidr {
cidr.clone()
} else {
Input::with_theme(&theme)
.with_prompt("Network CIDR") .with_prompt("Network CIDR")
.with_initial_text("10.42.0.0/16") .with_initial_text("10.42.0.0/16")
.interact() .interact()?
.map_err(|_| println!("failed to get cidr.")) };
.unwrap();
(name, root_cidr)
});
// This probably won't error because of the `hostname_validator` regex. // This probably won't error because of the `hostname_validator` regex.
let name = name.parse()?; let name = name.parse()?;
let endpoint: SocketAddr = conf.endpoint.unwrap_or_else(|| { let endpoint: SocketAddr = if let Some(endpoint) = opts.external_endpoint {
prompts::ask_endpoint() endpoint.clone()
.map_err(|_| println!("failed to get endpoint.")) } else {
.unwrap() let external_ip: Option<IpAddr> = ureq::get("http://4.icanhazip.com")
}); .call()
.ok()
.map(|res| res.into_string().ok())
.flatten()
.map(|body| body.trim().to_string())
.and_then(|body| body.parse().ok());
let listen_port: u16 = conf.listen_port.unwrap_or_else(|| { if opts.auto_external_endpoint {
let ip = external_ip.ok_or("couldn't get external IP")?;
(ip, 51820).into()
} else {
prompts::ask_endpoint(external_ip)?
}
};
let listen_port: u16 = if let Some(listen_port) = opts.listen_port {
listen_port
} else {
Input::with_theme(&theme) Input::with_theme(&theme)
.with_prompt("Listen port") .with_prompt("Listen port")
.default(51820) .default(51820)
.interact() .interact()
.map_err(|_| println!("failed to get listen port.")) .map_err(|_| "failed to get listen port.")?
.unwrap() };
});
let our_ip = root_cidr let our_ip = root_cidr
.iter() .iter()
.find(|ip| root_cidr.is_assignable(*ip)) .find(|ip| root_cidr.is_assignable(*ip))
@ -134,8 +172,8 @@ pub fn init_wizard(conf: &ServerConfig) -> Result<(), Error> {
config.write_to_path(&config_path)?; config.write_to_path(&config_path)?;
let db_init_data = DbInitData { let db_init_data = DbInitData {
root_cidr_name: name.to_string(), network_name: name.to_string(),
root_cidr, network_cidr: root_cidr,
server_cidr, server_cidr,
our_ip, our_ip,
public_key_base64: our_keypair.public.to_base64(), public_key_base64: our_keypair.public.to_base64(),

View File

@ -37,6 +37,7 @@ pub use endpoints::Endpoints;
pub use error::ServerError; pub use error::ServerError;
use shared::{prompts, wg, CidrTree, Error, Interface, SERVER_CONFIG_DIR, SERVER_DATABASE_DIR}; use shared::{prompts, wg, CidrTree, Error, Interface, SERVER_CONFIG_DIR, SERVER_DATABASE_DIR};
pub use shared::{Association, AssociationContents}; pub use shared::{Association, AssociationContents};
use initialize::InitializeOpts;
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -51,7 +52,10 @@ struct Opt {
enum Command { enum Command {
/// Create a new network. /// Create a new network.
#[structopt(alias = "init")] #[structopt(alias = "init")]
New, New {
#[structopt(flatten)]
opts: InitializeOpts
},
/// Permanently uninstall a created network, rendering it unusable. Use with care. /// Permanently uninstall a created network, rendering it unusable. Use with care.
Uninstall { interface: Interface }, Uninstall { interface: Interface },
@ -153,10 +157,6 @@ impl ConfigFile {
pub struct ServerConfig { pub struct ServerConfig {
wg_manage_dir_override: Option<PathBuf>, wg_manage_dir_override: Option<PathBuf>,
wg_dir_override: Option<PathBuf>, wg_dir_override: Option<PathBuf>,
root_cidr: Option<(String, IpNetwork)>,
endpoint: Option<SocketAddr>,
listen_port: Option<u16>,
noninteractive: bool,
} }
impl ServerConfig { impl ServerConfig {
@ -204,8 +204,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let conf = ServerConfig::default(); let conf = ServerConfig::default();
match opt.command { match opt.command {
Command::New => { Command::New { opts } => {
if let Err(e) = initialize::init_wizard(&conf) { if let Err(e) = initialize::init_wizard(&conf, opts) {
println!("{}: {}.", "creation failed".red(), e); println!("{}: {}.", "creation failed".red(), e);
} }
}, },
@ -237,7 +237,7 @@ fn open_database_connection(
fn add_peer( fn add_peer(
interface: &InterfaceName, interface: &InterfaceName,
conf: &ServerConfig, conf: &ServerConfig,
args: AddPeerOpts, opts: AddPeerOpts,
) -> Result<(), Error> { ) -> Result<(), Error> {
let config = ConfigFile::from_file(conf.config_path(interface))?; let config = ConfigFile::from_file(conf.config_path(interface))?;
let conn = open_database_connection(interface, conf)?; let conn = open_database_connection(interface, conf)?;
@ -248,7 +248,7 @@ fn add_peer(
let cidrs = DatabaseCidr::list(&conn)?; let cidrs = DatabaseCidr::list(&conn)?;
let cidr_tree = CidrTree::new(&cidrs[..]); let cidr_tree = CidrTree::new(&cidrs[..]);
if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree, &args)? { if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree, &opts)? {
let peer = DatabasePeer::create(&conn, peer_request)?; let peer = DatabasePeer::create(&conn, peer_request)?;
if cfg!(not(test)) && DeviceInfo::get_by_name(interface).is_ok() { if cfg!(not(test)) && DeviceInfo::get_by_name(interface).is_ok() {
// Update the current WireGuard interface with the new peers. // Update the current WireGuard interface with the new peers.
@ -268,7 +268,7 @@ fn add_peer(
&cidr_tree, &cidr_tree,
keypair, keypair,
&SocketAddr::new(config.address, config.listen_port), &SocketAddr::new(config.address, config.listen_port),
&args.save_config, &opts.save_config,
)?; )?;
} else { } else {
println!("exited without creating peer."); println!("exited without creating peer.");
@ -280,11 +280,11 @@ fn add_peer(
fn add_cidr( fn add_cidr(
interface: &InterfaceName, interface: &InterfaceName,
conf: &ServerConfig, conf: &ServerConfig,
args: AddCidrOpts, opts: AddCidrOpts,
) -> Result<(), Error> { ) -> Result<(), Error> {
let conn = open_database_connection(interface, conf)?; let conn = open_database_connection(interface, conf)?;
let cidrs = DatabaseCidr::list(&conn)?; let cidrs = DatabaseCidr::list(&conn)?;
if let Some(cidr_request) = shared::prompts::add_cidr(&cidrs, &args)? { if let Some(cidr_request) = shared::prompts::add_cidr(&cidrs, &opts)? {
let cidr = DatabaseCidr::create(&conn, cidr_request)?; let cidr = DatabaseCidr::create(&conn, cidr_request)?;
printdoc!( printdoc!(
" "

View File

@ -2,7 +2,7 @@
use crate::{ use crate::{
db::{DatabaseCidr, DatabasePeer}, db::{DatabaseCidr, DatabasePeer},
endpoints::Endpoints, endpoints::Endpoints,
initialize::init_wizard, initialize::{init_wizard, InitializeOpts},
Context, ServerConfig, Context, ServerConfig,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
@ -64,12 +64,16 @@ impl Server {
let conf = ServerConfig { let conf = ServerConfig {
wg_manage_dir_override: Some(test_dir_path.to_path_buf()), wg_manage_dir_override: Some(test_dir_path.to_path_buf()),
wg_dir_override: Some(test_dir_path.to_path_buf()), wg_dir_override: Some(test_dir_path.to_path_buf()),
root_cidr: Some((interface.clone(), ROOT_CIDR.parse()?)),
endpoint: Some("155.155.155.155:54321".parse()?),
listen_port: Some(54321),
noninteractive: true,
}; };
init_wizard(&conf).map_err(|_| anyhow!("init_wizard failed"))?;
let opts = InitializeOpts {
network_name: Some(interface.clone()),
network_cidr: Some(ROOT_CIDR.parse()?),
external_endpoint: Some("155.155.155.155:54321".parse()?),
listen_port: Some(54321),
auto_external_endpoint: false,
};
init_wizard(&conf, opts).map_err(|_| anyhow!("init_wizard failed"))?;
let interface = interface.parse().unwrap(); let interface = interface.parse().unwrap();
// Add developer CIDR and user CIDR and some peers for testing. // Add developer CIDR and user CIDR and some peers for testing.

View File

@ -362,16 +362,20 @@ pub fn set_listen_port(
} }
} }
pub fn ask_endpoint() -> Result<SocketAddr, Error> { pub fn ask_endpoint(external_ip: Option<IpAddr>) -> Result<SocketAddr, Error> {
println!("getting external IP address."); println!("getting external IP address.");
let external_ip: Option<IpAddr> = ureq::get("http://4.icanhazip.com") let external_ip = if external_ip.is_some() {
.call() external_ip
.ok() } else {
.map(|res| res.into_string().ok()) ureq::get("http://4.icanhazip.com")
.flatten() .call()
.map(|body| body.trim().to_string()) .ok()
.and_then(|body| body.parse().ok()); .map(|res| res.into_string().ok())
.flatten()
.map(|body| body.trim().to_string())
.and_then(|body| body.parse().ok())
};
let mut endpoint_builder = Input::with_theme(&*THEME); let mut endpoint_builder = Input::with_theme(&*THEME);
if let Some(ip) = external_ip { if let Some(ip) = external_ip {
@ -386,7 +390,11 @@ pub fn ask_endpoint() -> Result<SocketAddr, Error> {
} }
pub fn override_endpoint(unset: bool) -> Result<Option<Option<SocketAddr>>, Error> { pub fn override_endpoint(unset: bool) -> Result<Option<Option<SocketAddr>>, Error> {
let endpoint = if !unset { Some(ask_endpoint()?) } else { None }; let endpoint = if !unset {
Some(ask_endpoint(None)?)
} else {
None
};
Ok( Ok(
if Confirm::with_theme(&*THEME) if Confirm::with_theme(&*THEME)