diff --git a/core/zerotier.h b/core/zerotier.h index b36367251..67cffad86 100644 --- a/core/zerotier.h +++ b/core/zerotier.h @@ -33,10 +33,15 @@ extern "C" { /* ---------------------------------------------------------------------------------------------------------------- */ /** - * Default primary UDP port for devices running a ZeroTier endpoint + * Default primary UDP port */ #define ZT_DEFAULT_PORT 9993 +/** + * Default secondary UDP port (if enabled) + */ +#define ZT_DEFAULT_SECONDARY_PORT 293 + /** * IP protocol number for naked IP encapsulation (this is not currently used) */ diff --git a/rust-zerotier-core/src/lib.rs b/rust-zerotier-core/src/lib.rs index 138ff9199..11e115b62 100644 --- a/rust-zerotier-core/src/lib.rs +++ b/rust-zerotier-core/src/lib.rs @@ -53,14 +53,17 @@ pub use portableatomici64::PortableAtomicI64; pub use virtualnetworkconfig::*; pub use multicastgroup::MulticastGroup; -/// Recommended minimum thread stack size for ZeroTier threads. +/// Recommended minimum thread stack size for background threads. pub const RECOMMENDED_THREAD_STACK_SIZE: usize = 262144; -/// Default TCP and UDP port for ZeroTier. +/// Default TCP and UDP port. pub const DEFAULT_PORT: u16 = ztcore::ZT_DEFAULT_PORT as u16; +/// Default secondary UDP port if enabled. +pub const DEFAULT_SECONDARY_PORT: u16 = ztcore::ZT_DEFAULT_SECONDARY_PORT as u16; + /// Size of a ZeroTier core "Buffer" in bytes. -pub const BUF_SIZE: u32 = ztcore::ZT_BUF_SIZE; +pub const BUF_SIZE: usize = ztcore::ZT_BUF_SIZE as usize; /// Minimum physical MTU. pub const MIN_MTU: u32 = ztcore::ZT_MIN_MTU; diff --git a/rust-zerotier-service/Cargo.lock b/rust-zerotier-service/Cargo.lock index 783d3c7aa..47acb0a1d 100644 --- a/rust-zerotier-service/Cargo.lock +++ b/rust-zerotier-service/Cargo.lock @@ -131,6 +131,7 @@ dependencies = [ "atty", "bitflags", "strsim", + "term_size", "textwrap", "unicode-width", "vec_map", @@ -1037,12 +1038,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ + "term_size", "unicode-width", ] diff --git a/rust-zerotier-service/Cargo.toml b/rust-zerotier-service/Cargo.toml index 1013eb77f..1b4d02af9 100644 --- a/rust-zerotier-service/Cargo.toml +++ b/rust-zerotier-service/Cargo.toml @@ -9,18 +9,18 @@ build = "build.rs" [dependencies] zerotier-core = { path = "../rust-zerotier-core" } -num_cpus = "1.13" +num_cpus = "1" tokio = { version = "1", features = ["full"] } -warp = "0.3" +warp = "0" serde = { version = "1", features = ["derive"] } serde_json = "1" -futures = "0.3" -clap = { version = "2", features = ["default"] } -chrono = "0.4" -hex = "0.4" +futures = "0" +clap = { version = "2", features = ["suggestions", "wrap_help"] } +chrono = "0" +hex = "0" lazy_static = "1" -num-traits = "0.2" -num-derive = "0.3" +num-traits = "0" +num-derive = "0" [target."cfg(windows)".dependencies] winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } diff --git a/rust-zerotier-service/src/cli.rs b/rust-zerotier-service/src/cli.rs new file mode 100644 index 000000000..6d2b71142 --- /dev/null +++ b/rust-zerotier-service/src/cli.rs @@ -0,0 +1,259 @@ +use std::str::FromStr; +use clap::{App, Arg, ArgMatches, ErrorKind}; + +fn make_help() -> String { + let ver = zerotier_core::version(); + format!(r###"ZeroTier Network Hypervisor Service Version {}.{}.{} +(c)2013-2021 ZeroTier, Inc. +Licensed under the ZeroTier BSL (see LICENSE.txt) + +Usage: zerotier [-global options] [command args] + +Global Options: + + -j Output raw JSON where applicable + -p Use alternate base path + -t Load secret auth token from a file + -T Set secret auth token on command line + +Common Operations: + + help Show this help + version Print version + +· status Show node status and configuration + +· set [setting] [value] List all settings (with no args) +· port Primary P2P port +· secondaryport Secondary P2P port (0 to disable) +· blacklist cidr Toggle physical path blacklisting +· blacklist if Toggle interface prefix blacklisting +· portmap Toggle use of uPnP or NAT-PMP + +· peer [option] +· show
Show detailed peer information +· list List peers +· listroots List root peers +· try
[...] Try peer at explicit endpoint + +· network [option] +· show Show detailed network information +· list List networks +· set [option] [value] Get or set network options +· manageips Is IP management allowed? +· manageroutes Is route management allowed? +· managedns Allow network to push DNS config +· globalips Allow assignment of global IPs? +· globalroutes Can global IP space routes be set? +· defaultroute Can default route be overridden? + +· join [-options] Join a virtual network + -t Token to submit to controller + -c Controller identity or fingerprint +· leave Leave a virtual network + +Advanced Operations: + + service Start this node (runs until stopped) + + controller [option] +· list List networks on controller +· new Create a new network +· set [setting] [value] Show or modify network settings +· show [
] Show network or member status +· auth
Authorize a peer +· deauth
Deauthorize a peer + + identity [args] + new [c25519 | p384] Create identity (default: c25519) + getpublic Extract only public part of identity + fingerprint Get an identity's fingerprint + validate Locally validate an identity + sign Sign a file with an identity's key + verify Verify a signature + + locator [args] + new [...] Create new signed locator + verify Verify locator signature + show Show contents of a locator + + cert [args] +· list List certificates at local node +· show Show certificate details + newsid Create a new subject unique ID + newcsr Create a subject CSR + sign Sign a CSR to create a certificate + verify Verify certificate (not chain) + dump Verify and print certificate +· import [trust] [...] Import certificate into this node + trust flag: rootca Certificate is a root CA + trust flag: ztrootset ZeroTier root node set +· restore Re-import default certificates +· export [path] Export a certificate from this node +· delete Delete certificate from this node + + · Command requires a running node and access to a local API token. + +An
may be specified as a 10-digit short ZeroTier address, a +fingerprint containing both an address and a SHA384 hash, or an identity. +The latter two options are equivalent in terms of specificity and may be +used if stronger security guarantees are desired than those provided by +the basic ZeroTier addressing system. Fields of type must be +full identities and may be specified either verbatim or as a path to a file. + +An is a place where a peer may be reached. Currently these are +just 'IP/port' format addresses but other types may be added in the future. + +The 'service' command starts a node. It will run until the node receives +an exit signal and is normally not used directly."###, ver.0, ver.1, ver.2) +} + +pub(crate) fn print_help() { + println!("{}", make_help()); +} + +fn is_bool(v: String) -> Result<(), String> { + if !v.is_empty() { + match v.chars().next().unwrap() { + 'y' | 'Y' | '1' | 't' | 'T' | 'n' | 'N' | '0' | 'f' | 'F' => { return Ok(()); } + _ => {} + } + } + Err(format!("invalid boolean value: '{}'", v)) +} + +fn is_valid_port(v: String) -> Result<(), String> { + let i = u16::from_str(v.as_str()).unwrap_or(0); + if i >= 1 { + return Ok(()); + } + Err(format!("invalid TCP/IP port number: {}", v)) +} + +/// Parses CLI arguments, prints error and exits on failure. +pub(crate) fn parse_cli_args() -> ArgMatches<'static> { + let help = make_help(); + let args = App::new("zerotier") + .arg(Arg::with_name("json").short("j")) + .arg(Arg::with_name("path").short("p").takes_value(true)) + .arg(Arg::with_name("token_path").short("t").takes_value(true)) + .arg(Arg::with_name("token").short("T").takes_value(true)) + .subcommand(App::new("help")) + .subcommand(App::new("version")) + .subcommand(App::new("status")) + .subcommand(App::new("set") + .subcommand(App::new("port") + .arg(Arg::with_name("port#").index(1).validator(is_valid_port))) + .subcommand(App::new("secondaryport") + .arg(Arg::with_name("port#").index(1).validator(is_valid_port))) + .subcommand(App::new("blacklist") + .subcommand(App::new("cidr") + .arg(Arg::with_name("ip/bits").index(1)) + .arg(Arg::with_name("boolean").index(2).validator(is_bool))) + .subcommand(App::new("if") + .arg(Arg::with_name("prefix").index(1)) + .arg(Arg::with_name("boolean").index(2).validator(is_bool)))) + .subcommand(App::new("portmap") + .arg(Arg::with_name("boolean").index(1).validator(is_bool)))) + .subcommand(App::new("peer") + .subcommand(App::new("show") + .arg(Arg::with_name("address").index(1).required(true))) + .subcommand(App::new("list")) + .subcommand(App::new("listroots")) + .subcommand(App::new("try"))) + .subcommand(App::new("network") + .subcommand(App::new("show")) + .subcommand(App::new("list")) + .subcommand(App::new("set"))) + .subcommand(App::new("join") + .arg(Arg::with_name("token").short("t").takes_value(true)) + .arg(Arg::with_name("controller").short("c").takes_value(true)) + .arg(Arg::with_name("id").index(1).required(true))) + .subcommand(App::new("leave") + .arg(Arg::with_name("id").index(1).required(true))) + .subcommand(App::new("service")) + .subcommand(App::new("controller") + .subcommand(App::new("list")) + .subcommand(App::new("new")) + .subcommand(App::new("set") + .arg(Arg::with_name("id").index(1).required(true)) + .arg(Arg::with_name("setting").index(2)) + .arg(Arg::with_name("value").index(3))) + .subcommand(App::new("show") + .arg(Arg::with_name("id").index(1).required(true)) + .arg(Arg::with_name("member").index(2))) + .subcommand(App::new("auth") + .arg(Arg::with_name("member").index(1).required(true))) + .subcommand(App::new("deauth") + .arg(Arg::with_name("member").index(1).required(true)))) + .subcommand(App::new("identity") + .subcommand(App::new("new") + .arg(Arg::with_name("type").possible_value("p384").possible_value("c25519").index(1))) + .subcommand(App::new("getpublic") + .arg(Arg::with_name("identity").index(1).required(true))) + .subcommand(App::new("fingerprint") + .arg(Arg::with_name("identity").index(1).required(true))) + .subcommand(App::new("validate") + .arg(Arg::with_name("identity").index(1).required(true))) + .subcommand(App::new("sign") + .arg(Arg::with_name("identity").index(1).required(true)) + .arg(Arg::with_name("file").index(2).required(true))) + .subcommand(App::new("verify") + .arg(Arg::with_name("identity").index(1).required(true)) + .arg(Arg::with_name("file").index(2).required(true)) + .arg(Arg::with_name("signature").index(3).required(true)))) + .subcommand(App::new("locator") + .subcommand(App::new("new") + .arg(Arg::with_name("identity").index(1).required(true)) + .arg(Arg::with_name("endpoint").index(2).multiple(true).required(true))) + .subcommand(App::new("verify") + .arg(Arg::with_name("identity").index(1).required(true)) + .arg(Arg::with_name("locator").index(2).required(true))) + .subcommand(App::new("show") + .arg(Arg::with_name("locator").index(1).required(true)))) + .subcommand(App::new("cert") + .subcommand(App::new("list")) + .subcommand(App::new("show") + .arg(Arg::with_name("serial").index(1).required(true))) + .subcommand(App::new("newsid") + .arg(Arg::with_name("path").index(1).required(true))) + .subcommand(App::new("newcsr") + .arg(Arg::with_name("subject").index(1).required(true)) + .arg(Arg::with_name("secret").index(2).required(true)) + .arg(Arg::with_name("output").index(3).required(true))) + .subcommand(App::new("sign") + .arg(Arg::with_name("csr").index(1).required(true)) + .arg(Arg::with_name("identity").index(2).required(true)) + .arg(Arg::with_name("output").index(3).required(true))) + .subcommand(App::new("verify") + .arg(Arg::with_name("cert").index(1).required(true))) + .subcommand(App::new("dump") + .arg(Arg::with_name("cert").index(1).required(true))) + .subcommand(App::new("import") + .arg(Arg::with_name("cert").index(1).required(true)) + .arg(Arg::with_name("trust").possible_value("rootca").possible_value("ztrootset").index(2).multiple(true))) + .subcommand(App::new("restore")) + .subcommand(App::new("export") + .arg(Arg::with_name("serial").index(1).required(true)) + .arg(Arg::with_name("path").index(2))) + .subcommand(App::new("delete") + .arg(Arg::with_name("serial").index(1).required(true)))) + .help(help.as_str()) + .get_matches_from_safe(std::env::args()); + + if args.is_err() { + let e = args.err().unwrap(); + match e.kind { + ErrorKind::HelpDisplayed => {} + _ => { print_help(); } + } + std::process::exit(1); + } + let args = args.unwrap(); + if args.subcommand_name().is_none() { + print_help(); + std::process::exit(1); + } + + args +} diff --git a/rust-zerotier-service/src/localconfig.rs b/rust-zerotier-service/src/localconfig.rs index 6e29e32a4..219a64832 100644 --- a/rust-zerotier-service/src/localconfig.rs +++ b/rust-zerotier-service/src/localconfig.rs @@ -61,20 +61,20 @@ pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [ 1023, ]; -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct LocalConfigPhysicalPathConfig { pub blacklist: bool } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct LocalConfigVirtualConfig { #[serde(rename = "try")] pub try_: Vec } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct LocalConfigNetworkSettings { #[serde(rename = "allowManagedIPs")] @@ -89,7 +89,7 @@ pub struct LocalConfigNetworkSettings { pub allow_default_route_override: bool, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct LocalConfigSettings { #[serde(rename = "primaryPort")] @@ -118,7 +118,7 @@ pub struct LocalConfigSettings { pub explicit_addresses: Vec, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct LocalConfig { pub physical: BTreeMap, @@ -163,7 +163,6 @@ impl LocalConfigSettings { #[cfg(target_os = "linux")] const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"]; - // This is not applicable on Windows as it doesn't use the same device name semantics. #[cfg(windows)] const DEFAULT_PREFIX_BLACKLIST: [&'static str; 0] = []; @@ -187,7 +186,7 @@ impl Default for LocalConfigSettings { LocalConfigSettings { primary_port: zerotier_core::DEFAULT_PORT, - secondary_port: Some(293), // this is one of UNASSIGNED_PRIVILEGED_PORTS that we will default to + secondary_port: Some(zerotier_core::DEFAULT_SECONDARY_PORT), auto_port_search: true, port_mapping: true, log_size_max: 1048576, @@ -212,3 +211,29 @@ impl Default for LocalConfig { } } } + +zerotier_core::implement_to_from_json!(LocalConfig); + +impl LocalConfig { + pub fn load(path: &String) -> std::io::Result { + let md = std::path::Path::new(path).metadata(); + if md.is_err() { + return Err(md.err().unwrap()); + } + if md.unwrap().len() > 1048576 { + } + let json = std::fs::read_to_string(path); + if json.is_err() { + return Err(json.err().unwrap()); + } + let json = Self::new_from_json(json.unwrap().as_str()); + if json.is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, json.err().unwrap().to_string())); + } + Ok(json.unwrap()) + } + + pub fn save(&self, path: &String) -> std::io::Result<()> { + std::fs::write(path, self.to_json()) + } +} diff --git a/rust-zerotier-service/src/main.rs b/rust-zerotier-service/src/main.rs index f6a7735f3..37137802e 100644 --- a/rust-zerotier-service/src/main.rs +++ b/rust-zerotier-service/src/main.rs @@ -11,6 +11,7 @@ */ /****/ +mod cli; mod fastudpsocket; mod localconfig; mod physicallink; @@ -70,6 +71,7 @@ impl NodeEventHandler for ServiceEventHandler { 0 } + #[inline(always)] fn path_check(&self, address: Address, id: &Identity, local_socket: i64, sock_addr: &InetAddress) -> bool { true } @@ -80,6 +82,8 @@ impl NodeEventHandler for ServiceEventHandler { } fn main() { + let cli_args = Some(cli::parse_cli_args()); + let inaddr_v6_any = IpAddr::from_str("::0").unwrap(); let mut process_exit_value: i32 = 0; @@ -137,7 +141,7 @@ fn main() { .or(status) .or(network) .or(peer) - )).try_bind_with_graceful_shutdown((inaddr_v6_any, warp_server_port), async { shutdown_rx.await; }); + )).try_bind_with_graceful_shutdown((inaddr_v6_any, warp_server_port), async { let _ = shutdown_rx.await; }); if warp_server.is_err() { // TODO: log unable to bind to primary port run.store(false, Ordering::Relaxed); diff --git a/rust-zerotier-service/src/vnic/mac_feth_tap.rs b/rust-zerotier-service/src/vnic/mac_feth_tap.rs index 45734cf50..a087e3512 100644 --- a/rust-zerotier-service/src/vnic/mac_feth_tap.rs +++ b/rust-zerotier-service/src/vnic/mac_feth_tap.rs @@ -101,7 +101,7 @@ fn device_ipv6_set_params(device: &String, perform_nud: bool, accept_ra: bool) - let mut nd: osdep::in6_ndireq = zeroed(); copy_nonoverlapping(dev.as_ptr(), nd.ifname.as_mut_ptr().cast::(), if dev.len() > (nd.ifname.len() - 1) { nd.ifname.len() - 1 } else { dev.len() }); - if osdep::ioctl(s, osdep::c_SIOCGIFINFO_IN6, (&nd as *mut osdep::in6_ndireq).cast()) == 0 { + if osdep::ioctl(s, osdep::c_SIOCGIFINFO_IN6, (&mut nd as *mut osdep::in6_ndireq).cast::()) == 0 { let oldflags = nd.ndi.flags; if perform_nud { nd.ndi.flags |= osdep::ND6_IFF_PERFORMNUD as osdep::u_int32_t; @@ -109,7 +109,7 @@ fn device_ipv6_set_params(device: &String, perform_nud: bool, accept_ra: bool) - nd.ndi.flags &= !(osdep::ND6_IFF_PERFORMNUD as osdep::u_int32_t); } if nd.ndi.flags != oldflags { - if osdep::ioctl(s, osdep::c_SIOCSIFINFO_FLAGS, (&nd as *mut osdep::in6_ndireq).cast()) != 0 { + if osdep::ioctl(s, osdep::c_SIOCSIFINFO_FLAGS, (&mut nd as *mut osdep::in6_ndireq).cast::()) != 0 { ok = false; } } @@ -119,7 +119,7 @@ fn device_ipv6_set_params(device: &String, perform_nud: bool, accept_ra: bool) - let mut ifr: osdep::in6_ifreq = zeroed(); copy_nonoverlapping(dev.as_ptr(), ifr.ifr_name.as_mut_ptr().cast::(), if dev.len() > (ifr.ifr_name.len() - 1) { ifr.ifr_name.len() - 1 } else { dev.len() }); - if osdep::ioctl(s, if accept_ra { osdep::c_SIOCAUTOCONF_START } else { osdep::c_SIOCAUTOCONF_STOP }, (&ifr as *mut osdep::in6_ifreq).cast()) != 0 { + if osdep::ioctl(s, if accept_ra { osdep::c_SIOCAUTOCONF_START } else { osdep::c_SIOCAUTOCONF_STOP }, (&mut ifr as *mut osdep::in6_ifreq).cast::()) != 0 { ok = false; }