diff --git a/Cargo.lock b/Cargo.lock index 1186f12..bf286f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,7 +137,7 @@ dependencies = [ "dialoguer", "hostsfile", "indoc", - "ipnetwork", + "ipnet", "lazy_static", "log", "regex", @@ -470,9 +470,10 @@ dependencies = [ ] [[package]] -name = "ipnetwork" -version = "0.17.0" -source = "git+https://github.com/mcginty/ipnetwork?rev=393f2d89e41ac6c1c0d80a31fc0997c387a7f7ba#393f2d89e41ac6c1c0d80a31fc0997c387a7f7ba" +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" dependencies = [ "serde", ] @@ -933,7 +934,7 @@ dependencies = [ "dialoguer", "hyper", "indoc", - "ipnetwork", + "ipnet", "lazy_static", "libc", "libsqlite3-sys", @@ -966,7 +967,7 @@ dependencies = [ "colored", "dialoguer", "indoc", - "ipnetwork", + "ipnet", "lazy_static", "libc", "log", diff --git a/client/Cargo.toml b/client/Cargo.toml index 9eb7376..ed47e5d 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -21,7 +21,7 @@ clap_complete = "3" dialoguer = { version = "0.9", default-features = false } hostsfile = { path = "../hostsfile" } indoc = "1" -ipnetwork = { git = "https://github.com/mcginty/ipnetwork", rev = "393f2d89e41ac6c1c0d80a31fc0997c387a7f7ba" } +ipnet = { version = "2", features = ["serde"] } lazy_static = "1" log = "0.4" regex = { version = "1", default-features = false, features = ["std"] } diff --git a/server/Cargo.toml b/server/Cargo.toml index db50e32..e27012a 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -24,7 +24,7 @@ colored = "2" dialoguer = { version = "0.9", default-features = false } hyper = { version = "0.14", default-features = false, features = ["http1", "server", "runtime", "stream"] } indoc = "1" -ipnetwork = { git = "https://github.com/mcginty/ipnetwork", rev = "393f2d89e41ac6c1c0d80a31fc0997c387a7f7ba" } +ipnet = { version = "2", features = ["serde"] } lazy_static = "1" libc = "0.2" libsqlite3-sys = "0.23" diff --git a/server/src/db/cidr.rs b/server/src/db/cidr.rs index fee9ee0..cbbdf78 100644 --- a/server/src/db/cidr.rs +++ b/server/src/db/cidr.rs @@ -1,7 +1,7 @@ use crate::ServerError; -use ipnetwork::IpNetwork; +use ipnet::IpNet; use rusqlite::{params, Connection}; -use shared::{Cidr, CidrContents}; +use shared::{Cidr, CidrContents, IpNetExt}; use std::ops::Deref; pub static CREATE_TABLE_SQL: &str = "CREATE TABLE cidrs ( @@ -56,8 +56,8 @@ impl DatabaseCidr { let closest_parent = cidrs .iter() - .filter(|current| cidr.is_subnet_of(current.cidr)) - .max_by_key(|current| current.cidr.prefix()); + .filter(|current| current.cidr.contains(cidr)) + .max_by_key(|current| current.cidr.prefix_len()); if let Some(closest_parent) = closest_parent { if closest_parent.id != *parent_id { @@ -70,7 +70,7 @@ impl DatabaseCidr { } let parent_cidr = Self::get(conn, *parent_id)?.cidr; - if !parent_cidr.contains(cidr.network()) || !parent_cidr.contains(cidr.broadcast()) { + if !parent_cidr.contains(&cidr.network()) || !parent_cidr.contains(&cidr.broadcast()) { log::warn!("tried to add a CIDR with a network range outside of its parent."); return Err(ServerError::InvalidQuery); } @@ -81,10 +81,10 @@ impl DatabaseCidr { .filter(|current| current.parent == *parent) .map(|sibling| sibling.cidr) .any(|sibling| { - cidr.contains(sibling.network()) - || cidr.contains(sibling.broadcast()) - || sibling.contains(cidr.network()) - || sibling.contains(cidr.broadcast()) + cidr.contains(&sibling.network()) + || cidr.contains(&sibling.broadcast()) + || sibling.contains(&cidr.network()) + || sibling.contains(&cidr.broadcast()) }); if overlapping_sibling { @@ -95,7 +95,12 @@ impl DatabaseCidr { conn.execute( "INSERT INTO cidrs (name, ip, prefix, parent) VALUES (?1, ?2, ?3, ?4)", - params![name, cidr.ip().to_string(), cidr.prefix() as i32, parent], + params![ + name, + cidr.addr().to_string(), + cidr.prefix_len() as i32, + parent + ], )?; let id = conn.last_insert_rowid(); Ok(Cidr { id, contents }) @@ -109,14 +114,12 @@ impl DatabaseCidr { fn from_row(row: &rusqlite::Row) -> Result { let id = row.get(0)?; let name = row.get(1)?; - let ip: String = row.get(2)?; + let ip_str: String = row.get(2)?; let prefix = row.get(3)?; - let cidr = IpNetwork::new( - ip.parse() - .map_err(|_| rusqlite::Error::ExecuteReturnedResults)?, - prefix, - ) - .map_err(|_| rusqlite::Error::ExecuteReturnedResults)?; + let ip = ip_str + .parse() + .map_err(|_| rusqlite::Error::ExecuteReturnedResults)?; + let cidr = IpNet::new(ip, prefix).map_err(|_| rusqlite::Error::ExecuteReturnedResults)?; let parent = row.get(4)?; Ok(Cidr { id, diff --git a/server/src/db/peer.rs b/server/src/db/peer.rs index 87e72f4..6b1c1ab 100644 --- a/server/src/db/peer.rs +++ b/server/src/db/peer.rs @@ -3,7 +3,7 @@ use crate::ServerError; use lazy_static::lazy_static; use regex::Regex; use rusqlite::{params, types::Type, Connection}; -use shared::{Peer, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS}; +use shared::{IpNetExt, Peer, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS}; use std::{ net::IpAddr, ops::{Deref, DerefMut}, @@ -96,12 +96,12 @@ impl DatabasePeer { } let cidr = DatabaseCidr::get(conn, *cidr_id)?; - if !cidr.cidr.contains(*ip) { + if !cidr.cidr.contains(ip) { log::warn!("tried to add peer with IP outside of parent CIDR range."); return Err(ServerError::InvalidQuery); } - if !cidr.cidr.is_assignable(*ip) { + if !cidr.cidr.is_assignable(ip) { println!( "Peer IP {} is not unicast assignable in CIDR {}", ip, cidr.cidr diff --git a/server/src/initialize.rs b/server/src/initialize.rs index e888466..f143c20 100644 --- a/server/src/initialize.rs +++ b/server/src/initialize.rs @@ -4,9 +4,12 @@ use clap::Parser; use db::DatabaseCidr; use dialoguer::{theme::ColorfulTheme, Input}; use indoc::printdoc; +use ipnet::IpNet; use publicip::Preference; use rusqlite::{params, Connection}; -use shared::{prompts, CidrContents, Endpoint, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS}; +use shared::{ + prompts, CidrContents, Endpoint, IpNetExt, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS, +}; use wireguard_control::KeyPair; fn create_database>( @@ -31,7 +34,7 @@ pub struct InitializeOpts { /// The network CIDR (ex: 10.42.0.0/16) #[clap(long)] - pub network_cidr: Option, + pub network_cidr: Option, /// This server's external endpoint (ex: 100.100.100.100:51820) #[clap(long, conflicts_with = "auto-external-endpoint")] @@ -48,8 +51,8 @@ pub struct InitializeOpts { struct DbInitData { network_name: String, - network_cidr: IpNetwork, - server_cidr: IpNetwork, + network_cidr: IpNet, + server_cidr: IpNet, our_ip: IpAddr, public_key_base64: String, endpoint: Endpoint, @@ -131,7 +134,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro .interact()? }; - let root_cidr: IpNetwork = if let Some(cidr) = opts.network_cidr { + let root_cidr: IpNet = if let Some(cidr) = opts.network_cidr { cidr } else { Input::with_theme(&theme) @@ -163,10 +166,9 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro }; let our_ip = root_cidr - .iter() - .find(|ip| root_cidr.is_assignable(*ip)) + .hosts() + .find(|ip| root_cidr.is_assignable(ip)) .unwrap(); - let server_cidr = IpNetwork::new(our_ip, root_cidr.max_prefix())?; let config_path = conf.config_path(&name); let our_keypair = KeyPair::generate(); @@ -174,14 +176,14 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro private_key: our_keypair.private.to_base64(), listen_port, address: our_ip, - network_cidr_prefix: root_cidr.prefix(), + network_cidr_prefix: root_cidr.prefix_len(), }; config.write_to_path(&config_path)?; let db_init_data = DbInitData { network_name: name.to_string(), network_cidr: root_cidr, - server_cidr, + server_cidr: IpNet::new(our_ip, root_cidr.max_prefix_len())?, our_ip, public_key_base64: our_keypair.public.to_base64(), endpoint, diff --git a/server/src/main.rs b/server/src/main.rs index 6aebcd7..0e06b98 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,12 +4,12 @@ use colored::*; use dialoguer::Confirm; use hyper::{http, server::conn::AddrStream, Body, Request, Response}; use indoc::printdoc; -use ipnetwork::IpNetwork; +use ipnet::IpNet; use parking_lot::{Mutex, RwLock}; use rusqlite::Connection; use serde::{Deserialize, Serialize}; use shared::{ - get_local_addrs, AddCidrOpts, AddPeerOpts, DeleteCidrOpts, Endpoint, IoErrorContext, + get_local_addrs, AddCidrOpts, AddPeerOpts, DeleteCidrOpts, Endpoint, IoErrorContext, IpNetExt, NetworkOpts, PeerContents, RenamePeerOpts, INNERNET_PUBKEY_HEADER, }; use std::{ @@ -502,7 +502,7 @@ async fn serve( wg::up( &interface, &config.private_key, - IpNetwork::new(config.address, config.network_cidr_prefix)?, + IpNet::new(config.address, config.network_cidr_prefix)?, Some(config.listen_port), None, network, diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 91c057e..27b661d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -13,7 +13,7 @@ clap = { version = "3", features = ["derive"] } colored = "2.0" dialoguer = { version = "0.9", default-features = false } indoc = "1" -ipnetwork = { git = "https://github.com/mcginty/ipnetwork", rev ="393f2d89e41ac6c1c0d80a31fc0997c387a7f7ba" } +ipnet = { version = "2", features = ["serde"] } lazy_static = "1" libc = "0.2" log = "0.4" diff --git a/shared/src/interface_config.rs b/shared/src/interface_config.rs index f772ddf..55c20d1 100644 --- a/shared/src/interface_config.rs +++ b/shared/src/interface_config.rs @@ -1,6 +1,6 @@ use crate::{chmod, ensure_dirs_exist, Endpoint, Error, IoErrorContext, WrappedIoError}; use indoc::writedoc; -use ipnetwork::IpNetwork; +use ipnet::IpNet; use serde::{Deserialize, Serialize}; use std::{ fs::{File, OpenOptions}, @@ -28,7 +28,7 @@ pub struct InterfaceInfo { /// The invited peer's internal IP address that's been allocated to it, inside /// the entire network's CIDR prefix. - pub address: IpNetwork, + pub address: IpNet, /// WireGuard private key (base64) pub private_key: String, diff --git a/shared/src/lib.rs b/shared/src/lib.rs index bddef99..8ff00a3 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -1,4 +1,5 @@ pub use anyhow::Error; +use ipnet::{IpNet, Ipv4Net, Ipv6Net, PrefixLenError}; use std::{ fs::{self, File, Permissions}, io, @@ -116,3 +117,29 @@ pub fn get_local_addrs() -> Result, io::E }) .take(10)) } + +pub trait IpNetExt { + fn is_assignable(&self, ip: &IpAddr) -> bool; + + #[allow(clippy::new_ret_no_self)] + fn new(ip: IpAddr, prefix_len: u8) -> Result; +} + +impl IpNetExt for IpNet { + fn new(ip: IpAddr, prefix_len: u8) -> Result { + Ok(match ip { + IpAddr::V4(a) => Ipv4Net::new(a, prefix_len)?.into(), + IpAddr::V6(a) => Ipv6Net::new(a, prefix_len)?.into(), + }) + } + + fn is_assignable(&self, ip: &IpAddr) -> bool { + self.contains(ip) + && match self { + IpNet::V4(_) => { + self.prefix_len() >= 31 || (ip != &self.network() && ip != &self.broadcast()) + }, + IpNet::V6(_) => self.prefix_len() >= 127 || ip != &self.network(), + } + } +} diff --git a/shared/src/netlink.rs b/shared/src/netlink.rs index f887db5..c84c10f 100644 --- a/shared/src/netlink.rs +++ b/shared/src/netlink.rs @@ -1,4 +1,4 @@ -use ipnetwork::IpNetwork; +use ipnet::IpNet; use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_REQUEST}; use netlink_packet_route::{ address, @@ -36,11 +36,11 @@ pub fn set_up(interface: &InterfaceName, mtu: u32) -> Result<(), io::Error> { Ok(()) } -pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), io::Error> { +pub fn set_addr(interface: &InterfaceName, addr: IpNet) -> Result<(), io::Error> { let index = if_nametoindex(interface)?; let (family, nlas) = match addr { - IpNetwork::V4(network) => { - let addr_bytes = network.ip().octets().to_vec(); + IpNet::V4(network) => { + let addr_bytes = network.addr().octets().to_vec(); ( AF_INET as u8, vec![ @@ -49,16 +49,16 @@ pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), io::Er ], ) }, - IpNetwork::V6(network) => ( + IpNet::V6(network) => ( AF_INET6 as u8, - vec![address::Nla::Address(network.ip().octets().to_vec())], + vec![address::Nla::Address(network.addr().octets().to_vec())], ), }; let message = AddressMessage { header: AddressHeader { index, family, - prefix_len: addr.prefix(), + prefix_len: addr.prefix_len(), scope: RT_SCOPE_UNIVERSE, ..Default::default() }, @@ -72,11 +72,11 @@ pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), io::Er Ok(()) } -pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result { +pub fn add_route(interface: &InterfaceName, cidr: IpNet) -> Result { let if_index = if_nametoindex(interface)?; let (address_family, dst) = match cidr { - IpNetwork::V4(network) => (AF_INET as u8, network.network().octets().to_vec()), - IpNetwork::V6(network) => (AF_INET6 as u8, network.network().octets().to_vec()), + IpNet::V4(network) => (AF_INET as u8, network.network().octets().to_vec()), + IpNet::V6(network) => (AF_INET6 as u8, network.network().octets().to_vec()), }; let message = RouteMessage { header: RouteHeader { @@ -84,7 +84,7 @@ pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result, } impl Deref for CidrContents { - type Target = IpNetwork; + type Target = IpNet; fn deref(&self) -> &Self::Target { &self.cidr @@ -247,7 +247,7 @@ impl<'a> CidrTree<'a> { pub fn new(cidrs: &'a [Cidr]) -> Self { let root = cidrs .iter() - .min_by_key(|c| c.cidr.prefix()) + .min_by_key(|c| c.cidr.prefix_len()) .expect("failed to find root CIDR"); Self::with_root(cidrs, root) } @@ -356,7 +356,7 @@ pub struct AddCidrOpts { /// The CIDR network (eg. '10.42.5.0/24') #[clap(long)] - pub cidr: Option, + pub cidr: Option, /// The CIDR parent name #[clap(long)] @@ -431,7 +431,7 @@ pub struct NatOpts { #[clap(long)] /// Exclude one or more CIDRs from NAT candidate reporting. /// ex. --exclude-nat-candidates '0.0.0.0/0' would report no candidates. - pub exclude_nat_candidates: Vec, + pub exclude_nat_candidates: Vec, #[clap(long, conflicts_with = "exclude-nat-candidates")] /// Don't report any candidates to coordinating server. @@ -454,7 +454,7 @@ impl NatOpts { || self .exclude_nat_candidates .iter() - .any(|network| network.contains(ip)) + .any(|network| network.contains(&ip)) } } diff --git a/shared/src/wg.rs b/shared/src/wg.rs index 5a75317..71fd48b 100644 --- a/shared/src/wg.rs +++ b/shared/src/wg.rs @@ -1,5 +1,5 @@ use crate::{Error, IoErrorContext, NetworkOpts, Peer, PeerDiff}; -use ipnetwork::IpNetwork; +use ipnet::IpNet; use std::{ io, net::{IpAddr, SocketAddr}, @@ -32,17 +32,17 @@ fn cmd(bin: &str, args: &[&str]) -> Result { } #[cfg(target_os = "macos")] -pub fn set_addr(interface: &InterfaceName, addr: IpNetwork) -> Result<(), io::Error> { +pub fn set_addr(interface: &InterfaceName, addr: IpNet) -> Result<(), io::Error> { let real_interface = wireguard_control::backends::userspace::resolve_tun(interface)?; - if addr.is_ipv4() { + if matches!(addr, IpNet::V4(_)) { cmd( "ifconfig", &[ &real_interface, "inet", &addr.to_string(), - &addr.ip().to_string(), + &addr.addr().to_string(), "alias", ], ) @@ -72,7 +72,7 @@ pub use super::netlink::set_up; pub fn up( interface: &InterfaceName, private_key: &str, - address: IpNetwork, + address: IpNet, listen_port: Option, peer: Option<(&str, IpAddr, SocketAddr)>, network: NetworkOpts, @@ -102,9 +102,11 @@ pub fn up( set_addr(interface, address)?; set_up( interface, - network - .mtu - .unwrap_or_else(|| if address.is_ipv4() { 1420 } else { 1400 }), + network.mtu.unwrap_or(if matches!(address, IpNet::V4(_)) { + 1420 + } else { + 1400 + }), )?; if !network.no_routing { add_route(interface, address)?; @@ -139,14 +141,18 @@ pub fn down(interface: &InterfaceName, backend: Backend) -> Result<(), Error> { /// Returns an error if the process doesn't exit successfully, otherwise returns /// true if the route was changed, false if the route already exists. #[cfg(target_os = "macos")] -pub fn add_route(interface: &InterfaceName, cidr: IpNetwork) -> Result { +pub fn add_route(interface: &InterfaceName, cidr: IpNet) -> Result { let real_interface = wireguard_control::backends::userspace::resolve_tun(interface)?; let output = cmd( "route", &[ "-n", "add", - if cidr.is_ipv4() { "-inet" } else { "-inet6" }, + if matches!(cidr, IpNet::V4(_)) { + "-inet" + } else { + "-inet6" + }, &cidr.to_string(), "-interface", &real_interface,