diff --git a/Cargo.lock b/Cargo.lock index 9b4e3ea..71b8636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,9 +62,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bindgen" -version = "0.58.1" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" +checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" dependencies = [ "bitflags", "cexpr", @@ -85,6 +85,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -105,9 +117,9 @@ checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cexpr" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" dependencies = [ "nom", ] @@ -277,6 +289,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures-channel" version = "0.3.16" @@ -613,9 +631,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c5374735aa0cd07cb7fd820b656062b187b5588d79517f72956b57c6de9ef" +checksum = "f48ea34ea0678719815c3753155067212f853ad2d8ef4a49167bae7f7c254188" dependencies = [ "libc", "log", @@ -623,10 +641,12 @@ dependencies = [ [[package]] name = "nom" -version = "5.1.2" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ + "bitvec", + "funty", "memchr", "version_check", ] @@ -785,6 +805,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.8.4" @@ -1064,6 +1090,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.2.0" @@ -1370,6 +1402,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "x25519-dalek" version = "1.1.0" diff --git a/client/src/main.rs b/client/src/main.rs index 4434321..9b7880d 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -6,14 +6,14 @@ use indoc::eprintdoc; use shared::{ interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree, DeleteCidrOpts, EndpointContents, - InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, RedeemContents, RenamePeerOpts, - State, WrappedIoError, CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT, + InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, PeerDiff, RedeemContents, + RenamePeerOpts, State, WrappedIoError, CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT, }; use std::{ fmt, io, path::{Path, PathBuf}, thread, - time::{Duration, SystemTime}, + time::Duration, }; use structopt::{clap::AppSettings, StructOpt}; use wgctrl::{Device, DeviceUpdate, InterfaceName, PeerConfigBuilder, PeerInfo}; @@ -446,10 +446,9 @@ fn fetch( network: NetworkOpt, ) -> Result<(), Error> { let config = InterfaceConfig::from_interface(interface)?; - let interface_up = if let Ok(interfaces) = Device::list(network.backend) { - interfaces.iter().any(|name| name == interface) - } else { - false + let interface_up = match Device::list(network.backend) { + Ok(interfaces) => interfaces.iter().any(|name| name == interface), + _ => false, }; if !interface_up { @@ -493,61 +492,68 @@ fn fetch( .unwrap_or_default(); let existing_peers = &device_info.peers; - let peer_configs_diff = peers - .iter() - .filter(|peer| !peer.is_disabled && peer.public_key != interface_public_key) - .filter_map(|peer| { + // Match existing peers (by pubkey) to new peer information from the server. + let modifications = peers.iter().filter_map(|peer| { + if peer.is_disabled || peer.public_key == interface_public_key { + None + } else { let existing_peer = existing_peers .iter() .find(|p| p.config.public_key.to_base64() == peer.public_key); + PeerDiff::new(existing_peer, Some(peer)).unwrap() + } + }); - let change = match existing_peer { - Some(existing_peer) => peer.diff(&existing_peer.config).map(|diff| { - if let Some(endpoint) = diff.endpoint { - log::debug!(" Peer endpoint changed: {:?}", endpoint); - } - (PeerConfigBuilder::from(&diff), peer, "modified".normal()) - }), - None => Some((PeerConfigBuilder::from(peer), peer, "added".green())), + // Remove any peers on the interface that aren't in the server's peer list any more. + let removals = existing_peers.iter().filter_map(|existing| { + let public_key = existing.config.public_key.to_base64(); + if peers.iter().any(|p| p.public_key == public_key) { + None + } else { + PeerDiff::new(Some(&existing), None).unwrap() + } + }); + + let updates = modifications + .chain(removals) + .inspect(|diff| { + let public_key = diff.public_key().to_base64(); + + let text = match (diff.old, diff.new) { + (None, Some(_)) => "added".green(), + (Some(_), Some(_)) => "modified".yellow(), + (Some(_), None) => "removed".red(), + _ => unreachable!("PeerDiff can't be None -> None"), }; - change.map(|(builder, peer, text)| { - println!( - " peer {} ({}...) was {}.", - peer.name.yellow(), - &peer.public_key[..10].dimmed(), - text - ); - builder - }) - }) - .collect::>(); + // Grab the peer name from either the new data, or the historical data (if the peer is removed). + let peer_hostname = match diff.new { + Some(peer) => Some(peer.name.clone()), + _ => store + .peers() + .iter() + .find(|p| p.public_key == public_key) + .map(|p| p.name.clone()), + }; + let peer_name = peer_hostname.as_deref().unwrap_or("[unknown]"); - let mut device_config_builder = DeviceUpdate::new(); - let mut device_config_changed = false; - - if !peer_configs_diff.is_empty() { - device_config_builder = device_config_builder.add_peers(&peer_configs_diff); - device_config_changed = true; - } - - for peer in existing_peers { - let public_key = peer.config.public_key.to_base64(); - if !peers.iter().any(|p| p.public_key == public_key) { - println!( - " peer ({}...) was {}.", - &public_key[..10].yellow(), - "removed".red() + log::info!( + " peer {} ({}...) was {}.", + peer_name.yellow(), + &public_key[..10].dimmed(), + text ); - device_config_builder = - device_config_builder.remove_peer_by_key(&peer.config.public_key); - device_config_changed = true; - } - } + for change in diff.changes() { + log::debug!(" {}", change); + } + }) + .map(PeerConfigBuilder::from) + .collect::>(); - if device_config_changed { - device_config_builder + if !updates.is_empty() { + DeviceUpdate::new() + .add_peers(&updates) .apply(interface, network.backend) .with_str(interface.to_string())?; @@ -986,17 +992,16 @@ fn print_peer(peer: &PeerState, short: bool, level: usize) { let pad = level * 2; let PeerState { peer, info } = peer; if short { - let last_handshake = info - .and_then(|i| i.stats.last_handshake_time) - .and_then(|t| t.elapsed().ok()) - .unwrap_or_else(|| SystemTime::UNIX_EPOCH.elapsed().unwrap()); - - let online = last_handshake <= Duration::from_secs(180) || info.is_none(); + let connected = PeerDiff::peer_recently_connected(info); println_pad!( pad, "| {} {}: {} ({}{}…)", - if online { "◉".bold() } else { "◯".dimmed() }, + if connected { + "◉".bold() + } else { + "◯".dimmed() + }, peer.ip.to_string().yellow().bold(), peer.name.yellow(), if info.is_none() { "you, " } else { "" }, diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 0025e64..84048fa 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -25,7 +25,7 @@ url = "2" wgctrl = { path = "../wgctrl-rs" } [target.'cfg(target_os = "linux")'.dependencies] -netlink-sys = "0.6" +netlink-sys = "0.7" netlink-packet-core = "0.2" netlink-packet-route = "0.7" wgctrl-sys = { path = "../wgctrl-sys" } diff --git a/shared/src/types.rs b/shared/src/types.rs index 789675a..41bae3c 100644 --- a/shared/src/types.rs +++ b/shared/src/types.rs @@ -1,3 +1,4 @@ +use anyhow::{anyhow, Error}; use ipnetwork::IpNetwork; use lazy_static::lazy_static; use regex::Regex; @@ -14,7 +15,10 @@ use std::{ }; use structopt::StructOpt; use url::Host; -use wgctrl::{Backend, InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder}; +use wgctrl::{ + AllowedIp, Backend, InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder, + PeerInfo, +}; #[derive(Debug, Clone)] pub struct Interface { @@ -113,7 +117,7 @@ impl<'de> Deserialize<'de> for Endpoint { } } -impl fmt::Display for Endpoint { +impl Display for Endpoint { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.host.fmt(f)?; f.write_str(":")?; @@ -428,96 +432,179 @@ impl Display for Peer { } } -#[derive(Debug, PartialEq)] -pub struct PeerDiff { - pub public_key: String, - pub endpoint: Option, - pub persistent_keepalive_interval: Option, - pub is_disabled: bool, +#[derive(Clone, Debug, PartialEq)] +pub struct ChangeString { + name: &'static str, + old: Option, + new: Option, } -impl Peer { - pub fn diff(&self, peer: &PeerConfig) -> Option { - assert_eq!(self.public_key, peer.public_key.to_base64()); +impl Display for ChangeString { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{}: {} => {}", + self.name, + self.old.as_deref().unwrap_or("[none]"), + self.new.as_deref().unwrap_or("[none]") + ) + } +} - let endpoint_diff = if let Some(ref endpoint) = self.endpoint { - match endpoint.resolve() { - Ok(resolved) if Some(resolved) != peer.endpoint => Some(resolved), - _ => None, - } - } else { - None +impl ChangeString { + pub fn new(name: &'static str, old: Option, new: Option) -> Self + where + T: fmt::Debug, + U: fmt::Debug, + { + Self { + name, + old: old.map(|t| format!("{:?}", t)), + new: new.map(|t| format!("{:?}", t)), + } + } +} + +/// Encompasses the logic for comparing the peer configuration currently on the WireGuard interface +/// to a (potentially) more current peer configuration from the innernet server. +#[derive(Clone, Debug, PartialEq)] +pub struct PeerDiff<'a> { + pub old: Option<&'a PeerConfig>, + pub new: Option<&'a Peer>, + builder: PeerConfigBuilder, + changes: Vec, +} + +impl<'a> PeerDiff<'a> { + pub fn new( + old_info: Option<&'a PeerInfo>, + new: Option<&'a Peer>, + ) -> Result, Error> { + let old = old_info.map(|p| &p.config); + match (old_info, new) { + (Some(old), Some(new)) if old.config.public_key.to_base64() != new.public_key => Err( + anyhow!("old and new peer configs have different public keys"), + ), + (None, None) => Ok(None), + _ => Ok( + Self::peer_config_builder(old_info, new).map(|(builder, changes)| Self { + old, + new, + builder, + changes, + }), + ), + } + } + + /// WireGuard rejects any communication after REJECT_AFTER_TIME, so we can use this + /// as a heuristic for "currentness" without relying on heavier things like ICMP. + pub fn peer_recently_connected(peer: &Option<&PeerInfo>) -> bool { + const REJECT_AFTER_TIME: u64 = 180; + + let last_handshake = peer + .and_then(|p| p.stats.last_handshake_time) + .and_then(|t| t.elapsed().ok()) + .unwrap_or_else(|| SystemTime::UNIX_EPOCH.elapsed().unwrap()); + + last_handshake <= Duration::from_secs(REJECT_AFTER_TIME) + } + + pub fn public_key(&self) -> &Key { + &self.builder.public_key() + } + + pub fn changes(&self) -> &[ChangeString] { + &self.changes + } + + fn peer_config_builder( + old_info: Option<&PeerInfo>, + new: Option<&Peer>, + ) -> Option<(PeerConfigBuilder, Vec)> { + let old = old_info.map(|p| &p.config); + let public_key = match (old, new) { + (Some(old), _) => old.public_key.clone(), + (_, Some(new)) => Key::from_base64(&new.public_key).unwrap(), + _ => return None, }; + let mut builder = PeerConfigBuilder::new(&public_key); + let mut changes = vec![]; - let keepalive_diff = - if peer.persistent_keepalive_interval != self.persistent_keepalive_interval { - self.persistent_keepalive_interval - } else { - None + // Remove peer from interface if they're deleted or disabled, and we can return early. + if new.is_none() || matches!(new, Some(new) if new.is_disabled) { + return Some((builder.remove(), changes)); + } + // diff.new is now guaranteed to be a Some(_) variant. + let new = new.unwrap(); + + // TODO(jake): use contains() when stable: https://github.com/rust-lang/rust/issues/62358 + + let new_allowed_ips = &[AllowedIp { + address: new.ip, + cidr: if new.ip.is_ipv4() { 32 } else { 128 }, + }]; + if old.is_none() || matches!(old, Some(old) if old.allowed_ips != new_allowed_ips) { + builder = builder + .replace_allowed_ips() + .add_allowed_ips(new_allowed_ips); + changes.push(ChangeString::new( + "AllowedIPs", + old.map(|o| &o.allowed_ips[..]), + Some(&new_allowed_ips[0]), + )); + } + + if old.is_none() + || matches!(old, Some(old) if old.persistent_keepalive_interval != new.persistent_keepalive_interval) + { + builder = match new.persistent_keepalive_interval { + Some(interval) => builder.set_persistent_keepalive_interval(interval), + None => builder.unset_persistent_keepalive(), }; + changes.push(ChangeString::new( + "PersistentKeepalive", + old.and_then(|p| p.persistent_keepalive_interval), + new.persistent_keepalive_interval, + )); + } - if endpoint_diff.is_none() && keepalive_diff.is_none() { - None + // We won't update the endpoint if there's already a stable connection. + if !Self::peer_recently_connected(&old_info) { + let resolved = new.endpoint.as_ref().and_then(|e| e.resolve().ok()); + if let Some(addr) = resolved { + if old.is_none() || matches!(old, Some(old) if old.endpoint != resolved) { + builder = builder.set_endpoint(addr); + changes.push(ChangeString::new( + "Endpoint", + old.and_then(|p| p.endpoint), + Some(addr), + )); + } + } + } + if !changes.is_empty() { + Some((builder, changes)) } else { - Some(PeerDiff { - public_key: self.public_key.clone(), - endpoint: endpoint_diff, - persistent_keepalive_interval: keepalive_diff, - is_disabled: self.is_disabled, - }) + None } } } impl<'a> From<&'a Peer> for PeerConfigBuilder { fn from(peer: &Peer) -> Self { - let builder = PeerConfigBuilder::new(&Key::from_base64(&peer.public_key).unwrap()) - .replace_allowed_ips() - .add_allowed_ip(peer.ip, if peer.ip.is_ipv4() { 32 } else { 128 }); - - let builder = if peer.is_disabled { - builder.remove() - } else { - builder - }; - - let builder = if let Some(interval) = peer.persistent_keepalive_interval { - builder.set_persistent_keepalive_interval(interval) - } else { - builder - }; - - let resolved = peer.endpoint.as_ref().map(|e| e.resolve().ok()).flatten(); - - if let Some(endpoint) = resolved { - builder.set_endpoint(endpoint) - } else { - builder - } + PeerDiff::new(None, Some(peer)) + .expect("No Err on explicitly set peer data") + .expect("None -> Some(peer) will always create a PeerDiff") + .into() } } -impl<'a> From<&'a PeerDiff> for PeerConfigBuilder { - fn from(peer: &PeerDiff) -> Self { - let builder = PeerConfigBuilder::new(&Key::from_base64(&peer.public_key).unwrap()); - - let builder = if peer.is_disabled { - builder.remove() - } else { - builder - }; - - let builder = if let Some(interval) = peer.persistent_keepalive_interval { - builder.set_persistent_keepalive_interval(interval) - } else { - builder - }; - - if let Some(endpoint) = peer.endpoint { - builder.set_endpoint(endpoint) - } else { - builder - } +impl<'a> From> for PeerConfigBuilder { + /// Turn a PeerDiff into a minimal set of instructions to update the WireGuard interface, + /// hopefully minimizing dropped packets and other interruptions. + fn from(diff: PeerDiff) -> Self { + diff.builder } } @@ -646,7 +733,7 @@ pub struct WrappedIoError { context: String, } -impl std::fmt::Display for WrappedIoError { +impl Display for WrappedIoError { fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { write!(f, "{} - {}", self.context, self.io_error) } @@ -666,7 +753,7 @@ impl std::error::Error for WrappedIoError {} mod tests { use super::*; use std::net::IpAddr; - use wgctrl::{Key, PeerConfigBuilder}; + use wgctrl::{Key, PeerConfigBuilder, PeerStats}; #[test] fn test_peer_no_diff() { @@ -691,8 +778,15 @@ mod tests { PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32); let config = builder.into_peer_config(); + let info = PeerInfo { + config, + stats: Default::default(), + }; - assert_eq!(peer.diff(&config), None); + let diff = PeerDiff::new(Some(&info), Some(&peer)).unwrap(); + + println!("{:?}", diff); + assert_eq!(diff, None); } #[test] @@ -718,9 +812,56 @@ mod tests { PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32); let config = builder.into_peer_config(); + let info = PeerInfo { + config, + stats: Default::default(), + }; + let diff = PeerDiff::new(Some(&info), Some(&peer)).unwrap(); println!("{:?}", peer); - println!("{:?}", config); - assert!(matches!(peer.diff(&config), Some(_))); + println!("{:?}", info.config); + assert!(matches!(diff, Some(_))); + } + + #[test] + fn test_peer_diff_handshake_time() { + const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE="; + let ip: IpAddr = "10.0.0.1".parse().unwrap(); + let peer = Peer { + id: 1, + contents: PeerContents { + name: "peer1".parse().unwrap(), + ip, + cidr_id: 1, + public_key: PUBKEY.to_owned(), + endpoint: Some("1.1.1.1:1111".parse().unwrap()), + persistent_keepalive_interval: None, + is_admin: false, + is_disabled: false, + is_redeemed: true, + invite_expires: None, + }, + }; + let builder = + PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32); + + let config = builder.into_peer_config(); + let mut info = PeerInfo { + config, + stats: PeerStats { + last_handshake_time: Some(SystemTime::now() - Duration::from_secs(200)), + ..Default::default() + }, + }; + + // If there hasn't been a recent handshake, endpoint should be being set. + assert!(matches!( + PeerDiff::new(Some(&info), Some(&peer)), + Ok(Some(_)) + )); + + // If there *has* been a recent handshake, endpoint should *not* be being set. + info.stats.last_handshake_time = Some(SystemTime::now()); + assert!(matches!(PeerDiff::new(Some(&info), Some(&peer)), Ok(None))); } } diff --git a/shared/src/wg.rs b/shared/src/wg.rs index 428df7a..521b783 100644 --- a/shared/src/wg.rs +++ b/shared/src/wg.rs @@ -85,6 +85,7 @@ pub fn up( ) })?) .add_allowed_ip(address, prefix) + .set_persistent_keepalive_interval(25) .set_endpoint(endpoint); device = device.add_peer(peer_config); } diff --git a/wgctrl-rs/src/backends/kernel.rs b/wgctrl-rs/src/backends/kernel.rs index f5a0703..740961c 100644 --- a/wgctrl-rs/src/backends/kernel.rs +++ b/wgctrl-rs/src/backends/kernel.rs @@ -58,7 +58,6 @@ impl<'a> From<&'a wgctrl_sys::wg_peer> for PeerInfo { }, rx_bytes: raw.rx_bytes, tx_bytes: raw.tx_bytes, - __cant_construct_me: (), }, } } diff --git a/wgctrl-rs/src/backends/userspace.rs b/wgctrl-rs/src/backends/userspace.rs index dbb8e51..5792d3f 100644 --- a/wgctrl-rs/src/backends/userspace.rs +++ b/wgctrl-rs/src/backends/userspace.rs @@ -94,7 +94,6 @@ fn new_peer_info(public_key: Key) -> PeerInfo { last_handshake_time: None, rx_bytes: 0, tx_bytes: 0, - __cant_construct_me: (), }, } } diff --git a/wgctrl-rs/src/config.rs b/wgctrl-rs/src/config.rs index ac5f439..85d8178 100644 --- a/wgctrl-rs/src/config.rs +++ b/wgctrl-rs/src/config.rs @@ -73,6 +73,11 @@ impl PeerConfigBuilder { } } + /// The public key used in this builder. + pub fn public_key(&self) -> &Key { + &self.public_key + } + /// Creates a `PeerConfigBuilder` from a [`PeerConfig`](PeerConfig). /// /// This is mostly a convenience method for cases when you want to copy @@ -120,7 +125,7 @@ impl PeerConfigBuilder { } /// Specifies that this peer does not require keepalive packets. - pub fn disable_persistent_keepalive(self) -> Self { + pub fn unset_persistent_keepalive(self) -> Self { self.set_persistent_keepalive_interval(0) } diff --git a/wgctrl-rs/src/device.rs b/wgctrl-rs/src/device.rs index c9c0128..721dce1 100644 --- a/wgctrl-rs/src/device.rs +++ b/wgctrl-rs/src/device.rs @@ -12,7 +12,7 @@ use std::{ }; /// Represents an IP address a peer is allowed to have, in CIDR notation. -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Clone)] pub struct AllowedIp { /// The IP address. pub address: IpAddr, @@ -20,6 +20,12 @@ pub struct AllowedIp { pub cidr: u8, } +impl fmt::Debug for AllowedIp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.address, self.cidr) + } +} + impl std::str::FromStr for AllowedIp { type Err = (); @@ -58,7 +64,7 @@ pub struct PeerConfig { /// /// These are the attributes that will change over time; to update them, /// re-read the information from the interface. -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct PeerStats { /// Time of the last handshake/rekey with this peer. pub last_handshake_time: Option, @@ -66,7 +72,6 @@ pub struct PeerStats { pub rx_bytes: u64, /// Number of bytes transmitted to this peer. pub tx_bytes: u64, - pub(crate) __cant_construct_me: (), } /// Represents the complete status of a peer. diff --git a/wgctrl-sys/Cargo.toml b/wgctrl-sys/Cargo.toml index b510c88..0f66cea 100644 --- a/wgctrl-sys/Cargo.toml +++ b/wgctrl-sys/Cargo.toml @@ -13,5 +13,5 @@ version = "1.4.1" libc = "0.2" [build-dependencies] -bindgen = { version = "0.58", default-features = false } +bindgen = { version = "0", default-features = false } cc = "1.0"