2021-06-10 13:57:47 +00:00
use anyhow ::{ anyhow , bail } ;
2022-01-11 07:54:59 +00:00
use clap ::{ AppSettings , Args , IntoApp , Parser , Subcommand } ;
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-14 06:48:27 +00:00
get_local_addrs ,
2021-09-01 09:58:46 +00:00
interface_config ::InterfaceConfig ,
prompts ,
wg ::{ DeviceExt , PeerInfoExt } ,
2022-01-31 06:10:45 +00:00
AddCidrOpts , AddDeleteAssociationOpts , AddPeerOpts , Association , AssociationContents , Cidr ,
CidrTree , DeleteCidrOpts , Endpoint , EndpointContents , InstallOpts , Interface , IoErrorContext ,
2021-11-16 09:46:45 +00:00
ListenPortOpts , NatOpts , NetworkOpts , OverrideEndpointOpts , Peer , RedeemContents ,
RenamePeerOpts , State , WrappedIoError , 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-11-11 09:34:31 +00:00
time ::{ Duration , Instant } ,
2021-03-29 17:22:14 +00:00
} ;
2021-09-15 03:29:58 +00:00
use wireguard_control ::{ 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-11-24 06:07:57 +00:00
use crate ::util ::all_installed ;
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 ) * ) ;
}
}
2022-01-11 07:51:32 +00:00
#[ derive(Clone, Debug, Parser) ]
#[ clap(name = " innernet " , author, version, about) ]
#[ clap(global_setting(AppSettings::DeriveDisplayOrder)) ]
2021-04-30 10:02:03 +00:00
struct Opts {
2022-01-11 07:51:32 +00:00
#[ clap(subcommand) ]
2021-03-29 17:22:14 +00:00
command : Option < Command > ,
2021-05-19 07:54:07 +00:00
2021-11-16 09:46:45 +00:00
/// Verbose output, use -vv for even higher verbositude
2022-01-11 07:51:32 +00:00
#[ clap(short, long, parse(from_occurrences)) ]
2021-05-26 05:23:02 +00:00
verbose : u64 ,
2021-05-19 18:11:51 +00:00
2022-01-11 07:51:32 +00:00
#[ clap(short, long, default_value = " /etc/innernet " ) ]
2021-11-15 09:11:13 +00:00
config_dir : PathBuf ,
2022-01-11 07:51:32 +00:00
#[ clap(short, long, default_value = " /var/lib/innernet " ) ]
2021-11-15 09:11:13 +00:00
data_dir : PathBuf ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-12 05:42:10 +00:00
network : NetworkOpts ,
2021-03-29 17:22:14 +00:00
}
2022-01-11 07:51:32 +00:00
#[ derive(Clone, Debug, Args) ]
2021-04-07 08:00:52 +00:00
struct HostsOpt {
2021-11-16 09:46:45 +00:00
/// The path to write hosts to
2022-01-11 07:51:32 +00:00
#[ clap(long = " hosts-path " , default_value = " /etc/hosts " ) ]
2021-04-07 08:00:52 +00:00
hosts_path : PathBuf ,
2021-11-16 09:46:45 +00:00
/// Don't write to any hosts files
2022-01-11 07:51:32 +00:00
#[ clap(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 )
}
}
2022-01-11 07:51:32 +00:00
#[ derive(Clone, Debug, Subcommand) ]
2021-03-29 17:22:14 +00:00
enum Command {
2021-11-16 09:46:45 +00:00
/// Install a new innernet config
2022-01-11 07:51:32 +00:00
#[ clap(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
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-04-07 08:00:52 +00:00
hosts : HostsOpt ,
2021-04-17 03:18:50 +00:00
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-15 09:11:13 +00:00
install_opts : InstallOpts ,
2021-11-12 05:42:10 +00:00
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-12 05:42:10 +00:00
nat : NatOpts ,
2021-04-07 08:00:52 +00:00
} ,
2021-03-29 17:22:14 +00:00
2021-11-16 09:46:45 +00:00
/// Enumerate all innernet connections
2022-01-11 07:51:32 +00:00
#[ clap(alias = " list " ) ]
2021-03-29 17:22:14 +00:00
Show {
2021-04-17 16:12:15 +00:00
/// One-line peer list
2022-01-11 07:51:32 +00:00
#[ clap(short, long) ]
2021-03-29 17:22:14 +00:00
short : bool ,
2021-04-17 16:12:15 +00:00
/// Display peers in a tree based on the CIDRs
2022-01-11 07:51:32 +00:00
#[ clap(short, long) ]
2021-03-29 17:22:14 +00:00
tree : bool ,
interface : Option < Interface > ,
} ,
2021-11-16 09:46:45 +00:00
/// Bring up your local interface, and update it with latest peer list
2021-03-29 17:22:14 +00:00
Up {
/// Enable daemon mode i.e. keep the process running, while fetching
2021-11-16 09:46:45 +00:00
/// the latest peer list periodically
2022-01-11 07:51:32 +00:00
#[ clap(short, long) ]
2021-03-29 17:22:14 +00:00
daemon : bool ,
/// Keep fetching the latest peer list at the specified interval in
2021-11-16 09:46:45 +00:00
/// seconds. Valid only in daemon mode
2022-01-11 07:51:32 +00:00
#[ clap(long, default_value = " 60 " ) ]
2021-03-29 17:22:14 +00:00
interval : u64 ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-04-07 08:00:52 +00:00
hosts : HostsOpt ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-12 05:42:10 +00:00
nat : NatOpts ,
2021-11-24 06:07:57 +00:00
interface : Option < Interface > ,
2021-03-29 17:22:14 +00:00
} ,
2021-11-16 09:46:45 +00:00
/// Fetch and update your local interface with the latest peer list
2021-04-07 08:00:52 +00:00
Fetch {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-04-07 08:00:52 +00:00
hosts : HostsOpt ,
2021-11-12 05:42:10 +00:00
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-12 05:42:10 +00:00
nat : NatOpts ,
2021-04-07 08:00:52 +00:00
} ,
2021-03-29 17:22:14 +00:00
2021-04-09 13:37:33 +00:00
/// Uninstall an innernet network.
2022-01-31 06:10:45 +00:00
Uninstall {
interface : Interface ,
/// Bypass confirmation
#[ clap(long) ]
yes : bool ,
} ,
2021-04-09 13:37:33 +00:00
2021-09-21 03:46:56 +00:00
/// Bring down the interface (equivalent to 'wg-quick down <interface>')
2021-03-29 17:22:14 +00:00
Down { interface : Interface } ,
2021-11-16 09:46:45 +00:00
/// 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:
///
2021-09-21 03:46:56 +00:00
/// --name 'person' --cidr 'humans' --admin false --auto-ip --save-config 'person.toml' --yes
2021-04-14 15:25:31 +00:00
AddPeer {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-15 09:11:13 +00:00
sub_opts : AddPeerOpts ,
2021-04-14 15:25:31 +00:00
} ,
2021-03-29 17:22:14 +00:00
2021-11-16 09:46:45 +00:00
/// Rename a peer
2021-05-25 10:58:00 +00:00
///
/// By default, you'll be prompted interactively to select a peer, but you can
/// also specify all the options in the command, eg:
///
2021-09-21 03:46:56 +00:00
/// --name 'person' --new-name 'human'
2021-05-25 10:58:00 +00:00
RenamePeer {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-15 09:11:13 +00:00
sub_opts : RenamePeerOpts ,
2021-05-25 10:58:00 +00:00
} ,
2021-11-16 09:46:45 +00:00
/// Add a new CIDR
2021-04-14 15:25:31 +00:00
AddCidr {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-15 09:11:13 +00:00
sub_opts : AddCidrOpts ,
2021-04-14 15:25:31 +00:00
} ,
2021-03-29 17:22:14 +00:00
2021-11-16 09:46:45 +00:00
/// Delete a CIDR
2021-05-21 03:39:33 +00:00
DeleteCidr {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-15 09:11:13 +00:00
sub_opts : DeleteCidrOpts ,
2021-05-21 03:39:33 +00:00
} ,
2021-11-16 09:46:45 +00:00
/// List CIDRs
2021-06-14 09:50:21 +00:00
ListCidrs {
interface : Interface ,
/// Display CIDRs in tree format
2022-01-11 07:51:32 +00:00
#[ clap(short, long) ]
2021-06-14 09:50:21 +00:00
tree : bool ,
} ,
2021-11-16 09:46:45 +00:00
/// Disable an enabled peer
2021-03-29 17:22:14 +00:00
DisablePeer { interface : Interface } ,
2021-11-16 09:46:45 +00:00
/// Enable a disabled peer
2021-03-29 17:22:14 +00:00
EnablePeer { interface : Interface } ,
2021-11-16 09:46:45 +00:00
/// Add an association between CIDRs
2021-04-19 12:56:18 +00:00
AddAssociation {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2022-01-31 06:10:45 +00:00
sub_opts : AddDeleteAssociationOpts ,
2021-04-19 12:56:18 +00:00
} ,
2021-03-29 17:22:14 +00:00
2021-11-16 09:46:45 +00:00
/// Delete an association between CIDRs
2022-01-31 06:10:45 +00:00
DeleteAssociation {
interface : Interface ,
#[ clap(flatten) ]
sub_opts : AddDeleteAssociationOpts ,
} ,
2021-03-29 17:22:14 +00:00
2021-11-16 09:46:45 +00:00
/// List existing assocations between CIDRs
2021-03-29 17:22:14 +00:00
ListAssociations { interface : Interface } ,
/// Set the local listen port.
SetListenPort {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-16 09:46:45 +00:00
sub_opts : ListenPortOpts ,
2021-03-29 17:22:14 +00:00
} ,
2021-11-16 09:46:45 +00:00
/// Override your external endpoint that the server sends to other peers
2021-03-29 17:22:14 +00:00
OverrideEndpoint {
interface : Interface ,
2022-01-11 07:51:32 +00:00
#[ clap(flatten) ]
2021-11-16 09:46:45 +00:00
sub_opts : OverrideEndpointOpts ,
2021-03-29 17:22:14 +00:00
} ,
2021-05-25 07:10:16 +00:00
/// Generate shell completion scripts
Completions {
2022-01-11 07:51:32 +00:00
#[ clap(arg_enum) ]
shell : clap_complete ::Shell ,
2021-05-25 07:10:16 +00:00
} ,
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 (
2021-11-15 09:11:13 +00:00
opts : & Opts ,
2021-05-11 17:31:47 +00:00
invite : & Path ,
hosts_file : Option < PathBuf > ,
2021-11-15 09:11:13 +00:00
install_opts : InstallOpts ,
2021-11-12 05:42:10 +00:00
nat : & NatOpts ,
2021-05-11 17:31:47 +00:00
) -> Result < ( ) , Error > {
2021-11-15 09:11:13 +00:00
shared ::ensure_dirs_exist ( & [ & opts . 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-11-15 09:11:13 +00:00
let iface = if install_opts . default_name {
2021-04-17 03:18:50 +00:00
config . interface . network_name . clone ( )
2021-11-15 09:11:13 +00:00
} else if let Some ( ref iface ) = install_opts . name {
2021-04-17 03:18:50 +00:00
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-11-15 09:11:13 +00:00
let target_conf = opts . 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 ( ) ? ;
2021-11-15 09:11:13 +00:00
if Device ::list ( opts . 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-11-15 09:11:13 +00:00
redeem_invite ( & iface , config , target_conf , opts . 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-11-15 09:11:13 +00:00
if let Err ( e ) = wg ::down ( & iface , opts . 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-11-15 09:11:13 +00:00
if fetch ( & iface , opts , true , hosts_file . clone ( ) , nat ) . 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-11-15 09:11:13 +00:00
if install_opts . delete_invite
2021-04-17 03:18:50 +00:00
| | 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 60 s update loop :
{ daemon_mode } { interface }
" ,
interface = iface . to_string ( ) . yellow ( ) ,
daemon_mode = " innernet up -d --interval 60 " . yellow ( ) ) ;
} else {
2021-09-12 15:48:49 +00:00
eprintdoc! (
"
2021-09-05 07:37:58 +00:00
It ' s recommended to now keep the interface automatically refreshing via whatever service
system your distribution provides .
Ex . to run innernet in a 60 s update loop :
{ daemon_mode } { interface }
" ,
interface = iface . to_string ( ) . yellow ( ) ,
2021-09-12 15:48:49 +00:00
daemon_mode = " innernet up -d --interval 60 " . yellow ( )
) ;
2021-09-05 07:37:58 +00:00
}
2021-04-10 08:38:59 +00:00
Ok ( ( ) )
}
fn redeem_invite (
iface : & InterfaceName ,
mut config : InterfaceConfig ,
target_conf : PathBuf ,
2021-11-12 05:42:10 +00:00
network : NetworkOpts ,
2021-04-10 08:38:59 +00:00
) -> Result < ( ) , Error > {
2021-11-24 06:07:57 +00:00
log ::info! ( " bringing up interface {}. " , iface . as_str_lossy ( ) . yellow ( ) ) ;
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-09-15 03:29:58 +00:00
let keypair = wireguard_control ::KeyPair ::generate ( ) ;
2021-03-29 17:22:14 +00:00
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-09-14 06:48:27 +00:00
log ::info! ( " Changing keys and waiting 5s 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-11-15 09:11:13 +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-11-24 06:07:57 +00:00
interface : Option < Interface > ,
2021-11-15 09:11:13 +00:00
opts : & Opts ,
2021-04-07 08:00:52 +00:00
loop_interval : Option < Duration > ,
hosts_path : Option < PathBuf > ,
2021-11-12 05:42:10 +00:00
nat : & NatOpts ,
2021-04-07 08:00:52 +00:00
) -> Result < ( ) , Error > {
2021-03-29 17:22:14 +00:00
loop {
2021-11-24 06:07:57 +00:00
let interfaces = match & interface {
Some ( iface ) = > vec! [ iface . clone ( ) ] ,
None = > all_installed ( & opts . config_dir ) ? ,
} ;
for iface in interfaces {
fetch ( & * iface , opts , true , hosts_path . clone ( ) , nat ) ? ;
}
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-11-15 09:11:13 +00:00
opts : & Opts ,
2021-04-07 08:00:52 +00:00
bring_up_interface : bool ,
hosts_path : Option < PathBuf > ,
2021-11-12 05:42:10 +00:00
nat : & NatOpts ,
2021-04-07 08:00:52 +00:00
) -> Result < ( ) , Error > {
2021-11-15 09:11:13 +00:00
let config = InterfaceConfig ::from_interface ( & opts . config_dir , interface ) ? ;
let interface_up = match Device ::list ( opts . network . backend ) {
2021-08-05 00:38:14 +00:00
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
}
2022-01-07 09:35:21 +00:00
log ::info! (
" bringing up interface {}. " ,
interface . as_str_lossy ( ) . yellow ( )
) ;
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-11-15 09:11:13 +00:00
opts . network ,
2021-06-16 11:22:28 +00:00
)
. with_str ( interface . to_string ( ) ) ? ;
2021-03-29 17:22:14 +00:00
}
2022-01-07 09:35:21 +00:00
log ::info! (
" fetching state for {} from server... " ,
interface . as_str_lossy ( ) . yellow ( )
) ;
2021-11-15 09:11:13 +00:00
let mut store = DataStore ::open_or_create ( & opts . data_dir , interface ) ? ;
2021-11-12 08:53:37 +00:00
let api = Api ::new ( & config . server ) ;
let State { peers , cidrs } = api . http ( " GET " , " /user/state " ) ? ;
2021-03-29 17:22:14 +00:00
2021-11-15 09:11:13 +00:00
let device = Device ::get ( interface , opts . network . backend ) ? ;
2021-09-01 09:58:46 +00:00
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-09-14 08:56:08 +00:00
if ! updates . is_empty ( ) | | ! interface_up {
2021-08-05 00:38:14 +00:00
DeviceUpdate ::new ( )
. add_peers ( & updates )
2021-11-15 09:11:13 +00:00
. apply ( interface , opts . 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-11-16 09:46:45 +00:00
log ::info! ( " {} " , " peers are already up to date " . green ( ) ) ;
2021-03-29 17:22:14 +00:00
}
2021-11-11 09:34:31 +00:00
let interface_updated_time = Instant ::now ( ) ;
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-14 06:48:27 +00:00
let candidates : Vec < Endpoint > = get_local_addrs ( ) ?
2021-11-12 05:42:10 +00:00
. filter ( | ip | ! nat . is_excluded ( * ip ) )
2021-09-01 09:58:46 +00:00
. map ( | addr | SocketAddr ::from ( ( addr , device . listen_port . unwrap_or ( 51820 ) ) ) . into ( ) )
. collect ::< Vec < Endpoint > > ( ) ;
log ::info! (
2022-02-01 03:21:31 +00:00
" reporting {} interface address{} as NAT traversal candidates " ,
2021-09-01 09:58:46 +00:00
candidates . len ( ) ,
2022-02-01 03:21:31 +00:00
if candidates . len ( ) = = 1 { " " } else { " es " } ,
2021-09-01 09:58:46 +00:00
) ;
2022-02-01 03:21:31 +00:00
for candidate in & candidates {
log ::debug! ( " candidate: {} " , candidate ) ;
}
2021-11-12 08:53:37 +00:00
match api . http_form ::< _ , ( ) > ( " PUT " , " /user/candidates " , & candidates ) {
2021-09-01 09:58:46 +00:00
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 ( ) ) ,
_ = > { } ,
}
2022-02-02 16:49:51 +00:00
log ::debug! ( " candidates successfully reported " ) ;
2021-09-01 09:58:46 +00:00
2021-11-12 05:42:10 +00:00
if nat . no_nat_traversal {
log ::debug! ( " NAT traversal explicitly disabled, not attempting. " ) ;
} else {
2021-11-15 09:11:13 +00:00
let mut nat_traverse = NatTraverse ::new ( interface , opts . network . backend , & modifications ) ? ;
2021-11-11 09:34:31 +00:00
2021-11-12 05:42:10 +00:00
// Give time for handshakes with recently changed endpoints to complete before attempting traversal.
if ! nat_traverse . is_finished ( ) {
thread ::sleep ( nat ::STEP_INTERVAL - interface_updated_time . elapsed ( ) ) ;
}
loop {
if nat_traverse . is_finished ( ) {
break ;
}
log ::info! (
" Attempting to establish connection with {} remaining unconnected peers... " ,
nat_traverse . remaining ( )
) ;
nat_traverse . step ( ) ? ;
2021-09-01 09:58:46 +00:00
}
}
2021-03-29 17:22:14 +00:00
Ok ( ( ) )
}
2022-01-31 06:10:45 +00:00
fn uninstall ( interface : & InterfaceName , opts : & Opts , yes : bool ) -> Result < ( ) , Error > {
2022-01-24 01:46:04 +00:00
let config = InterfaceConfig ::get_path ( & opts . config_dir , interface ) ;
let data = DataStore ::get_path ( & opts . data_dir , interface ) ;
if ! config . exists ( ) & & ! data . exists ( ) {
bail! (
" No network named \" {} \" exists. " ,
interface . as_str_lossy ( ) . yellow ( )
) ;
}
2022-01-31 06:10:45 +00:00
if yes
| | Confirm ::with_theme ( & * prompts ::THEME )
. with_prompt ( & format! (
" Permanently delete network \" {} \" ? " ,
interface . as_str_lossy ( ) . yellow ( )
) )
. default ( false )
. wait_for_newline ( true )
. interact ( ) ?
2021-04-09 13:37:33 +00:00
{
2021-05-20 03:46:12 +00:00
log ::info! ( " bringing down interface (if up). " ) ;
2021-11-15 09:11:13 +00:00
wg ::down ( interface , opts . network . backend ) . ok ( ) ;
2021-04-09 13:37:33 +00:00
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-11-15 09:11:13 +00:00
fn add_cidr ( interface : & InterfaceName , opts : & Opts , sub_opts : AddCidrOpts ) -> Result < ( ) , Error > {
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , 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-11-15 09:11:13 +00:00
if let Some ( cidr_request ) = prompts ::add_cidr ( & cidrs , & sub_opts ) ? {
2021-05-21 04:00:09 +00:00
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-11-15 09:11:13 +00:00
fn delete_cidr (
interface : & InterfaceName ,
opts : & Opts ,
sub_opts : DeleteCidrOpts ,
) -> Result < ( ) , Error > {
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , interface ) ? ;
2021-05-21 03:39:33 +00:00
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 " ) ? ;
2021-11-15 09:11:13 +00:00
let cidr_id = prompts ::delete_cidr ( & cidrs , & peers , & sub_opts ) ? ;
2021-05-21 03:39:33 +00:00
println! ( " Deleting CIDR... " ) ;
2022-07-29 08:12:43 +00:00
api . http ( " DELETE " , & * format! ( " /admin/cidrs/ {} " , cidr_id ) ) ? ;
2021-05-21 03:39:33 +00:00
println! ( " CIDR deleted. " ) ;
Ok ( ( ) )
}
2021-11-15 09:11:13 +00:00
fn list_cidrs ( interface : & InterfaceName , opts : & Opts , tree : bool ) -> Result < ( ) , Error > {
let data_store = DataStore ::open ( & opts . data_dir , interface ) ? ;
2021-06-14 09:50:21 +00:00
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-11-15 09:11:13 +00:00
fn add_peer ( interface : & InterfaceName , opts : & Opts , sub_opts : AddPeerOpts ) -> Result < ( ) , Error > {
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , 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-11-15 09:11:13 +00:00
if let Some ( result ) = prompts ::add_peer ( & peers , & cidr_tree , & sub_opts ) ? {
2021-06-14 09:15:31 +00:00
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-11-15 09:11:13 +00:00
fn rename_peer (
interface : & InterfaceName ,
opts : & Opts ,
sub_opts : RenamePeerOpts ,
) -> Result < ( ) , Error > {
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , interface ) ? ;
2021-05-25 10:58:00 +00:00
let api = Api ::new ( & server ) ;
log ::info! ( " Fetching peers " ) ;
let peers : Vec < Peer > = api . http ( " GET " , " /admin/peers " ) ? ;
2021-11-15 09:11:13 +00:00
if let Some ( ( peer_request , old_name ) ) = prompts ::rename_peer ( & peers , & sub_opts ) ? {
2021-05-25 10:58:00 +00:00
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
2022-07-29 08:12:43 +00:00
api . http_form ( " PUT " , & format! ( " /admin/peers/ {} " , id ) , peer_request ) ? ;
2021-05-25 10:58:00 +00:00
log ::info! ( " Peer renamed. " ) ;
} else {
log ::info! ( " exited without renaming peer. " ) ;
}
Ok ( ( ) )
}
2021-11-15 09:11:13 +00:00
fn enable_or_disable_peer (
interface : & InterfaceName ,
opts : & Opts ,
enable : bool ,
) -> Result < ( ) , Error > {
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , 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-11-15 09:11:13 +00:00
fn add_association (
interface : & InterfaceName ,
opts : & Opts ,
2022-01-31 06:10:45 +00:00
sub_opts : AddDeleteAssociationOpts ,
2021-11-15 09:11:13 +00:00
) -> Result < ( ) , Error > {
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , 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
2022-01-31 06:10:45 +00:00
let association = if let ( Some ( ref cidr1 ) , Some ( ref cidr2 ) ) = ( & sub_opts . cidr1 , & sub_opts . cidr2 )
{
2021-04-19 12:56:18 +00:00
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 )
2022-01-31 06:10:45 +00:00
} else if let Some ( ( cidr1 , cidr2 ) ) = prompts ::add_association ( & cidrs [ .. ] , & sub_opts ) ? {
2021-04-19 12:56:18 +00:00
( 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 ( ( ) )
}
2022-01-31 06:10:45 +00:00
fn delete_association (
interface : & InterfaceName ,
opts : & Opts ,
sub_opts : AddDeleteAssociationOpts ,
) -> Result < ( ) , Error > {
2021-11-15 09:11:13 +00:00
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , 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
2022-01-31 06:10:45 +00:00
if let Some ( association ) =
prompts ::delete_association ( & associations [ .. ] , & cidrs [ .. ] , & sub_opts ) ?
{
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-11-15 09:11:13 +00:00
fn list_associations ( interface : & InterfaceName , opts : & Opts ) -> Result < ( ) , Error > {
let InterfaceConfig { server , .. } =
InterfaceConfig ::from_interface ( & opts . config_dir , 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 ,
2021-11-15 09:11:13 +00:00
opts : & Opts ,
2021-11-16 09:46:45 +00:00
sub_opts : ListenPortOpts ,
2021-09-15 11:43:38 +00:00
) -> Result < Option < u16 > , Error > {
2021-11-15 09:11:13 +00:00
let mut config = InterfaceConfig ::from_interface ( & opts . config_dir , interface ) ? ;
2021-03-29 17:22:14 +00:00
2021-11-16 09:46:45 +00:00
let listen_port = prompts ::set_listen_port ( & config . interface , sub_opts ) ? ;
2021-09-15 11:43:38 +00:00
if let Some ( listen_port ) = listen_port {
2021-11-15 09:11:13 +00:00
wg ::set_listen_port ( interface , listen_port , opts . 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 ;
2021-11-15 09:11:13 +00:00
config . write_to_interface ( & opts . config_dir , 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
}
2021-09-15 11:43:38 +00:00
Ok ( listen_port . flatten ( ) )
2021-03-29 17:22:14 +00:00
}
2021-11-16 09:46:45 +00:00
fn override_endpoint (
interface : & InterfaceName ,
opts : & Opts ,
sub_opts : OverrideEndpointOpts ,
) -> Result < ( ) , Error > {
2021-11-15 09:11:13 +00:00
let config = InterfaceConfig ::from_interface ( & opts . config_dir , interface ) ? ;
2021-11-16 09:46:45 +00:00
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 )
2021-09-15 11:43:38 +00:00
} else {
2021-11-16 09:46:45 +00:00
let endpoint = prompts ::override_endpoint ( & sub_opts , port ) ? ;
2021-09-15 12:18:04 +00:00
endpoint . map ( EndpointContents ::Set )
2021-09-15 11:43:38 +00:00
} ;
2021-03-29 17:22:14 +00:00
2021-09-15 11:43:38 +00:00
if let Some ( contents ) = endpoint_contents {
2021-11-16 09:46:45 +00:00
log ::info! ( " requesting endpoint update... " ) ;
2021-09-15 12:18:04 +00:00
Api ::new ( & config . server ) . http_form ( " PUT " , " /user/endpoint " , contents ) ? ;
2021-11-16 09:46:45 +00:00
log ::info! (
" endpoint override {} " ,
if sub_opts . unset { " unset " } else { " set " }
) ;
2021-03-29 17:22:14 +00:00
} else {
2021-11-16 09:46:45 +00:00
log ::info! ( " exiting without overriding endpoint " ) ;
2021-03-29 17:22:14 +00:00
}
Ok ( ( ) )
}
2021-11-15 09:11:13 +00:00
fn show ( opts : & Opts , short : bool , tree : bool , interface : Option < Interface > ) -> Result < ( ) , Error > {
2021-05-19 07:54:07 +00:00
let interfaces = interface . map_or_else (
2021-11-15 09:11:13 +00:00
| | Device ::list ( opts . network . backend ) ,
2021-05-19 07:54:07 +00:00
| 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-11-15 09:11:13 +00:00
match DataStore ::open ( & opts . data_dir , & name ) {
2021-06-10 13:57:47 +00:00
Ok ( store ) = > {
2021-11-15 09:11:13 +00:00
let device =
Device ::get ( & name , opts . network . backend ) . with_str ( name . as_str_lossy ( ) ) ;
2021-06-10 13:57:47 +00:00
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 {
2022-01-07 09:35:21 +00:00
let public_key = match & device_info . public_key {
Some ( key ) = > key . to_base64 ( ) ,
None = > {
log ::warn! (
" network {} is missing public key. " ,
device_info . name . to_string ( ) . yellow ( )
) ;
continue ;
} ,
} ;
2021-03-29 17:22:14 +00:00
let peers = store . peers ( ) ;
let cidrs = store . cidrs ( ) ;
let me = peers
. iter ( )
2022-01-07 09:35:21 +00:00
. find ( | p | p . public_key = = public_key )
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
2021-09-14 13:33:49 +00:00
. map ( | info | info . is_recently_connected ( ) )
2021-09-01 09:58:46 +00:00
. unwrap_or_default ( ) ;
2021-05-09 15:03:00 +00:00
2021-09-14 13:49:00 +00:00
let is_you = info . is_none ( ) ;
2021-05-09 15:03:00 +00:00
println_pad! (
pad ,
" | {} {}: {} ({}{}…) " ,
2021-09-14 13:49:00 +00:00
if connected | | is_you {
2021-08-05 00:38:14 +00:00
" ◉ " . bold ( )
} else {
" ◯ " . dimmed ( )
} ,
2021-05-09 15:03:00 +00:00
peer . ip . to_string ( ) . yellow ( ) . bold ( ) ,
peer . name . yellow ( ) ,
2021-09-14 13:49:00 +00:00
if is_you { " you, " } else { " " } ,
2021-05-09 15:03:00 +00:00
& 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 ( info ) = info {
2021-09-14 14:12:12 +00:00
if let Some ( endpoint ) = info . config . endpoint {
2021-09-15 12:18:04 +00:00
println_pad! ( pad , " {}: {} " , " endpoint " . bold ( ) , endpoint ) ;
2021-09-14 14:12:12 +00:00
}
2021-05-09 15:03:00 +00:00
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 ( ) {
2022-01-11 07:51:32 +00:00
let opts = Opts ::parse ( ) ;
2021-11-15 09:11:13 +00:00
util ::init_logger ( opts . verbose ) ;
2021-03-29 17:22:14 +00:00
2021-11-29 19:16:44 +00:00
let argv0 = std ::env ::args ( ) . next ( ) . unwrap ( ) ;
let executable = Path ::new ( & argv0 ) . file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
if executable = = " inn " {
log ::warn! ( " " ) ;
log ::warn! ( " {}: the {} shortcut will be removed from OS packages soon in favor of users creating a shell alias. " , " WARNING " . bold ( ) , " inn " . yellow ( ) ) ;
log ::warn! ( " " ) ;
log ::warn! ( " See https://github.com/tonarino/innernet/issues/176 for instructions to continue using it. " ) ;
log ::warn! ( " " ) ;
}
2021-11-15 09:11:13 +00:00
if let Err ( e ) = run ( & opts ) {
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 > ( ) {
2021-11-15 09:11:13 +00:00
util ::permissions_helptext ( & opts . config_dir , & opts . data_dir , e ) ;
2021-06-10 13:57:47 +00:00
}
2021-06-16 11:22:28 +00:00
if let Some ( e ) = e . downcast_ref ::< io ::Error > ( ) {
2021-11-15 09:11:13 +00:00
util ::permissions_helptext ( & opts . config_dir , & opts . data_dir , e ) ;
2021-06-16 11:22:28 +00:00
}
2021-03-29 17:22:14 +00:00
std ::process ::exit ( 1 ) ;
}
}
2021-11-15 09:11:13 +00:00
fn run ( opts : & Opts ) -> Result < ( ) , Error > {
let command = opts . command . clone ( ) . unwrap_or ( Command ::Show {
2021-03-29 17:22:14 +00:00
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 ,
2021-11-15 09:11:13 +00:00
install_opts ,
2021-11-12 05:42:10 +00:00
nat ,
2021-11-15 09:11:13 +00:00
} = > install ( opts , & invite , hosts . into ( ) , install_opts , & nat ) ? ,
2021-03-29 17:22:14 +00:00
Command ::Show {
short ,
tree ,
interface ,
2021-11-15 09:11:13 +00:00
} = > show ( opts , short , tree , interface ) ? ,
2021-11-12 05:42:10 +00:00
Command ::Fetch {
interface ,
hosts ,
nat ,
2021-11-15 09:11:13 +00:00
} = > fetch ( & interface , opts , false , hosts . into ( ) , & nat ) ? ,
2021-03-29 17:22:14 +00:00
Command ::Up {
interface ,
daemon ,
2021-04-07 08:00:52 +00:00
hosts ,
2021-11-12 05:42:10 +00:00
nat ,
2021-03-29 17:22:14 +00:00
interval ,
2021-04-07 08:00:52 +00:00
} = > up (
2021-11-24 06:07:57 +00:00
interface ,
2021-11-15 09:11:13 +00:00
opts ,
2021-04-07 08:00:52 +00:00
daemon . then ( | | Duration ::from_secs ( interval ) ) ,
hosts . into ( ) ,
2021-11-12 05:42:10 +00:00
& nat ,
2021-04-07 08:00:52 +00:00
) ? ,
2021-11-15 09:11:13 +00:00
Command ::Down { interface } = > wg ::down ( & interface , opts . network . backend ) ? ,
2022-01-31 06:10:45 +00:00
Command ::Uninstall { interface , yes } = > uninstall ( & interface , opts , yes ) ? ,
2021-11-15 09:11:13 +00:00
Command ::AddPeer {
interface ,
sub_opts ,
} = > add_peer ( & interface , opts , sub_opts ) ? ,
Command ::RenamePeer {
interface ,
sub_opts ,
} = > rename_peer ( & interface , opts , sub_opts ) ? ,
Command ::AddCidr {
interface ,
sub_opts ,
} = > add_cidr ( & interface , opts , sub_opts ) ? ,
Command ::DeleteCidr {
interface ,
sub_opts ,
} = > delete_cidr ( & interface , opts , sub_opts ) ? ,
Command ::ListCidrs { interface , tree } = > list_cidrs ( & interface , opts , tree ) ? ,
Command ::DisablePeer { interface } = > enable_or_disable_peer ( & interface , opts , false ) ? ,
Command ::EnablePeer { interface } = > enable_or_disable_peer ( & interface , opts , true ) ? ,
Command ::AddAssociation {
interface ,
sub_opts ,
} = > add_association ( & interface , opts , sub_opts ) ? ,
2022-01-31 06:10:45 +00:00
Command ::DeleteAssociation {
interface ,
sub_opts ,
} = > delete_association ( & interface , opts , sub_opts ) ? ,
2021-11-15 09:11:13 +00:00
Command ::ListAssociations { interface } = > list_associations ( & interface , opts ) ? ,
2021-11-16 09:46:45 +00:00
Command ::SetListenPort {
interface ,
sub_opts ,
} = > {
set_listen_port ( & interface , opts , sub_opts ) ? ;
2021-05-19 07:54:07 +00:00
} ,
2021-11-16 09:46:45 +00:00
Command ::OverrideEndpoint {
interface ,
sub_opts ,
} = > {
override_endpoint ( & interface , opts , sub_opts ) ? ;
2021-05-19 07:54:07 +00:00
} ,
2021-05-25 07:10:16 +00:00
Command ::Completions { shell } = > {
2022-02-17 00:53:59 +00:00
let mut app = Opts ::command ( ) ;
2022-01-11 07:51:32 +00:00
let app_name = app . get_name ( ) . to_string ( ) ;
clap_complete ::generate ( shell , & mut app , app_name , & mut std ::io ::stdout ( ) ) ;
2021-05-25 07:10:16 +00:00
std ::process ::exit ( 0 ) ;
} ,
2021-03-29 17:22:14 +00:00
}
Ok ( ( ) )
}