shared(PeerDiff): refactor struct and update peer endpoints only when handshake failed

The past behavior of clients was to, on every fetch from the server, update each of its peer's endpoints with the one reported from the server. While this wasn't a problem on certain types of NATs to help with holepunching, in some situations it caused previously working connections to no longer work (when one peer had a port-restricted or symmetric cone type NAT).
pull/127/head
Jake McGinty 2021-08-05 09:38:14 +09:00 committed by GitHub
parent b169435355
commit e97eb737a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 348 additions and 155 deletions

54
Cargo.lock generated
View File

@ -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"

View File

@ -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::<Vec<PeerConfigBuilder>>();
// 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::<Vec<_>>();
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 { "" },

View File

@ -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" }

View File

@ -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<SocketAddr>,
pub persistent_keepalive_interval: Option<u16>,
pub is_disabled: bool,
#[derive(Clone, Debug, PartialEq)]
pub struct ChangeString {
name: &'static str,
old: Option<String>,
new: Option<String>,
}
impl Peer {
pub fn diff(&self, peer: &PeerConfig) -> Option<PeerDiff> {
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<T, U>(name: &'static str, old: Option<T>, new: Option<U>) -> 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<ChangeString>,
}
impl<'a> PeerDiff<'a> {
pub fn new(
old_info: Option<&'a PeerInfo>,
new: Option<&'a Peer>,
) -> Result<Option<Self>, 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<ChangeString>)> {
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<PeerDiff<'a>> 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)));
}
}

View File

@ -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);
}

View File

@ -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: (),
},
}
}

View File

@ -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: (),
},
}
}

View File

@ -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)
}

View File

@ -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<SystemTime>,
@ -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.

View File

@ -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"