2021-06-10 13:57:47 +00:00
|
|
|
use anyhow::{anyhow, bail};
|
2021-03-29 17:22:14 +00:00
|
|
|
use colored::*;
|
2021-04-10 07:03:39 +00:00
|
|
|
use dialoguer::{Confirm, Input};
|
2021-03-29 17:22:14 +00:00
|
|
|
use hostsfile::HostsBuilder;
|
2021-05-20 03:46:12 +00:00
|
|
|
use indoc::eprintdoc;
|
2021-03-29 17:22:14 +00:00
|
|
|
use shared::{
|
2021-09-01 09:58:46 +00:00
|
|
|
interface_config::InterfaceConfig,
|
|
|
|
prompts,
|
|
|
|
wg::{DeviceExt, PeerInfoExt},
|
|
|
|
AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree,
|
|
|
|
DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext, NetworkOpt,
|
|
|
|
Peer, RedeemContents, RenamePeerOpts, State, WrappedIoError, CLIENT_CONFIG_DIR,
|
|
|
|
REDEEM_TRANSITION_WAIT,
|
2021-03-29 17:22:14 +00:00
|
|
|
};
|
|
|
|
use std::{
|
2021-06-10 13:57:47 +00:00
|
|
|
fmt, io,
|
2021-09-01 09:58:46 +00:00
|
|
|
net::SocketAddr,
|
2021-03-29 17:22:14 +00:00
|
|
|
path::{Path, PathBuf},
|
|
|
|
thread,
|
2021-08-05 00:38:14 +00:00
|
|
|
time::Duration,
|
2021-03-29 17:22:14 +00:00
|
|
|
};
|
2021-05-26 05:23:02 +00:00
|
|
|
use structopt::{clap::AppSettings, StructOpt};
|
2021-05-19 07:54:07 +00:00
|
|
|
use wgctrl::{Device, DeviceUpdate, InterfaceName, PeerConfigBuilder, PeerInfo};
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
mod data_store;
|
2021-09-01 09:58:46 +00:00
|
|
|
mod nat;
|
2021-03-29 17:22:14 +00:00
|
|
|
mod util;
|
|
|
|
|
|
|
|
use data_store::DataStore;
|
2021-09-01 09:58:46 +00:00
|
|
|
use nat::NatTraverse;
|
2021-03-29 17:22:14 +00:00
|
|
|
use shared::{wg, Error};
|
2021-04-09 04:48:00 +00:00
|
|
|
use util::{human_duration, human_size, Api};
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-09 15:03:00 +00:00
|
|
|
struct PeerState<'a> {
|
|
|
|
peer: &'a Peer,
|
|
|
|
info: Option<&'a PeerInfo>,
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! println_pad {
|
|
|
|
($pad:expr, $($arg:tt)*) => {
|
|
|
|
print!("{:pad$}", "", pad = $pad);
|
|
|
|
println!($($arg)*);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
#[derive(Debug, StructOpt)]
|
2021-05-26 05:23:02 +00:00
|
|
|
#[structopt(name = "innernet", about, global_settings(&[AppSettings::ColoredHelp, AppSettings::DeriveDisplayOrder, AppSettings::VersionlessSubcommands, AppSettings::UnifiedHelpMessage]))]
|
2021-04-30 10:02:03 +00:00
|
|
|
struct Opts {
|
2021-03-29 17:22:14 +00:00
|
|
|
#[structopt(subcommand)]
|
|
|
|
command: Option<Command>,
|
2021-05-19 07:54:07 +00:00
|
|
|
|
2021-05-26 05:23:02 +00:00
|
|
|
/// Verbose output, use -vv for even higher verbositude.
|
|
|
|
#[structopt(short, long, parse(from_occurrences))]
|
|
|
|
verbose: u64,
|
2021-05-19 18:11:51 +00:00
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
#[structopt(flatten)]
|
|
|
|
network: NetworkOpt,
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
2021-04-07 08:00:52 +00:00
|
|
|
#[derive(Debug, StructOpt)]
|
|
|
|
struct HostsOpt {
|
|
|
|
/// The path to write hosts to.
|
2021-04-11 15:34:56 +00:00
|
|
|
#[structopt(long = "hosts-path", default_value = "/etc/hosts")]
|
2021-04-07 08:00:52 +00:00
|
|
|
hosts_path: PathBuf,
|
|
|
|
|
2021-04-11 15:34:56 +00:00
|
|
|
/// Don't write to any hosts files.
|
|
|
|
#[structopt(long = "no-write-hosts", conflicts_with = "hosts-path")]
|
2021-04-07 08:00:52 +00:00
|
|
|
no_write_hosts: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<HostsOpt> for Option<PathBuf> {
|
|
|
|
fn from(opt: HostsOpt) -> Self {
|
|
|
|
(!opt.no_write_hosts).then(|| opt.hosts_path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
#[derive(Debug, StructOpt)]
|
|
|
|
enum Command {
|
|
|
|
/// Install a new innernet config.
|
|
|
|
#[structopt(alias = "redeem")]
|
2021-04-07 08:00:52 +00:00
|
|
|
Install {
|
2021-04-17 16:12:15 +00:00
|
|
|
/// Path to the invitation file
|
|
|
|
invite: PathBuf,
|
2021-04-07 08:00:52 +00:00
|
|
|
|
|
|
|
#[structopt(flatten)]
|
|
|
|
hosts: HostsOpt,
|
2021-04-17 03:18:50 +00:00
|
|
|
|
|
|
|
#[structopt(flatten)]
|
|
|
|
opts: InstallOpts,
|
2021-04-07 08:00:52 +00:00
|
|
|
},
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
/// Enumerate all innernet connections.
|
|
|
|
#[structopt(alias = "list")]
|
|
|
|
Show {
|
2021-04-17 16:12:15 +00:00
|
|
|
/// One-line peer list
|
2021-03-29 17:22:14 +00:00
|
|
|
#[structopt(short, long)]
|
|
|
|
short: bool,
|
|
|
|
|
2021-04-17 16:12:15 +00:00
|
|
|
/// Display peers in a tree based on the CIDRs
|
2021-03-29 17:22:14 +00:00
|
|
|
#[structopt(short, long)]
|
|
|
|
tree: bool,
|
|
|
|
|
|
|
|
interface: Option<Interface>,
|
|
|
|
},
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
#[structopt(short, long)]
|
|
|
|
daemon: bool,
|
|
|
|
|
|
|
|
/// Keep fetching the latest peer list at the specified interval in
|
|
|
|
/// seconds. Valid only in daemon mode.
|
|
|
|
#[structopt(long, default_value = "60")]
|
|
|
|
interval: u64,
|
|
|
|
|
2021-04-07 08:00:52 +00:00
|
|
|
#[structopt(flatten)]
|
|
|
|
hosts: HostsOpt,
|
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
interface: Interface,
|
|
|
|
},
|
|
|
|
|
|
|
|
/// Fetch and update your local interface with the latest peer list.
|
2021-04-07 08:00:52 +00:00
|
|
|
Fetch {
|
|
|
|
interface: Interface,
|
|
|
|
|
|
|
|
#[structopt(flatten)]
|
|
|
|
hosts: HostsOpt,
|
|
|
|
},
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-04-09 13:37:33 +00:00
|
|
|
/// Uninstall an innernet network.
|
|
|
|
Uninstall { interface: Interface },
|
|
|
|
|
2021-05-11 17:31:47 +00:00
|
|
|
/// Bring down the interface (equivalent to "wg-quick down <interface>")
|
2021-03-29 17:22:14 +00:00
|
|
|
Down { interface: Interface },
|
|
|
|
|
|
|
|
/// Add a new peer.
|
2021-04-19 12:56:18 +00:00
|
|
|
///
|
|
|
|
/// By default, you'll be prompted interactively to create a peer, but you can
|
|
|
|
/// also specify all the options in the command, eg:
|
|
|
|
///
|
|
|
|
/// --name "person" --cidr "humans" --admin false --auto-ip --save-config "person.toml" --yes
|
2021-04-14 15:25:31 +00:00
|
|
|
AddPeer {
|
|
|
|
interface: Interface,
|
|
|
|
|
|
|
|
#[structopt(flatten)]
|
2021-04-17 03:18:50 +00:00
|
|
|
opts: AddPeerOpts,
|
2021-04-14 15:25:31 +00:00
|
|
|
},
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-25 10:58:00 +00:00
|
|
|
/// 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:
|
|
|
|
///
|
|
|
|
/// --name "person" --new-name "human"
|
|
|
|
RenamePeer {
|
|
|
|
interface: Interface,
|
|
|
|
|
|
|
|
#[structopt(flatten)]
|
|
|
|
opts: RenamePeerOpts,
|
|
|
|
},
|
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
/// Add a new CIDR.
|
2021-04-14 15:25:31 +00:00
|
|
|
AddCidr {
|
|
|
|
interface: Interface,
|
|
|
|
|
|
|
|
#[structopt(flatten)]
|
2021-04-17 03:18:50 +00:00
|
|
|
opts: AddCidrOpts,
|
2021-04-14 15:25:31 +00:00
|
|
|
},
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-21 03:39:33 +00:00
|
|
|
/// Delete a CIDR.
|
|
|
|
DeleteCidr {
|
|
|
|
interface: Interface,
|
|
|
|
|
|
|
|
#[structopt(flatten)]
|
|
|
|
opts: DeleteCidrOpts,
|
|
|
|
},
|
|
|
|
|
2021-06-14 09:50:21 +00:00
|
|
|
/// List CIDRs.
|
|
|
|
ListCidrs {
|
|
|
|
interface: Interface,
|
|
|
|
|
|
|
|
/// Display CIDRs in tree format
|
|
|
|
#[structopt(short, long)]
|
|
|
|
tree: bool,
|
|
|
|
},
|
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
/// Disable an enabled peer.
|
|
|
|
DisablePeer { interface: Interface },
|
|
|
|
|
|
|
|
/// Enable a disabled peer.
|
|
|
|
EnablePeer { interface: Interface },
|
|
|
|
|
|
|
|
/// Add an association between CIDRs.
|
2021-04-19 12:56:18 +00:00
|
|
|
AddAssociation {
|
|
|
|
interface: Interface,
|
|
|
|
|
|
|
|
#[structopt(flatten)]
|
|
|
|
opts: AddAssociationOpts,
|
|
|
|
},
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
/// Delete an association between CIDRs.
|
|
|
|
DeleteAssociation { interface: Interface },
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
},
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
},
|
2021-05-25 07:10:16 +00:00
|
|
|
|
|
|
|
/// Generate shell completion scripts
|
|
|
|
Completions {
|
|
|
|
#[structopt(possible_values = &structopt::clap::Shell::variants(), case_insensitive = true)]
|
|
|
|
shell: structopt::clap::Shell,
|
|
|
|
},
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Application-level error.
|
|
|
|
#[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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 01:28:37 +00:00
|
|
|
fn update_hosts_file(
|
|
|
|
interface: &InterfaceName,
|
|
|
|
hosts_path: PathBuf,
|
2021-05-06 03:40:00 +00:00
|
|
|
peers: &[Peer],
|
2021-06-16 11:22:28 +00:00
|
|
|
) -> Result<(), WrappedIoError> {
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!("updating {} with the latest peers.", "/etc/hosts".yellow());
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
let mut hosts_builder = HostsBuilder::new(format!("innernet {}", interface));
|
|
|
|
for peer in peers {
|
|
|
|
hosts_builder.add_hostname(
|
|
|
|
peer.contents.ip,
|
|
|
|
&format!("{}.{}.wg", peer.contents.name, interface),
|
|
|
|
);
|
|
|
|
}
|
2021-06-10 13:57:47 +00:00
|
|
|
if let Err(e) = hosts_builder.write_to(&hosts_path).with_path(hosts_path) {
|
|
|
|
log::warn!("failed to update hosts ({})", e);
|
|
|
|
}
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-11 17:31:47 +00:00
|
|
|
fn install(
|
|
|
|
invite: &Path,
|
|
|
|
hosts_file: Option<PathBuf>,
|
|
|
|
opts: InstallOpts,
|
2021-05-19 07:54:07 +00:00
|
|
|
network: NetworkOpt,
|
2021-05-11 17:31:47 +00:00
|
|
|
) -> Result<(), Error> {
|
2021-05-19 07:54:07 +00:00
|
|
|
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_DIR])?;
|
2021-04-10 08:38:59 +00:00
|
|
|
let config = InterfaceConfig::from_file(invite)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-04-17 03:18:50 +00:00
|
|
|
let iface = if opts.default_name {
|
|
|
|
config.interface.network_name.clone()
|
|
|
|
} else if let Some(ref iface) = opts.name {
|
|
|
|
iface.clone()
|
|
|
|
} else {
|
|
|
|
Input::with_theme(&*prompts::THEME)
|
|
|
|
.with_prompt("Interface name")
|
|
|
|
.default(config.interface.network_name.clone())
|
|
|
|
.interact()?
|
|
|
|
};
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
let target_conf = CLIENT_CONFIG_DIR.join(&iface).with_extension("conf");
|
2021-03-29 17:22:14 +00:00
|
|
|
if target_conf.exists() {
|
2021-08-02 14:10:20 +00:00
|
|
|
bail!(
|
|
|
|
"An existing innernet network with the name \"{}\" already exists.",
|
|
|
|
iface
|
|
|
|
);
|
2021-08-02 14:06:13 +00:00
|
|
|
}
|
2021-08-02 15:44:06 +00:00
|
|
|
let iface = iface.parse()?;
|
|
|
|
if Device::list(network.backend)
|
2021-08-02 14:10:20 +00:00
|
|
|
.iter()
|
2021-08-02 15:44:06 +00:00
|
|
|
.flatten()
|
|
|
|
.any(|name| name == &iface)
|
2021-08-02 14:10:20 +00:00
|
|
|
{
|
|
|
|
bail!(
|
|
|
|
"An existing WireGuard interface with the name \"{}\" already exists.",
|
|
|
|
iface
|
|
|
|
);
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
redeem_invite(&iface, config, target_conf, network).map_err(|e| {
|
2021-05-20 03:46:12 +00:00
|
|
|
log::error!("failed to start the interface: {}.", e);
|
|
|
|
log::info!("bringing down the interface.");
|
2021-05-19 07:54:07 +00:00
|
|
|
if let Err(e) = wg::down(&iface, network.backend) {
|
2021-05-20 03:46:12 +00:00
|
|
|
log::warn!("failed to bring down interface: {}.", e.to_string());
|
2021-04-10 08:38:59 +00:00
|
|
|
};
|
2021-05-20 03:46:12 +00:00
|
|
|
log::error!("Failed to redeem invite. Now's a good time to make sure the server is started and accessible!");
|
2021-04-10 08:38:59 +00:00
|
|
|
e
|
|
|
|
})?;
|
2021-04-09 01:28:37 +00:00
|
|
|
|
2021-04-28 05:59:59 +00:00
|
|
|
let mut fetch_success = false;
|
|
|
|
for _ in 0..3 {
|
2021-05-19 18:16:19 +00:00
|
|
|
if fetch(&iface, true, hosts_file.clone(), network).is_ok() {
|
2021-04-28 05:59:59 +00:00
|
|
|
fetch_success = true;
|
|
|
|
break;
|
|
|
|
}
|
2021-05-19 18:16:19 +00:00
|
|
|
thread::sleep(Duration::from_secs(1));
|
2021-04-28 05:59:59 +00:00
|
|
|
}
|
|
|
|
if !fetch_success {
|
2021-05-20 03:46:12 +00:00
|
|
|
log::warn!(
|
|
|
|
"Failed to fetch peers from server, you will need to manually run the 'up' command.",
|
2021-04-28 06:16:03 +00:00
|
|
|
);
|
2021-04-28 05:59:59 +00:00
|
|
|
}
|
2021-04-10 08:38:59 +00:00
|
|
|
|
2021-04-17 03:18:50 +00:00
|
|
|
if opts.delete_invite
|
|
|
|
|| Confirm::with_theme(&*prompts::THEME)
|
2021-05-21 04:02:11 +00:00
|
|
|
.wait_for_newline(true)
|
2021-04-17 03:18:50 +00:00
|
|
|
.with_prompt(&format!(
|
|
|
|
"Delete invitation file \"{}\" now? (It's no longer needed)",
|
|
|
|
invite.to_string_lossy().yellow()
|
|
|
|
))
|
|
|
|
.default(true)
|
|
|
|
.interact()?
|
2021-04-10 08:38:59 +00:00
|
|
|
{
|
|
|
|
std::fs::remove_file(invite).with_path(invite)?;
|
|
|
|
}
|
|
|
|
|
2021-05-20 03:46:12 +00:00
|
|
|
eprintdoc!(
|
2021-04-10 08:38:59 +00:00
|
|
|
"
|
|
|
|
{star} Done!
|
|
|
|
|
|
|
|
{interface} has been {installed}.
|
|
|
|
|
|
|
|
By default, innernet will write to your /etc/hosts file for peer name
|
|
|
|
resolution. To disable this behavior, use the --no-write-hosts or --write-hosts [PATH]
|
|
|
|
options.
|
|
|
|
|
|
|
|
See the manpage or innernet GitHub repo for more detailed instruction on managing your
|
|
|
|
interface and network. Have fun!
|
|
|
|
|
|
|
|
",
|
|
|
|
star = "[*]".dimmed(),
|
|
|
|
interface = iface.to_string().yellow(),
|
|
|
|
installed = "installed".green(),
|
|
|
|
);
|
2021-09-05 07:37:58 +00:00
|
|
|
if cfg!(target_os = "linux") {
|
|
|
|
eprintdoc!(
|
|
|
|
"
|
|
|
|
It's recommended to now keep the interface automatically refreshing via systemd:
|
|
|
|
|
|
|
|
{systemctl_enable}{interface}
|
|
|
|
",
|
|
|
|
interface = iface.to_string().yellow(),
|
|
|
|
systemctl_enable = "systemctl enable --now innernet@".yellow(),
|
|
|
|
);
|
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
eprintdoc!("
|
|
|
|
It's recommended to now keep the interface automatically refreshing, which you can
|
|
|
|
do via a launchd script (easier macOS helpers to be added to innernet in a later version).
|
|
|
|
|
|
|
|
Ex. to run innernet in a 60s update loop:
|
|
|
|
|
|
|
|
{daemon_mode} {interface}
|
|
|
|
",
|
|
|
|
interface = iface.to_string().yellow(),
|
|
|
|
daemon_mode = "innernet up -d --interval 60".yellow());
|
|
|
|
} else {
|
|
|
|
eprintdoc!("
|
|
|
|
It's recommended to now keep the interface automatically refreshing via whatever service
|
|
|
|
system your distribution provides.
|
|
|
|
|
|
|
|
Ex. to run innernet in a 60s update loop:
|
|
|
|
|
|
|
|
{daemon_mode} {interface}
|
|
|
|
",
|
|
|
|
interface = iface.to_string().yellow(),
|
|
|
|
daemon_mode = "innernet up -d --interval 60".yellow());
|
|
|
|
}
|
2021-04-10 08:38:59 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn redeem_invite(
|
|
|
|
iface: &InterfaceName,
|
|
|
|
mut config: InterfaceConfig,
|
|
|
|
target_conf: PathBuf,
|
2021-05-19 07:54:07 +00:00
|
|
|
network: NetworkOpt,
|
2021-04-10 08:38:59 +00:00
|
|
|
) -> Result<(), Error> {
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!("bringing up the interface.");
|
2021-06-16 11:22:28 +00:00
|
|
|
let resolved_endpoint = config
|
|
|
|
.server
|
|
|
|
.external_endpoint
|
|
|
|
.resolve()
|
|
|
|
.with_str(config.server.external_endpoint.to_string())?;
|
2021-03-29 17:22:14 +00:00
|
|
|
wg::up(
|
2021-06-22 02:27:29 +00:00
|
|
|
iface,
|
2021-03-29 17:22:14 +00:00
|
|
|
&config.interface.private_key,
|
|
|
|
config.interface.address,
|
|
|
|
None,
|
|
|
|
Some((
|
|
|
|
&config.server.public_key,
|
|
|
|
config.server.internal_endpoint.ip(),
|
2021-04-20 15:35:10 +00:00
|
|
|
resolved_endpoint,
|
2021-03-29 17:22:14 +00:00
|
|
|
)),
|
2021-05-19 07:54:07 +00:00
|
|
|
network,
|
2021-06-16 11:22:28 +00:00
|
|
|
)
|
|
|
|
.with_str(iface.to_string())?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!("Generating new keypair.");
|
2021-03-29 17:22:14 +00:00
|
|
|
let keypair = wgctrl::KeyPair::generate();
|
|
|
|
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!(
|
|
|
|
"Registering keypair with server (at {}).",
|
2021-03-29 17:22:14 +00:00
|
|
|
&config.server.internal_endpoint
|
|
|
|
);
|
2021-04-09 04:48:00 +00:00
|
|
|
Api::new(&config.server).http_form(
|
|
|
|
"POST",
|
2021-03-29 17:22:14 +00:00
|
|
|
"/user/redeem",
|
|
|
|
RedeemContents {
|
|
|
|
public_key: keypair.public.to_base64(),
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
|
|
|
|
config.interface.private_key = keypair.private.to_base64();
|
|
|
|
config.write_to_path(&target_conf, false, Some(0o600))?;
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!(
|
|
|
|
"New keypair registered. Copied config to {}.\n",
|
2021-03-29 17:22:14 +00:00
|
|
|
target_conf.to_string_lossy().yellow()
|
|
|
|
);
|
2021-04-28 05:59:59 +00:00
|
|
|
|
2021-05-21 04:35:52 +00:00
|
|
|
log::info!("Changing keys and waiting for server's WireGuard interface to transition.",);
|
2021-05-19 07:54:07 +00:00
|
|
|
DeviceUpdate::new()
|
2021-03-29 17:22:14 +00:00
|
|
|
.set_private_key(keypair.private)
|
2021-06-22 02:27:29 +00:00
|
|
|
.apply(iface, network.backend)
|
2021-06-16 11:22:28 +00:00
|
|
|
.with_str(iface.to_string())?;
|
2021-04-28 05:59:59 +00:00
|
|
|
thread::sleep(*REDEEM_TRANSITION_WAIT);
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-07 08:00:52 +00:00
|
|
|
fn up(
|
2021-04-09 01:28:37 +00:00
|
|
|
interface: &InterfaceName,
|
2021-04-07 08:00:52 +00:00
|
|
|
loop_interval: Option<Duration>,
|
|
|
|
hosts_path: Option<PathBuf>,
|
2021-05-19 07:54:07 +00:00
|
|
|
routing: NetworkOpt,
|
2021-04-07 08:00:52 +00:00
|
|
|
) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
loop {
|
2021-05-11 17:31:47 +00:00
|
|
|
fetch(interface, true, hosts_path.clone(), routing)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
match loop_interval {
|
|
|
|
Some(interval) => thread::sleep(interval),
|
|
|
|
None => break,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-07 08:00:52 +00:00
|
|
|
fn fetch(
|
2021-04-09 01:28:37 +00:00
|
|
|
interface: &InterfaceName,
|
2021-04-07 08:00:52 +00:00
|
|
|
bring_up_interface: bool,
|
|
|
|
hosts_path: Option<PathBuf>,
|
2021-05-19 07:54:07 +00:00
|
|
|
network: NetworkOpt,
|
2021-04-07 08:00:52 +00:00
|
|
|
) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let config = InterfaceConfig::from_interface(interface)?;
|
2021-08-05 00:38:14 +00:00
|
|
|
let interface_up = match Device::list(network.backend) {
|
|
|
|
Ok(interfaces) => interfaces.iter().any(|name| name == interface),
|
|
|
|
_ => false,
|
2021-03-29 17:22:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if !interface_up {
|
|
|
|
if !bring_up_interface {
|
2021-06-10 13:57:47 +00:00
|
|
|
bail!(
|
2021-03-29 17:22:14 +00:00
|
|
|
"Interface is not up. Use 'innernet up {}' instead",
|
|
|
|
interface
|
2021-06-10 13:57:47 +00:00
|
|
|
);
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("bringing up the interface.");
|
2021-06-16 11:22:28 +00:00
|
|
|
let resolved_endpoint = config
|
|
|
|
.server
|
|
|
|
.external_endpoint
|
|
|
|
.resolve()
|
|
|
|
.with_str(config.server.external_endpoint.to_string())?;
|
2021-03-29 17:22:14 +00:00
|
|
|
wg::up(
|
|
|
|
interface,
|
|
|
|
&config.interface.private_key,
|
|
|
|
config.interface.address,
|
|
|
|
config.interface.listen_port,
|
|
|
|
Some((
|
|
|
|
&config.server.public_key,
|
|
|
|
config.server.internal_endpoint.ip(),
|
2021-04-20 15:35:10 +00:00
|
|
|
resolved_endpoint,
|
2021-03-29 17:22:14 +00:00
|
|
|
)),
|
2021-05-19 07:54:07 +00:00
|
|
|
network,
|
2021-06-16 11:22:28 +00:00
|
|
|
)
|
|
|
|
.with_str(interface.to_string())?;
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("fetching state from server.");
|
2021-06-22 02:27:29 +00:00
|
|
|
let mut store = DataStore::open_or_create(interface)?;
|
2021-04-09 04:48:00 +00:00
|
|
|
let State { peers, cidrs } = Api::new(&config.server).http("GET", "/user/state")?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-09-01 09:58:46 +00:00
|
|
|
let device = Device::get(interface, network.backend)?;
|
|
|
|
let modifications = device.diff(&peers);
|
2021-08-05 00:38:14 +00:00
|
|
|
|
|
|
|
let updates = modifications
|
2021-09-01 09:58:46 +00:00
|
|
|
.iter()
|
|
|
|
.inspect(|diff| util::print_peer_diff(&store, diff))
|
|
|
|
.cloned()
|
2021-08-05 00:38:14 +00:00
|
|
|
.map(PeerConfigBuilder::from)
|
|
|
|
.collect::<Vec<_>>();
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-08-05 00:38:14 +00:00
|
|
|
if !updates.is_empty() {
|
|
|
|
DeviceUpdate::new()
|
|
|
|
.add_peers(&updates)
|
2021-06-22 02:27:29 +00:00
|
|
|
.apply(interface, network.backend)
|
2021-06-16 11:22:28 +00:00
|
|
|
.with_str(interface.to_string())?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-04-07 08:00:52 +00:00
|
|
|
if let Some(path) = hosts_path {
|
|
|
|
update_hosts_file(interface, path, &peers)?;
|
|
|
|
}
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
println!();
|
2021-05-19 18:18:19 +00:00
|
|
|
log::info!("updated interface {}\n", interface.as_str_lossy().yellow());
|
2021-03-29 17:22:14 +00:00
|
|
|
} else {
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("{}", "peers are already up to date.".green());
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
2021-09-01 09:58:46 +00:00
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
store.set_cidrs(cidrs);
|
2021-09-01 09:58:46 +00:00
|
|
|
store.update_peers(&peers)?;
|
2021-06-16 11:22:28 +00:00
|
|
|
store.write().with_str(interface.to_string())?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-09-01 09:58:46 +00:00
|
|
|
let candidates = wg::get_local_addrs()?
|
|
|
|
.into_iter()
|
|
|
|
.map(|addr| SocketAddr::from((addr, device.listen_port.unwrap_or(51820))).into())
|
|
|
|
.take(10)
|
|
|
|
.collect::<Vec<Endpoint>>();
|
|
|
|
log::info!(
|
|
|
|
"reporting {} interface address{} as NAT traversal candidates...",
|
|
|
|
candidates.len(),
|
|
|
|
if candidates.len() == 1 { "" } else { "es" }
|
|
|
|
);
|
|
|
|
log::debug!("candidates: {:?}", candidates);
|
|
|
|
match Api::new(&config.server).http_form::<_, ()>("PUT", "/user/candidates", &candidates) {
|
|
|
|
Err(ureq::Error::Status(404, _)) => {
|
|
|
|
log::warn!("your network is using an old version of innernet-server that doesn't support NAT traversal candidate reporting.")
|
|
|
|
},
|
|
|
|
Err(e) => return Err(e.into()),
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
|
|
|
|
log::debug!("viable ICE candidates: {:?}", candidates);
|
|
|
|
|
|
|
|
let mut nat_traverse = NatTraverse::new(interface, network.backend, &modifications)?;
|
|
|
|
loop {
|
|
|
|
if nat_traverse.is_finished() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
log::info!(
|
|
|
|
"Attempting to establish connection with {} remaining unconnected peers...",
|
|
|
|
nat_traverse.remaining()
|
|
|
|
);
|
|
|
|
nat_traverse.step()?;
|
|
|
|
}
|
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
fn uninstall(interface: &InterfaceName, network: NetworkOpt) -> Result<(), Error> {
|
2021-04-09 13:42:29 +00:00
|
|
|
if Confirm::with_theme(&*prompts::THEME)
|
2021-04-09 13:37:33 +00:00
|
|
|
.with_prompt(&format!(
|
|
|
|
"Permanently delete network \"{}\"?",
|
|
|
|
interface.as_str_lossy().yellow()
|
|
|
|
))
|
|
|
|
.default(false)
|
|
|
|
.interact()?
|
|
|
|
{
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!("bringing down interface (if up).");
|
2021-05-19 07:54:07 +00:00
|
|
|
wg::down(interface, network.backend).ok();
|
2021-04-09 13:37:33 +00:00
|
|
|
let config = InterfaceConfig::get_path(interface);
|
|
|
|
let data = DataStore::get_path(interface);
|
|
|
|
std::fs::remove_file(&config)
|
|
|
|
.with_path(&config)
|
2021-05-20 03:46:12 +00:00
|
|
|
.map_err(|e| log::warn!("{}", e.to_string().yellow()))
|
2021-04-09 13:37:33 +00:00
|
|
|
.ok();
|
|
|
|
std::fs::remove_file(&data)
|
|
|
|
.with_path(&data)
|
2021-05-20 03:46:12 +00:00
|
|
|
.map_err(|e| log::warn!("{}", e.to_string().yellow()))
|
2021-04-09 13:37:33 +00:00
|
|
|
.ok();
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!(
|
|
|
|
"network {} is uninstalled.",
|
2021-04-09 13:37:33 +00:00
|
|
|
interface.as_str_lossy().yellow()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-17 03:18:50 +00:00
|
|
|
fn add_cidr(interface: &InterfaceName, opts: AddCidrOpts) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching CIDRs");
|
2021-04-09 04:48:00 +00:00
|
|
|
let api = Api::new(&server);
|
|
|
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-21 04:00:09 +00:00
|
|
|
if let Some(cidr_request) = prompts::add_cidr(&cidrs, &opts)? {
|
|
|
|
log::info!("Creating CIDR...");
|
|
|
|
let cidr: Cidr = api.http_form("POST", "/admin/cidrs", cidr_request)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-21 04:00:09 +00:00
|
|
|
eprintdoc!(
|
|
|
|
"
|
|
|
|
CIDR \"{cidr_name}\" added.
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-21 04:00:09 +00:00
|
|
|
Right now, peers within {cidr_name} can only see peers in the same CIDR
|
|
|
|
, and in the special \"infra\" CIDR that includes the innernet server peer.
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-21 04:00:09 +00:00
|
|
|
You'll need to add more associations for peers in diffent CIDRs to communicate.
|
|
|
|
",
|
|
|
|
cidr_name = cidr.name.bold()
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log::info!("exited without creating CIDR.");
|
|
|
|
}
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-21 03:39:33 +00:00
|
|
|
fn delete_cidr(interface: &InterfaceName, opts: DeleteCidrOpts) -> Result<(), Error> {
|
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
|
|
|
println!("Fetching eligible CIDRs");
|
|
|
|
let api = Api::new(&server);
|
|
|
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
|
|
|
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
|
|
|
|
|
|
|
|
let cidr_id = prompts::delete_cidr(&cidrs, &peers, &opts)?;
|
|
|
|
|
|
|
|
println!("Deleting CIDR...");
|
|
|
|
let _ = api.http("DELETE", &*format!("/admin/cidrs/{}", cidr_id))?;
|
|
|
|
|
|
|
|
println!("CIDR deleted.");
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-06-14 09:50:21 +00:00
|
|
|
fn list_cidrs(interface: &InterfaceName, tree: bool) -> Result<(), Error> {
|
|
|
|
let data_store = DataStore::open(interface)?;
|
|
|
|
if tree {
|
|
|
|
let cidr_tree = CidrTree::new(data_store.cidrs());
|
|
|
|
colored::control::set_override(false);
|
|
|
|
print_tree(&cidr_tree, &[], 0);
|
|
|
|
colored::control::unset_override();
|
|
|
|
} else {
|
|
|
|
for cidr in data_store.cidrs() {
|
|
|
|
println!("{} {}", cidr.cidr, cidr.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-17 03:18:50 +00:00
|
|
|
fn add_peer(interface: &InterfaceName, opts: AddPeerOpts) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
2021-04-09 04:48:00 +00:00
|
|
|
let api = Api::new(&server);
|
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching CIDRs");
|
2021-04-09 04:48:00 +00:00
|
|
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching peers");
|
2021-04-09 04:48:00 +00:00
|
|
|
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
|
2021-03-29 17:22:14 +00:00
|
|
|
let cidr_tree = CidrTree::new(&cidrs[..]);
|
|
|
|
|
2021-06-14 09:15:31 +00:00
|
|
|
if let Some(result) = prompts::add_peer(&peers, &cidr_tree, &opts)? {
|
|
|
|
let (peer_request, keypair, target_path, mut target_file) = result;
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!("Creating peer...");
|
2021-04-09 04:48:00 +00:00
|
|
|
let peer: Peer = api.http_form("POST", "/admin/peers", peer_request)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
let server_peer = peers.iter().find(|p| p.id == 1).unwrap();
|
2021-06-14 09:15:31 +00:00
|
|
|
prompts::write_peer_invitation(
|
2021-06-16 11:34:53 +00:00
|
|
|
(&mut target_file, &target_path),
|
2021-03-29 17:22:14 +00:00
|
|
|
interface,
|
|
|
|
&peer,
|
|
|
|
server_peer,
|
|
|
|
&cidr_tree,
|
|
|
|
keypair,
|
|
|
|
&server.internal_endpoint,
|
|
|
|
)?;
|
|
|
|
} else {
|
2021-06-14 09:15:31 +00:00
|
|
|
log::info!("Exited without creating peer.");
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-25 10:58:00 +00:00
|
|
|
fn rename_peer(interface: &InterfaceName, opts: RenamePeerOpts) -> Result<(), Error> {
|
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
|
|
|
let api = Api::new(&server);
|
|
|
|
|
|
|
|
log::info!("Fetching peers");
|
|
|
|
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
|
|
|
|
|
|
|
|
if let Some((peer_request, old_name)) = prompts::rename_peer(&peers, &opts)? {
|
|
|
|
log::info!("Renaming peer...");
|
|
|
|
|
|
|
|
let id = peers
|
|
|
|
.iter()
|
|
|
|
.filter(|p| p.name == old_name)
|
|
|
|
.map(|p| p.id)
|
|
|
|
.next()
|
2021-06-16 11:34:53 +00:00
|
|
|
.ok_or_else(|| anyhow!("Peer not found."))?;
|
2021-05-25 10:58:00 +00:00
|
|
|
|
|
|
|
let _ = api.http_form("PUT", &format!("/admin/peers/{}", id), peer_request)?;
|
|
|
|
log::info!("Peer renamed.");
|
|
|
|
} else {
|
|
|
|
log::info!("exited without renaming peer.");
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-09 01:28:37 +00:00
|
|
|
fn enable_or_disable_peer(interface: &InterfaceName, enable: bool) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
2021-04-09 04:48:00 +00:00
|
|
|
let api = Api::new(&server);
|
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching peers.");
|
2021-04-09 04:48:00 +00:00
|
|
|
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
if let Some(peer) = prompts::enable_or_disable_peer(&peers[..], enable)? {
|
|
|
|
let Peer { id, mut contents } = peer;
|
|
|
|
contents.is_disabled = !enable;
|
2021-04-09 04:48:00 +00:00
|
|
|
api.http_form("PUT", &format!("/admin/peers/{}", id), contents)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
} else {
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("exiting without disabling peer.");
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-19 12:56:18 +00:00
|
|
|
fn add_association(interface: &InterfaceName, opts: AddAssociationOpts) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
2021-04-09 04:48:00 +00:00
|
|
|
let api = Api::new(&server);
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching CIDRs");
|
2021-04-09 04:48:00 +00:00
|
|
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-04-19 12:56:18 +00:00
|
|
|
let association = if let (Some(ref cidr1), Some(ref cidr2)) = (opts.cidr1, opts.cidr2) {
|
|
|
|
let cidr1 = cidrs
|
|
|
|
.iter()
|
|
|
|
.find(|c| &c.name == cidr1)
|
2021-06-16 11:34:53 +00:00
|
|
|
.ok_or_else(|| anyhow!("can't find cidr '{}'", cidr1))?;
|
2021-04-19 12:56:18 +00:00
|
|
|
let cidr2 = cidrs
|
|
|
|
.iter()
|
|
|
|
.find(|c| &c.name == cidr2)
|
2021-06-16 11:34:53 +00:00
|
|
|
.ok_or_else(|| anyhow!("can't find cidr '{}'", cidr2))?;
|
2021-04-19 12:56:18 +00:00
|
|
|
(cidr1, cidr2)
|
|
|
|
} else if let Some((cidr1, cidr2)) = prompts::add_association(&cidrs[..])? {
|
|
|
|
(cidr1, cidr2)
|
2021-03-29 17:22:14 +00:00
|
|
|
} else {
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("exiting without adding association.");
|
2021-04-19 12:56:18 +00:00
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
|
|
|
api.http_form(
|
|
|
|
"POST",
|
|
|
|
"/admin/associations",
|
|
|
|
AssociationContents {
|
|
|
|
cidr_id_1: association.0.id,
|
|
|
|
cidr_id_2: association.1.id,
|
|
|
|
},
|
|
|
|
)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-09 01:28:37 +00:00
|
|
|
fn delete_association(interface: &InterfaceName) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
2021-04-09 04:48:00 +00:00
|
|
|
let api = Api::new(&server);
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching CIDRs");
|
2021-04-09 04:48:00 +00:00
|
|
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching associations");
|
2021-04-09 04:48:00 +00:00
|
|
|
let associations: Vec<Association> = api.http("GET", "/admin/associations")?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
if let Some(association) = prompts::delete_association(&associations[..], &cidrs[..])? {
|
2021-04-09 04:48:00 +00:00
|
|
|
api.http("DELETE", &format!("/admin/associations/{}", association.id))?;
|
2021-03-29 17:22:14 +00:00
|
|
|
} else {
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("exiting without adding association.");
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-09 01:28:37 +00:00
|
|
|
fn list_associations(interface: &InterfaceName) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
2021-04-09 04:48:00 +00:00
|
|
|
let api = Api::new(&server);
|
|
|
|
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching CIDRs");
|
2021-04-09 04:48:00 +00:00
|
|
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("Fetching associations");
|
2021-04-09 04:48:00 +00:00
|
|
|
let associations: Vec<Association> = api.http("GET", "/admin/associations")?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
for association in associations {
|
|
|
|
println!(
|
|
|
|
"{}: {} <=> {}",
|
|
|
|
association.id,
|
|
|
|
&cidrs
|
|
|
|
.iter()
|
|
|
|
.find(|c| c.id == association.cidr_id_1)
|
|
|
|
.unwrap()
|
|
|
|
.name
|
|
|
|
.yellow(),
|
|
|
|
&cidrs
|
|
|
|
.iter()
|
|
|
|
.find(|c| c.id == association.cidr_id_2)
|
|
|
|
.unwrap()
|
|
|
|
.name
|
|
|
|
.yellow()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
fn set_listen_port(
|
|
|
|
interface: &InterfaceName,
|
|
|
|
unset: bool,
|
|
|
|
network: NetworkOpt,
|
|
|
|
) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let mut config = InterfaceConfig::from_interface(interface)?;
|
|
|
|
|
|
|
|
if let Some(listen_port) = prompts::set_listen_port(&config.interface, unset)? {
|
2021-05-19 07:54:07 +00:00
|
|
|
wg::set_listen_port(interface, listen_port, network.backend)?;
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("the interface is updated");
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
config.interface.listen_port = listen_port;
|
|
|
|
config.write_to_interface(interface)?;
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("the config file is updated");
|
2021-03-29 17:22:14 +00:00
|
|
|
} else {
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("exiting without updating the listen port.");
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
fn override_endpoint(
|
|
|
|
interface: &InterfaceName,
|
|
|
|
unset: bool,
|
|
|
|
network: NetworkOpt,
|
|
|
|
) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let config = InterfaceConfig::from_interface(interface)?;
|
|
|
|
if !unset && config.interface.listen_port.is_none() {
|
|
|
|
println!(
|
|
|
|
"{}: you need to set a listen port for your interface first.",
|
|
|
|
"note".bold().yellow()
|
|
|
|
);
|
2021-05-19 07:54:07 +00:00
|
|
|
set_listen_port(interface, unset, network)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(endpoint) = prompts::override_endpoint(unset)? {
|
2021-05-20 03:46:12 +00:00
|
|
|
log::info!("Updating endpoint.");
|
2021-04-09 04:48:00 +00:00
|
|
|
Api::new(&config.server).http_form(
|
|
|
|
"PUT",
|
2021-03-29 17:22:14 +00:00
|
|
|
"/user/endpoint",
|
|
|
|
EndpointContents::from(endpoint),
|
|
|
|
)?;
|
|
|
|
} else {
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("exiting without overriding endpoint.");
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
fn show(
|
|
|
|
short: bool,
|
|
|
|
tree: bool,
|
|
|
|
interface: Option<Interface>,
|
|
|
|
network: NetworkOpt,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let interfaces = interface.map_or_else(
|
|
|
|
|| Device::list(network.backend),
|
|
|
|
|interface| Ok(vec![*interface]),
|
|
|
|
)?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-09 15:03:00 +00:00
|
|
|
let devices = interfaces
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|name| {
|
2021-06-10 13:57:47 +00:00
|
|
|
match DataStore::open(&name) {
|
|
|
|
Ok(store) => {
|
|
|
|
let device = Device::get(&name, network.backend).with_str(name.as_str_lossy());
|
|
|
|
Some(device.map(|device| (device, store)))
|
|
|
|
},
|
|
|
|
// Skip WireGuard interfaces that aren't managed by innernet.
|
|
|
|
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
|
|
|
|
// Error on interfaces that *are* managed by innernet but are not readable.
|
|
|
|
Err(e) => Some(Err(e)),
|
|
|
|
}
|
2021-05-09 15:03:00 +00:00
|
|
|
})
|
2021-06-10 13:57:47 +00:00
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
2021-05-09 15:03:00 +00:00
|
|
|
|
|
|
|
if devices.is_empty() {
|
2021-05-19 18:11:51 +00:00
|
|
|
log::info!("No innernet networks currently running.");
|
2021-05-09 15:03:00 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
for (device_info, store) in devices {
|
2021-03-29 17:22:14 +00:00
|
|
|
let peers = store.peers();
|
|
|
|
let cidrs = store.cidrs();
|
|
|
|
let me = peers
|
|
|
|
.iter()
|
|
|
|
.find(|p| p.public_key == device_info.public_key.as_ref().unwrap().to_base64())
|
2021-06-16 11:34:53 +00:00
|
|
|
.ok_or_else(|| anyhow!("missing peer info"))?;
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-09 15:03:00 +00:00
|
|
|
let mut peer_states = device_info
|
|
|
|
.peers
|
|
|
|
.iter()
|
|
|
|
.map(|info| {
|
|
|
|
let public_key = info.config.public_key.to_base64();
|
|
|
|
match peers.iter().find(|p| p.public_key == public_key) {
|
|
|
|
Some(peer) => Ok(PeerState {
|
|
|
|
peer,
|
|
|
|
info: Some(info),
|
|
|
|
}),
|
2021-06-10 13:57:47 +00:00
|
|
|
None => Err(anyhow!("peer {} isn't an innernet peer.", public_key)),
|
2021-05-09 15:03:00 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<PeerState>, _>>()?;
|
|
|
|
peer_states.push(PeerState {
|
|
|
|
peer: me,
|
|
|
|
info: None,
|
2021-03-29 17:22:14 +00:00
|
|
|
});
|
|
|
|
|
2021-05-09 15:03:00 +00:00
|
|
|
print_interface(&device_info, short || tree)?;
|
|
|
|
peer_states.sort_by_key(|peer| peer.peer.ip);
|
|
|
|
|
2021-03-29 17:22:14 +00:00
|
|
|
if tree {
|
2021-05-06 03:40:00 +00:00
|
|
|
let cidr_tree = CidrTree::new(cidrs);
|
2021-05-09 15:03:00 +00:00
|
|
|
print_tree(&cidr_tree, &peer_states, 1);
|
2021-03-29 17:22:14 +00:00
|
|
|
} else {
|
2021-05-09 15:03:00 +00:00
|
|
|
for peer_state in peer_states {
|
|
|
|
print_peer(&peer_state, short, 1);
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-09 15:03:00 +00:00
|
|
|
fn print_tree(cidr: &CidrTree, peers: &[PeerState], level: usize) {
|
|
|
|
println_pad!(
|
|
|
|
level * 2,
|
|
|
|
"{} {}",
|
2021-03-29 17:22:14 +00:00
|
|
|
cidr.cidr.to_string().bold().blue(),
|
|
|
|
cidr.name.blue(),
|
|
|
|
);
|
|
|
|
|
2021-04-30 10:02:03 +00:00
|
|
|
let mut children: Vec<_> = cidr.children().collect();
|
2021-05-06 03:40:00 +00:00
|
|
|
children.sort();
|
2021-04-30 10:02:03 +00:00
|
|
|
children
|
|
|
|
.iter()
|
2021-06-22 02:27:29 +00:00
|
|
|
.for_each(|child| print_tree(child, peers, level + 1));
|
2021-03-29 17:22:14 +00:00
|
|
|
|
2021-05-09 15:03:00 +00:00
|
|
|
for peer in peers.iter().filter(|p| p.peer.cidr_id == cidr.id) {
|
|
|
|
print_peer(peer, true, level);
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 07:54:07 +00:00
|
|
|
fn print_interface(device_info: &Device, short: bool) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
if short {
|
2021-05-09 15:03:00 +00:00
|
|
|
let listen_port_str = device_info
|
|
|
|
.listen_port
|
|
|
|
.map(|p| format!("(:{}) ", p))
|
|
|
|
.unwrap_or_default();
|
2021-03-29 17:22:14 +00:00
|
|
|
println!(
|
2021-05-09 15:03:00 +00:00
|
|
|
"{} {}",
|
|
|
|
device_info.name.to_string().green().bold(),
|
|
|
|
listen_port_str.dimmed(),
|
2021-03-29 17:22:14 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
println!(
|
2021-05-09 15:03:00 +00:00
|
|
|
"{}: {}",
|
|
|
|
"network".green().bold(),
|
2021-04-09 01:28:37 +00:00
|
|
|
device_info.name.to_string().green(),
|
2021-03-29 17:22:14 +00:00
|
|
|
);
|
2021-05-09 15:03:00 +00:00
|
|
|
if let Some(listen_port) = device_info.listen_port {
|
|
|
|
println!(" {}: {}", "listening port".bold(), listen_port);
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-09 15:03:00 +00:00
|
|
|
fn print_peer(peer: &PeerState, short: bool, level: usize) {
|
|
|
|
let pad = level * 2;
|
|
|
|
let PeerState { peer, info } = peer;
|
2021-03-29 17:22:14 +00:00
|
|
|
if short {
|
2021-09-01 09:58:46 +00:00
|
|
|
let connected = info
|
|
|
|
.map(|info| !info.is_recently_connected())
|
|
|
|
.unwrap_or_default();
|
2021-05-09 15:03:00 +00:00
|
|
|
|
|
|
|
println_pad!(
|
|
|
|
pad,
|
|
|
|
"| {} {}: {} ({}{}…)",
|
2021-08-05 00:38:14 +00:00
|
|
|
if connected {
|
|
|
|
"◉".bold()
|
|
|
|
} else {
|
|
|
|
"◯".dimmed()
|
|
|
|
},
|
2021-05-09 15:03:00 +00:00
|
|
|
peer.ip.to_string().yellow().bold(),
|
|
|
|
peer.name.yellow(),
|
|
|
|
if info.is_none() { "you, " } else { "" },
|
|
|
|
&peer.public_key[..6].dimmed(),
|
2021-03-29 17:22:14 +00:00
|
|
|
);
|
|
|
|
} else {
|
2021-05-09 15:03:00 +00:00
|
|
|
println_pad!(
|
|
|
|
pad,
|
2021-03-29 17:22:14 +00:00
|
|
|
"{}: {} ({}...)",
|
|
|
|
"peer".yellow().bold(),
|
2021-05-09 15:03:00 +00:00
|
|
|
peer.name.yellow(),
|
|
|
|
&peer.public_key[..10].yellow(),
|
2021-03-29 17:22:14 +00:00
|
|
|
);
|
2021-05-09 15:03:00 +00:00
|
|
|
println_pad!(pad, " {}: {}", "ip".bold(), peer.ip);
|
|
|
|
if let Some(ref endpoint) = peer.endpoint {
|
|
|
|
println_pad!(pad, " {}: {}", "endpoint".bold(), endpoint);
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
2021-05-09 15:03:00 +00:00
|
|
|
if let Some(info) = info {
|
|
|
|
if let Some(last_handshake) = info.stats.last_handshake_time {
|
|
|
|
let duration = last_handshake.elapsed().expect("horrible clock problem");
|
|
|
|
println_pad!(
|
|
|
|
pad,
|
|
|
|
" {}: {}",
|
|
|
|
"last handshake".bold(),
|
|
|
|
human_duration(duration),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if info.stats.tx_bytes > 0 || info.stats.rx_bytes > 0 {
|
|
|
|
println_pad!(
|
|
|
|
pad,
|
|
|
|
" {}: {} received, {} sent",
|
|
|
|
"transfer".bold(),
|
|
|
|
human_size(info.stats.rx_bytes),
|
|
|
|
human_size(info.stats.tx_bytes),
|
|
|
|
);
|
|
|
|
}
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
2021-04-30 10:02:03 +00:00
|
|
|
let opt = Opts::from_args();
|
2021-05-26 05:23:02 +00:00
|
|
|
util::init_logger(opt.verbose);
|
2021-03-29 17:22:14 +00:00
|
|
|
|
|
|
|
if let Err(e) = run(opt) {
|
2021-05-20 03:46:12 +00:00
|
|
|
println!();
|
|
|
|
log::error!("{}\n", e);
|
2021-06-10 13:57:47 +00:00
|
|
|
if let Some(e) = e.downcast_ref::<WrappedIoError>() {
|
|
|
|
util::permissions_helptext(e);
|
|
|
|
}
|
2021-06-16 11:22:28 +00:00
|
|
|
if let Some(e) = e.downcast_ref::<io::Error>() {
|
|
|
|
util::permissions_helptext(e);
|
|
|
|
}
|
2021-03-29 17:22:14 +00:00
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-30 10:02:03 +00:00
|
|
|
fn run(opt: Opts) -> Result<(), Error> {
|
2021-03-29 17:22:14 +00:00
|
|
|
let command = opt.command.unwrap_or(Command::Show {
|
|
|
|
short: false,
|
|
|
|
tree: false,
|
|
|
|
interface: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
match command {
|
2021-04-17 03:18:50 +00:00
|
|
|
Command::Install {
|
2021-04-17 16:12:15 +00:00
|
|
|
invite,
|
2021-04-17 03:18:50 +00:00
|
|
|
hosts,
|
|
|
|
opts,
|
2021-05-19 07:54:07 +00:00
|
|
|
} => install(&invite, hosts.into(), opts, opt.network)?,
|
2021-03-29 17:22:14 +00:00
|
|
|
Command::Show {
|
|
|
|
short,
|
|
|
|
tree,
|
|
|
|
interface,
|
2021-05-19 07:54:07 +00:00
|
|
|
} => show(short, tree, interface, opt.network)?,
|
|
|
|
Command::Fetch { interface, hosts } => fetch(&interface, false, hosts.into(), opt.network)?,
|
2021-03-29 17:22:14 +00:00
|
|
|
Command::Up {
|
|
|
|
interface,
|
|
|
|
daemon,
|
2021-04-07 08:00:52 +00:00
|
|
|
hosts,
|
2021-03-29 17:22:14 +00:00
|
|
|
interval,
|
2021-04-07 08:00:52 +00:00
|
|
|
} => up(
|
|
|
|
&interface,
|
|
|
|
daemon.then(|| Duration::from_secs(interval)),
|
|
|
|
hosts.into(),
|
2021-05-19 07:54:07 +00:00
|
|
|
opt.network,
|
2021-04-07 08:00:52 +00:00
|
|
|
)?,
|
2021-05-19 07:54:07 +00:00
|
|
|
Command::Down { interface } => wg::down(&interface, opt.network.backend)?,
|
|
|
|
Command::Uninstall { interface } => uninstall(&interface, opt.network)?,
|
2021-04-17 03:18:50 +00:00
|
|
|
Command::AddPeer { interface, opts } => add_peer(&interface, opts)?,
|
2021-05-25 10:58:00 +00:00
|
|
|
Command::RenamePeer { interface, opts } => rename_peer(&interface, opts)?,
|
2021-04-17 03:18:50 +00:00
|
|
|
Command::AddCidr { interface, opts } => add_cidr(&interface, opts)?,
|
2021-05-21 03:39:33 +00:00
|
|
|
Command::DeleteCidr { interface, opts } => delete_cidr(&interface, opts)?,
|
2021-06-14 09:50:21 +00:00
|
|
|
Command::ListCidrs { interface, tree } => list_cidrs(&interface, tree)?,
|
2021-03-29 17:22:14 +00:00
|
|
|
Command::DisablePeer { interface } => enable_or_disable_peer(&interface, false)?,
|
|
|
|
Command::EnablePeer { interface } => enable_or_disable_peer(&interface, true)?,
|
2021-04-19 12:56:18 +00:00
|
|
|
Command::AddAssociation { interface, opts } => add_association(&interface, opts)?,
|
2021-03-29 17:22:14 +00:00
|
|
|
Command::DeleteAssociation { interface } => delete_association(&interface)?,
|
|
|
|
Command::ListAssociations { interface } => list_associations(&interface)?,
|
2021-05-19 07:54:07 +00:00
|
|
|
Command::SetListenPort { interface, unset } => {
|
|
|
|
set_listen_port(&interface, unset, opt.network)?
|
|
|
|
},
|
|
|
|
Command::OverrideEndpoint { interface, unset } => {
|
|
|
|
override_endpoint(&interface, unset, opt.network)?
|
|
|
|
},
|
2021-05-25 07:10:16 +00:00
|
|
|
Command::Completions { shell } => {
|
|
|
|
Opts::clap().gen_completions_to("innernet", shell, &mut std::io::stdout());
|
|
|
|
std::process::exit(0);
|
|
|
|
},
|
2021-03-29 17:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|