From 53bd428b87c5dd147f803588c252c37349969337 Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 20 Jul 2020 14:42:42 +0200 Subject: [PATCH 01/69] Initial commit. --- Cargo.toml | 4 ++++ protocols/autonat/Cargo.toml | 17 +++++++++++++ protocols/autonat/build.rs | 23 ++++++++++++++++++ protocols/autonat/src/autonat.rs | 23 ++++++++++++++++++ protocols/autonat/src/lib.rs | 29 ++++++++++++++++++++++ protocols/autonat/src/structs.proto | 37 +++++++++++++++++++++++++++++ src/lib.rs | 4 ++++ 7 files changed, 137 insertions(+) create mode 100644 protocols/autonat/Cargo.toml create mode 100644 protocols/autonat/build.rs create mode 100644 protocols/autonat/src/autonat.rs create mode 100644 protocols/autonat/src/lib.rs create mode 100644 protocols/autonat/src/structs.proto diff --git a/Cargo.toml b/Cargo.toml index 05b5ccc8860..bb665d0d573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["network-programming", "asynchronous"] [features] default = [ + "autonat", "deflate", "dns-async-std", "floodsub", @@ -33,6 +34,7 @@ default = [ "websocket", "yamux", ] +autonat = ["libp2p-autonat"] deflate = ["libp2p-deflate"] dns-async-std = ["libp2p-dns", "libp2p-dns/async-std"] dns-tokio = ["libp2p-dns", "libp2p-dns/tokio"] @@ -68,6 +70,7 @@ atomic = "0.5.0" bytes = "1" futures = "0.3.1" lazy_static = "1.2" +libp2p-autonat = { version = "0.20.0", path = "protocols/autonat", optional = true } libp2p-core = { version = "0.30.0", path = "core", default-features = false } libp2p-floodsub = { version = "0.31.0", path = "protocols/floodsub", optional = true } libp2p-gossipsub = { version = "0.33.0", path = "./protocols/gossipsub", optional = true } @@ -116,6 +119,7 @@ members = [ "misc/peer-id-generator", "muxers/mplex", "muxers/yamux", + "protocols/autonat", "protocols/floodsub", "protocols/gossipsub", "protocols/rendezvous", diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml new file mode 100644 index 00000000000..892f991af67 --- /dev/null +++ b/protocols/autonat/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "libp2p-autonat" +version = "0.20.0" +authors = ["David Craven "] +edition = "2018" +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] + +[build-dependencies] +prost-build = "0.6" + +[dependencies] +libp2p-core = { version = "0.30.0", path = "../../core" } +libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +prost = "0.8.0" diff --git a/protocols/autonat/build.rs b/protocols/autonat/build.rs new file mode 100644 index 00000000000..56c7b20121a --- /dev/null +++ b/protocols/autonat/build.rs @@ -0,0 +1,23 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +fn main() { + prost_build::compile_protos(&["src/structs.proto"], &["src"]).unwrap(); +} diff --git a/protocols/autonat/src/autonat.rs b/protocols/autonat/src/autonat.rs new file mode 100644 index 00000000000..6954d9ce554 --- /dev/null +++ b/protocols/autonat/src/autonat.rs @@ -0,0 +1,23 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +pub struct AutoNat; + +pub enum AutoNatEvent {} diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs new file mode 100644 index 00000000000..d05a3328540 --- /dev/null +++ b/protocols/autonat/src/lib.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Implementation of the [AutoNAT] protocol. + +pub use self::autonat::{AutoNat, AutoNatEvent}; + +mod autonat; + +mod structs_proto { + include!(concat!(env!("OUT_DIR"), "/structs.rs")); +} diff --git a/protocols/autonat/src/structs.proto b/protocols/autonat/src/structs.proto new file mode 100644 index 00000000000..19e27abd36a --- /dev/null +++ b/protocols/autonat/src/structs.proto @@ -0,0 +1,37 @@ +syntax = "proto2"; + +package structs; + +message Message { + enum MessageType { + DIAL = 0; + DIAL_RESPONSE = 1; + } + + enum ResponseStatus { + OK = 0; + E_DIAL_ERROR = 100; + E_DIAL_REFUSED = 101; + E_BAD_REQUEST = 200; + E_INTERNAL_ERROR = 300; + } + + message PeerInfo { + optional bytes id = 1; + repeated bytes addrs = 2; + } + + message Dial { + optional PeerInfo peer = 1; + } + + message DialResponse { + optional ResponseStatus status = 1; + optional string statusText = 2; + optional bytes addr = 3; + } + + optional MessageType type = 1; + optional Dial dial = 2; + optional DialResponse dialResponse = 3; +} diff --git a/src/lib.rs b/src/lib.rs index b8728005ea3..b913a94121e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,10 @@ pub use libp2p_core::multihash; #[doc(inline)] pub use multiaddr; +#[cfg(feature = "autonat")] +#[cfg_attr(docsrs, doc(cfg(feature = "autonat")))] +#[doc(inline)] +pub use libp2p_autonat as autonat; #[doc(inline)] pub use libp2p_core as core; #[cfg(feature = "deflate")] From c4ae5231d8b4ecddf7d4d3386252b19eb4027bc2 Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 20 Jul 2020 17:19:36 +0200 Subject: [PATCH 02/69] Implement protocol. --- protocols/autonat/Cargo.toml | 3 + protocols/autonat/src/lib.rs | 1 + protocols/autonat/src/protocol.rs | 277 ++++++++++++++++++++++++++++++ 3 files changed, 281 insertions(+) create mode 100644 protocols/autonat/src/protocol.rs diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index 892f991af67..a60c4c3be82 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -12,6 +12,9 @@ categories = ["network-programming", "asynchronous"] prost-build = "0.6" [dependencies] +async-trait = "0.1.51" +futures = "0.3.17" libp2p-core = { version = "0.30.0", path = "../../core" } libp2p-swarm = { version = "0.31.0", path = "../../swarm" } +libp2p-request-response = { version = "0.13.0", path = "../request-response" } prost = "0.8.0" diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index d05a3328540..a694409a678 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -23,6 +23,7 @@ pub use self::autonat::{AutoNat, AutoNatEvent}; mod autonat; +mod protocol; mod structs_proto { include!(concat!(env!("OUT_DIR"), "/structs.rs")); diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs new file mode 100644 index 00000000000..30d9d6f9962 --- /dev/null +++ b/protocols/autonat/src/protocol.rs @@ -0,0 +1,277 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use crate::structs_proto; +pub use crate::structs_proto::message::ResponseStatus; +use async_trait::async_trait; +use futures::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; +use libp2p_core::{upgrade, Multiaddr, PeerId}; +use libp2p_request_response::{ProtocolName, RequestResponseCodec}; +use prost::Message; +use std::{convert::TryFrom, io}; + +#[derive(Clone, Debug)] +pub struct AutoNatProtocol; + +impl ProtocolName for AutoNatProtocol { + fn protocol_name(&self) -> &[u8] { + b"/libp2p/autonat/1.0.0" + } +} + +#[derive(Clone)] +pub struct AutoNatCodec; + +#[async_trait] +impl RequestResponseCodec for AutoNatCodec { + type Protocol = AutoNatProtocol; + type Request = DialRequest; + type Response = DialResponse; + + async fn read_request( + &mut self, + _: &AutoNatProtocol, + io: &mut T, + ) -> io::Result + where + T: AsyncRead + Send + Unpin, + { + let bytes = upgrade::read_length_prefixed(io, 1024) + .await + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let request = DialRequest::from_bytes(&bytes) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(request) + } + + async fn read_response( + &mut self, + _: &AutoNatProtocol, + io: &mut T, + ) -> io::Result + where + T: AsyncRead + Send + Unpin, + { + let bytes = upgrade::read_length_prefixed(io, 1024) + .await + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let response = DialResponse::from_bytes(&bytes) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(response) + } + + async fn write_request( + &mut self, + _: &AutoNatProtocol, + io: &mut T, + data: Self::Request, + ) -> io::Result<()> + where + T: AsyncWrite + Send + Unpin, + { + upgrade::write_length_prefixed(io, data.to_bytes()).await?; + io.close().await + } + + async fn write_response( + &mut self, + _: &AutoNatProtocol, + io: &mut T, + data: Self::Response, + ) -> io::Result<()> + where + T: AsyncWrite + Send + Unpin, + { + upgrade::write_length_prefixed(io, data.to_bytes()).await?; + io.close().await + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DialRequest { + pub peer_id: PeerId, + pub addrs: Vec, +} + +impl DialRequest { + pub fn from_bytes(bytes: &[u8]) -> Result { + let msg = structs_proto::Message::decode(bytes) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + if msg.r#type != Some(structs_proto::message::MessageType::Dial as _) { + return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid type")); + } + let (peer_id, addrs) = if let Some(structs_proto::message::Dial { + peer: + Some(structs_proto::message::PeerInfo { + id: Some(peer_id), + addrs, + }), + }) = msg.dial + { + (peer_id, addrs) + } else { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid dial message", + )); + }; + + let peer_id = { + PeerId::try_from(peer_id) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid peer id"))? + }; + let addrs = { + let mut maddrs = vec![]; + for addr in addrs.into_iter() { + let maddr = Multiaddr::try_from(addr) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + maddrs.push(maddr); + } + maddrs + }; + Ok(Self { peer_id, addrs }) + } + + pub fn to_bytes(self) -> Vec { + let peer_id = self.peer_id.to_bytes(); + let addrs = self.addrs.into_iter().map(|addr| addr.to_vec()).collect(); + + let msg = structs_proto::Message { + r#type: Some(structs_proto::message::MessageType::Dial as _), + dial: Some(structs_proto::message::Dial { + peer: Some(structs_proto::message::PeerInfo { + id: Some(peer_id), + addrs: addrs, + }), + }), + dial_response: None, + }; + + let mut bytes = Vec::with_capacity(msg.encoded_len()); + msg.encode(&mut bytes) + .expect("Vec provides capacity as needed"); + bytes + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DialResponse { + Ok(Multiaddr), + Err(ResponseStatus, String), +} + +impl DialResponse { + pub fn from_bytes(bytes: &[u8]) -> Result { + let msg = structs_proto::Message::decode(bytes) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + if msg.r#type != Some(structs_proto::message::MessageType::DialResponse as _) { + return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid type")); + } + + Ok(match msg.dial_response { + Some(structs_proto::message::DialResponse { + status: Some(0), + status_text: None, + addr: Some(addr), + }) => { + let addr = Multiaddr::try_from(addr) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + Self::Ok(addr) + } + Some(structs_proto::message::DialResponse { + status: Some(status), + status_text, + addr: None, + }) => { + let status = ResponseStatus::from_i32(status).ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "invalid status code") + })?; + Self::Err(status, status_text.unwrap_or_default()) + } + _ => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid dial response message", + )); + } + }) + } + + pub fn to_bytes(self) -> Vec { + let dial_response = match self { + Self::Ok(addr) => structs_proto::message::DialResponse { + status: Some(0), + status_text: None, + addr: Some(addr.to_vec()), + }, + Self::Err(status, status_text) => structs_proto::message::DialResponse { + status: Some(status as _), + status_text: Some(status_text), + addr: None, + }, + }; + + let msg = structs_proto::Message { + r#type: Some(structs_proto::message::MessageType::DialResponse as _), + dial: None, + dial_response: Some(dial_response), + }; + + let mut bytes = Vec::with_capacity(msg.encoded_len()); + msg.encode(&mut bytes) + .expect("Vec provides capacity as needed"); + bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_request_encode_decode() { + let request = DialRequest { + peer_id: PeerId::random(), + addrs: vec![ + "/ip4/8.8.8.8/tcp/30333".parse().unwrap(), + "/ip4/192.168.1.42/tcp/30333".parse().unwrap(), + ], + }; + let bytes = request.clone().to_bytes(); + let request2 = DialRequest::from_bytes(&bytes).unwrap(); + assert_eq!(request, request2); + } + + #[test] + fn test_response_ok_encode_decode() { + let response = DialResponse::Ok("/ip4/8.8.8.8/tcp/30333".parse().unwrap()); + let bytes = response.clone().to_bytes(); + let response2 = DialResponse::from_bytes(&bytes).unwrap(); + assert_eq!(response, response2); + } + + #[test] + fn test_response_err_encode_decode() { + let response = DialResponse::Err(ResponseStatus::EDialError, "failed to dial".into()); + let bytes = response.clone().to_bytes(); + let response2 = DialResponse::from_bytes(&bytes).unwrap(); + assert_eq!(response, response2); + } +} From bccf94c27d8abda322c4653903fa75e203e86c60 Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 20 Jul 2020 19:03:33 +0200 Subject: [PATCH 03/69] Add behaviour boilerplate. --- protocols/autonat/src/autonat.rs | 249 ++++++++++++++++++++++++++++++- protocols/autonat/src/lib.rs | 2 +- 2 files changed, 248 insertions(+), 3 deletions(-) diff --git a/protocols/autonat/src/autonat.rs b/protocols/autonat/src/autonat.rs index 6954d9ce554..773da1038bc 100644 --- a/protocols/autonat/src/autonat.rs +++ b/protocols/autonat/src/autonat.rs @@ -18,6 +18,251 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -pub struct AutoNat; +pub use crate::protocol::DialResponse; +use crate::protocol::{AutoNatCodec, AutoNatProtocol}; +use libp2p_core::{ + connection::{ConnectionId, ListenerId}, + ConnectedPoint, Multiaddr, PeerId, +}; +use libp2p_request_response::{ + handler::RequestResponseHandlerEvent, ProtocolSupport, RequestResponse, RequestResponseConfig, + RequestResponseEvent, RequestResponseMessage, +}; +use libp2p_swarm::{ + IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, +}; +use std::{ + iter, + task::{Context, Poll}, +}; -pub enum AutoNatEvent {} +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum AutoNatEvent { + DialResponse(DialResponse), +} + +pub struct AutoNat { + inner: RequestResponse, +} + +impl AutoNat { + pub fn new() -> Self { + let protocols = iter::once((AutoNatProtocol, ProtocolSupport::Full)); + let cfg = RequestResponseConfig::default(); + let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); + Self { inner } + } + + pub fn add_address(&mut self, peer_id: &PeerId, addr: Multiaddr) { + self.inner.add_address(peer_id, addr) + } +} + +impl NetworkBehaviour for AutoNat { + type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; + type OutEvent = AutoNatEvent; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + self.inner.new_handler() + } + + fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { + self.inner.addresses_of_peer(peer) + } + + fn inject_connected(&mut self, peer: &PeerId) { + self.inner.inject_connected(peer) + } + + fn inject_disconnected(&mut self, peer: &PeerId) { + self.inner.inject_disconnected(peer) + } + + fn inject_connection_established( + &mut self, + peer: &PeerId, + conn: &ConnectionId, + endpoint: &ConnectedPoint, + ) { + self.inner + .inject_connection_established(peer, conn, endpoint) + } + + fn inject_connection_closed( + &mut self, + peer: &PeerId, + conn: &ConnectionId, + endpoint: &ConnectedPoint, + handler: ::Handler, + ) { + self.inner + .inject_connection_closed(peer, conn, endpoint, handler) + } + + fn inject_address_change( + &mut self, + peer: &PeerId, + conn: &ConnectionId, + old: &ConnectedPoint, + new: &ConnectedPoint, + ) { + self.inner.inject_address_change(peer, conn, old, new) + } + + fn inject_event( + &mut self, + peer: PeerId, + conn: ConnectionId, + event: RequestResponseHandlerEvent, + ) { + self.inner.inject_event(peer, conn, event) + } + + fn inject_addr_reach_failure( + &mut self, + peer_id: Option<&PeerId>, + addr: &Multiaddr, + error: &dyn std::error::Error, + ) { + self.inner.inject_addr_reach_failure(peer_id, addr, error) + } + + fn inject_dial_failure( + &mut self, + peer_id: &PeerId, + handler: Self::ProtocolsHandler, + error: libp2p_swarm::DialError, + ) { + self.inner.inject_dial_failure(peer_id, handler, error) + } + + fn inject_listen_failure( + &mut self, + local_addr: &Multiaddr, + send_back_addr: &Multiaddr, + handler: Self::ProtocolsHandler, + ) { + self.inner + .inject_listen_failure(local_addr, send_back_addr, handler) + } + + fn inject_new_listener(&mut self, id: ListenerId) { + self.inner.inject_new_listener(id) + } + + fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { + self.inner.inject_new_listen_addr(id, addr) + } + + fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { + self.inner.inject_expired_listen_addr(id, addr) + } + + fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { + self.inner.inject_listener_error(id, err) + } + + fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &std::io::Error>) { + self.inner.inject_listener_closed(id, reason) + } + + fn inject_new_external_addr(&mut self, addr: &Multiaddr) { + self.inner.inject_new_external_addr(addr) + } + + fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { + self.inner.inject_expired_external_addr(addr) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + params: &mut impl PollParameters, + ) -> Poll> { + loop { + match self.inner.poll(cx, params) { + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::Message { peer, message }, + )) => match message { + RequestResponseMessage::Request { + request_id, + request, + channel, + } => { + println!("{} {:?} {:?} {:?}", peer, request_id, request, channel); + } + RequestResponseMessage::Response { + request_id, + response, + } => { + println!("{} {:?} {:?}", peer, request_id, response); + } + }, + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::ResponseSent { peer, request_id }, + )) => { + println!("response sent {} {:?}", peer, request_id); + } + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::OutboundFailure { + peer, + request_id, + error, + }, + )) => { + println!("outbound failure {} {:?} {:?}", peer, request_id, error); + } + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::InboundFailure { + peer, + error, + request_id, + }, + )) => { + println!("inbound failure {} {:?} {:?}", peer, request_id, error); + } + Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) => { + return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) + } + Poll::Ready(NetworkBehaviourAction::DialPeer { + peer_id, + condition, + handler, + }) => { + return Poll::Ready(NetworkBehaviourAction::DialPeer { + peer_id, + condition, + handler, + }) + } + Poll::Ready(NetworkBehaviourAction::CloseConnection { + peer_id, + connection, + }) => { + return Poll::Ready(NetworkBehaviourAction::CloseConnection { + peer_id, + connection, + }) + } + Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id, + handler, + event, + }) => { + return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id, + handler, + event, + }) + } + Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }) => { + return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { + address, + score, + }) + } + Poll::Pending => return Poll::Pending, + } + } + } +} diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index a694409a678..891ac5ff44b 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -20,7 +20,7 @@ //! Implementation of the [AutoNAT] protocol. -pub use self::autonat::{AutoNat, AutoNatEvent}; +pub use self::autonat::{AutoNat, AutoNatEvent, DialResponse}; mod autonat; mod protocol; From 28f3514d6e25cb13ba58fdac56dbc600ae8cd830 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Tue, 28 Sep 2021 01:15:49 +0200 Subject: [PATCH 04/69] protocols/autonat: Add basic behaviour logic --- .../autonat/src/{autonat.rs => behaviour.rs} | 166 +++++++++++++----- protocols/autonat/src/lib.rs | 4 +- protocols/autonat/src/protocol.rs | 71 ++++++-- 3 files changed, 175 insertions(+), 66 deletions(-) rename protocols/autonat/src/{autonat.rs => behaviour.rs} (57%) diff --git a/protocols/autonat/src/autonat.rs b/protocols/autonat/src/behaviour.rs similarity index 57% rename from protocols/autonat/src/autonat.rs rename to protocols/autonat/src/behaviour.rs index 773da1038bc..61f4315e87c 100644 --- a/protocols/autonat/src/autonat.rs +++ b/protocols/autonat/src/behaviour.rs @@ -18,49 +18,61 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -pub use crate::protocol::DialResponse; -use crate::protocol::{AutoNatCodec, AutoNatProtocol}; +use crate::protocol::{AutoNatCodec, AutoNatProtocol, DialRequest, DialResponse, ResponseError}; use libp2p_core::{ connection::{ConnectionId, ListenerId}, ConnectedPoint, Multiaddr, PeerId, }; use libp2p_request_response::{ handler::RequestResponseHandlerEvent, ProtocolSupport, RequestResponse, RequestResponseConfig, - RequestResponseEvent, RequestResponseMessage, + RequestResponseEvent, RequestResponseMessage, ResponseChannel, }; use libp2p_swarm::{ - IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, + AddressScore, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }; use std::{ + collections::{HashMap, HashSet, VecDeque}, iter, task::{Context, Poll}, }; -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum AutoNatEvent { - DialResponse(DialResponse), -} +type FiniteAddrScore = u32; pub struct AutoNat { inner: RequestResponse, + local_addresses: HashMap, + pending_inbound: HashMap>, + pending_outbound: HashSet, + send_request: VecDeque, } -impl AutoNat { - pub fn new() -> Self { +impl Default for AutoNat { + fn default() -> Self { let protocols = iter::once((AutoNatProtocol, ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); - Self { inner } + Self { + inner, + local_addresses: HashMap::default(), + pending_inbound: HashMap::default(), + pending_outbound: HashSet::default(), + send_request: VecDeque::default(), + } } +} - pub fn add_address(&mut self, peer_id: &PeerId, addr: Multiaddr) { - self.inner.add_address(peer_id, addr) +impl AutoNat { + pub fn add_local_address(&mut self, address: Multiaddr) { + if self.local_addresses.get(&address).is_none() { + self.local_addresses.insert(address, 1); + } } } impl NetworkBehaviour for AutoNat { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; - type OutEvent = AutoNatEvent; + type OutEvent = (); fn new_handler(&mut self) -> Self::ProtocolsHandler { self.inner.new_handler() @@ -85,7 +97,17 @@ impl NetworkBehaviour for AutoNat { endpoint: &ConnectedPoint, ) { self.inner - .inject_connection_established(peer, conn, endpoint) + .inject_connection_established(peer, conn, endpoint); + if !self.pending_outbound.contains(peer) { + self.send_request.push_back(*peer); + } + if let ConnectedPoint::Dialer { address } = endpoint { + if let Some(channel) = self.pending_inbound.remove(peer) { + let _ = self + .inner + .send_response(channel, DialResponse::Ok(address.clone())); + } + } } fn inject_connection_closed( @@ -96,7 +118,10 @@ impl NetworkBehaviour for AutoNat { handler: ::Handler, ) { self.inner - .inject_connection_closed(peer, conn, endpoint, handler) + .inject_connection_closed(peer, conn, endpoint, handler); + // Channel can be dropped, as the underlying substream already closed. + self.pending_inbound.remove(peer); + self.send_request.retain(|p| p != peer); } fn inject_address_change( @@ -106,16 +131,34 @@ impl NetworkBehaviour for AutoNat { old: &ConnectedPoint, new: &ConnectedPoint, ) { - self.inner.inject_address_change(peer, conn, old, new) + self.inner.inject_address_change(peer, conn, old, new); + if let ConnectedPoint::Listener { + local_addr: old_addr, + .. + } = old + { + match new { + ConnectedPoint::Listener { + local_addr: new_addr, + .. + } if old_addr != new_addr => { + self.local_addresses.remove(old_addr); + if !self.local_addresses.contains_key(new_addr) { + self.local_addresses.insert(new_addr.clone(), 1); + } + } + _ => {} + } + } } fn inject_event( &mut self, - peer: PeerId, + peer_id: PeerId, conn: ConnectionId, event: RequestResponseHandlerEvent, ) { - self.inner.inject_event(peer, conn, event) + self.inner.inject_event(peer_id, conn, event) } fn inject_addr_reach_failure( @@ -133,7 +176,12 @@ impl NetworkBehaviour for AutoNat { handler: Self::ProtocolsHandler, error: libp2p_swarm::DialError, ) { - self.inner.inject_dial_failure(peer_id, handler, error) + self.inner.inject_dial_failure(peer_id, handler, error); + if let Some(channel) = self.pending_inbound.remove(peer_id) { + let _ = self + .inner + .send_response(channel, DialResponse::Err(ResponseError::DialError)); + } } fn inject_listen_failure( @@ -151,11 +199,15 @@ impl NetworkBehaviour for AutoNat { } fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_new_listen_addr(id, addr) + self.inner.inject_new_listen_addr(id, addr); + if !self.local_addresses.contains_key(addr) { + self.local_addresses.insert(addr.clone(), 0); + } } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_expired_listen_addr(id, addr) + self.inner.inject_expired_listen_addr(id, addr); + self.local_addresses.remove(addr); } fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { @@ -167,11 +219,19 @@ impl NetworkBehaviour for AutoNat { } fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - self.inner.inject_new_external_addr(addr) + self.inner.inject_new_external_addr(addr); + match self.local_addresses.get_mut(addr) { + Some(score) if *score == 0 => *score = 1, + Some(_) => {}, + None => { + self.local_addresses.insert(addr.clone(), 1); + } + } } fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - self.inner.inject_expired_external_addr(addr) + self.inner.inject_expired_external_addr(addr); + self.local_addresses.remove(addr); } fn poll( @@ -180,46 +240,62 @@ impl NetworkBehaviour for AutoNat { params: &mut impl PollParameters, ) -> Poll> { loop { + if let Some(peer_id) = self.send_request.pop_front() { + let mut scores: Vec<(Multiaddr, FiniteAddrScore)> = + self.local_addresses.clone().into_iter().collect(); + // Sort so that the address with the highest score will be dialed first by the remote. + scores.sort_by(|(_, score_a), (_, score_b)| score_b.cmp(score_a)); + let addrs = scores.into_iter().map(|(a, _)| a).collect(); + self.inner + .send_request(&peer_id, DialRequest { peer_id, addrs }); + } match self.inner.poll(cx, params) { Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::Message { peer, message }, )) => match message { RequestResponseMessage::Request { - request_id, - request, + request_id: _, + request: DialRequest { peer_id, addrs }, channel, } => { - println!("{} {:?} {:?} {:?}", peer, request_id, request, channel); + for addr in addrs { + self.inner.add_address(&peer, addr) + } + // TODO: Handle if there is already a pending request. + self.pending_inbound.insert(peer_id, channel); + return Poll::Ready(NetworkBehaviourAction::DialPeer { + peer_id: peer, + handler: self.inner.new_handler(), + condition: DialPeerCondition::Always, + }); } RequestResponseMessage::Response { - request_id, + request_id: _, response, } => { - println!("{} {:?} {:?}", peer, request_id, response); + self.pending_outbound.remove(&peer); + if let DialResponse::Ok(address) = response { + let score = self.local_addresses.entry(address.clone()).or_insert(1); + *score += 1; + return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { + address, + score: AddressScore::Finite(*score), + }); + } } }, Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::ResponseSent { peer, request_id }, - )) => { - println!("response sent {} {:?}", peer, request_id); - } + RequestResponseEvent::ResponseSent { .. }, + )) => {} Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { - peer, - request_id, - error, - }, + RequestResponseEvent::OutboundFailure { peer, .. }, )) => { - println!("outbound failure {} {:?} {:?}", peer, request_id, error); + self.pending_outbound.remove(&peer); } Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::InboundFailure { - peer, - error, - request_id, - }, + RequestResponseEvent::InboundFailure { peer, .. }, )) => { - println!("inbound failure {} {:?} {:?}", peer, request_id, error); + self.pending_inbound.remove(&peer); } Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) => { return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 891ac5ff44b..25530a3a40e 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -20,9 +20,9 @@ //! Implementation of the [AutoNAT] protocol. -pub use self::autonat::{AutoNat, AutoNatEvent, DialResponse}; +pub use self::behaviour::AutoNat; -mod autonat; +mod behaviour; mod protocol; mod structs_proto { diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index 30d9d6f9962..b7ddd037764 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -19,7 +19,6 @@ // DEALINGS IN THE SOFTWARE. use crate::structs_proto; -pub use crate::structs_proto::message::ResponseStatus; use async_trait::async_trait; use futures::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use libp2p_core::{upgrade, Multiaddr, PeerId}; @@ -86,7 +85,7 @@ impl RequestResponseCodec for AutoNatCodec { where T: AsyncWrite + Send + Unpin, { - upgrade::write_length_prefixed(io, data.to_bytes()).await?; + upgrade::write_length_prefixed(io, data.into_bytes()).await?; io.close().await } @@ -99,7 +98,7 @@ impl RequestResponseCodec for AutoNatCodec { where T: AsyncWrite + Send + Unpin, { - upgrade::write_length_prefixed(io, data.to_bytes()).await?; + upgrade::write_length_prefixed(io, data.into_bytes()).await?; io.close().await } } @@ -149,7 +148,7 @@ impl DialRequest { Ok(Self { peer_id, addrs }) } - pub fn to_bytes(self) -> Vec { + pub fn into_bytes(self) -> Vec { let peer_id = self.peer_id.to_bytes(); let addrs = self.addrs.into_iter().map(|addr| addr.to_vec()).collect(); @@ -158,7 +157,7 @@ impl DialRequest { dial: Some(structs_proto::message::Dial { peer: Some(structs_proto::message::PeerInfo { id: Some(peer_id), - addrs: addrs, + addrs, }), }), dial_response: None, @@ -171,10 +170,46 @@ impl DialRequest { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ResponseError { + DialError, + DialRefused, + BadRequest, + InternalError, +} + +impl From for i32 { + fn from(t: ResponseError) -> Self { + match t { + ResponseError::DialError => 100, + ResponseError::DialRefused => 101, + ResponseError::BadRequest => 200, + ResponseError::InternalError => 300, + } + } +} + +impl TryFrom for ResponseError { + type Error = io::Error; + + fn try_from(value: i32) -> Result { + match value { + 100 => Ok(ResponseError::DialError), + 101 => Ok(ResponseError::DialRefused), + 200 => Ok(ResponseError::BadRequest), + 300 => Ok(ResponseError::InternalError), + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid response error type", + )), + } + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum DialResponse { Ok(Multiaddr), - Err(ResponseStatus, String), + Err(ResponseError), } impl DialResponse { @@ -197,13 +232,11 @@ impl DialResponse { } Some(structs_proto::message::DialResponse { status: Some(status), - status_text, + status_text: _, addr: None, }) => { - let status = ResponseStatus::from_i32(status).ok_or_else(|| { - io::Error::new(io::ErrorKind::InvalidData, "invalid status code") - })?; - Self::Err(status, status_text.unwrap_or_default()) + let status = ResponseError::try_from(status)?; + Self::Err(status) } _ => { return Err(io::Error::new( @@ -214,16 +247,16 @@ impl DialResponse { }) } - pub fn to_bytes(self) -> Vec { + pub fn into_bytes(self) -> Vec { let dial_response = match self { Self::Ok(addr) => structs_proto::message::DialResponse { status: Some(0), status_text: None, addr: Some(addr.to_vec()), }, - Self::Err(status, status_text) => structs_proto::message::DialResponse { - status: Some(status as _), - status_text: Some(status_text), + Self::Err(status) => structs_proto::message::DialResponse { + status: Some(status.into()), + status_text: None, addr: None, }, }; @@ -254,7 +287,7 @@ mod tests { "/ip4/192.168.1.42/tcp/30333".parse().unwrap(), ], }; - let bytes = request.clone().to_bytes(); + let bytes = request.clone().into_bytes(); let request2 = DialRequest::from_bytes(&bytes).unwrap(); assert_eq!(request, request2); } @@ -262,15 +295,15 @@ mod tests { #[test] fn test_response_ok_encode_decode() { let response = DialResponse::Ok("/ip4/8.8.8.8/tcp/30333".parse().unwrap()); - let bytes = response.clone().to_bytes(); + let bytes = response.clone().into_bytes(); let response2 = DialResponse::from_bytes(&bytes).unwrap(); assert_eq!(response, response2); } #[test] fn test_response_err_encode_decode() { - let response = DialResponse::Err(ResponseStatus::EDialError, "failed to dial".into()); - let bytes = response.clone().to_bytes(); + let response = DialResponse::Err(ResponseError::DialError); + let bytes = response.clone().into_bytes(); let response2 = DialResponse::from_bytes(&bytes).unwrap(); assert_eq!(response, response2); } From 2e042d8e27659fa448578a7269e5c35be1bb10bb Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 3 Oct 2021 01:17:58 +0200 Subject: [PATCH 05/69] protocols/autonat: clippy::module_name_repetitions --- protocols/autonat/src/behaviour.rs | 10 +++++----- protocols/autonat/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 61f4315e87c..aa6aa241803 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -39,7 +39,7 @@ use std::{ type FiniteAddrScore = u32; -pub struct AutoNat { +pub struct Behaviour { inner: RequestResponse, local_addresses: HashMap, pending_inbound: HashMap>, @@ -47,7 +47,7 @@ pub struct AutoNat { send_request: VecDeque, } -impl Default for AutoNat { +impl Default for Behaviour { fn default() -> Self { let protocols = iter::once((AutoNatProtocol, ProtocolSupport::Full)); let cfg = RequestResponseConfig::default(); @@ -62,7 +62,7 @@ impl Default for AutoNat { } } -impl AutoNat { +impl Behaviour { pub fn add_local_address(&mut self, address: Multiaddr) { if self.local_addresses.get(&address).is_none() { self.local_addresses.insert(address, 1); @@ -70,7 +70,7 @@ impl AutoNat { } } -impl NetworkBehaviour for AutoNat { +impl NetworkBehaviour for Behaviour { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; type OutEvent = (); @@ -222,7 +222,7 @@ impl NetworkBehaviour for AutoNat { self.inner.inject_new_external_addr(addr); match self.local_addresses.get_mut(addr) { Some(score) if *score == 0 => *score = 1, - Some(_) => {}, + Some(_) => {} None => { self.local_addresses.insert(addr.clone(), 1); } diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 25530a3a40e..4d6eec86124 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -20,7 +20,7 @@ //! Implementation of the [AutoNAT] protocol. -pub use self::behaviour::AutoNat; +pub use self::behaviour::Behaviour; mod behaviour; mod protocol; From 3fc78f008829cedaba9bbd46a96d83851ac79b15 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 3 Oct 2021 03:21:01 +0200 Subject: [PATCH 06/69] protocols/autonat/behaviour: timeout config --- protocols/autonat/src/behaviour.rs | 31 +++++++++++++++++++++++++++--- protocols/autonat/src/lib.rs | 2 +- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index aa6aa241803..88694aeb47b 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -35,10 +35,30 @@ use std::{ collections::{HashMap, HashSet, VecDeque}, iter, task::{Context, Poll}, + time::Duration, }; type FiniteAddrScore = u32; +pub struct Config { + timeout: Duration, +} + +impl Default for Config { + fn default() -> Self { + Config { + timeout: Duration::from_secs(5), + } + } +} + +impl Config { + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } +} + pub struct Behaviour { inner: RequestResponse, local_addresses: HashMap, @@ -49,8 +69,15 @@ pub struct Behaviour { impl Default for Behaviour { fn default() -> Self { + Behaviour::new(Config::default()) + } +} + +impl Behaviour { + pub fn new(config: Config) -> Self { let protocols = iter::once((AutoNatProtocol, ProtocolSupport::Full)); - let cfg = RequestResponseConfig::default(); + let mut cfg = RequestResponseConfig::default(); + cfg.set_request_timeout(config.timeout); let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); Self { inner, @@ -60,9 +87,7 @@ impl Default for Behaviour { send_request: VecDeque::default(), } } -} -impl Behaviour { pub fn add_local_address(&mut self, address: Multiaddr) { if self.local_addresses.get(&address).is_none() { self.local_addresses.insert(address, 1); diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 4d6eec86124..069c2ae60bb 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -20,7 +20,7 @@ //! Implementation of the [AutoNAT] protocol. -pub use self::behaviour::Behaviour; +pub use self::behaviour::{Behaviour, Config}; mod behaviour; mod protocol; From 1cc2fe13a9f530ce9e9cb1c06c7e04ee09f4d712 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 3 Oct 2021 21:13:31 +0200 Subject: [PATCH 07/69] protocols/autonat: rustdoc::broken-intra-doc-links --- protocols/autonat/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 069c2ae60bb..60984000fbe 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Implementation of the [AutoNAT] protocol. +//! Implementation of the AutoNAT protocol. pub use self::behaviour::{Behaviour, Config}; From 8650b97f54488bcaf12ee4c762b3cdace60a1fba Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 3 Oct 2021 22:22:23 +0200 Subject: [PATCH 08/69] protocols/autonat/behaviour: smaller fixes, docs --- protocols/autonat/src/behaviour.rs | 109 +++++++++++++++-------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 88694aeb47b..f16355535d3 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -40,7 +40,9 @@ use std::{ type FiniteAddrScore = u32; +/// Config for the [`Behaviour`]. pub struct Config { + // Timeout for requests. timeout: Duration, } @@ -53,18 +55,26 @@ impl Default for Config { } impl Config { + /// Set the timeout for dial-requests. pub fn with_timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; self } } +/// Network Behaviour for AutoNAT. pub struct Behaviour { + // Inner protocol for sending requests and receiving the response. inner: RequestResponse, - local_addresses: HashMap, - pending_inbound: HashMap>, - pending_outbound: HashSet, - send_request: VecDeque, + // Local listening addresses with a score indicating their reachability. + // The score increases each time a remote peer successfully dials this address. + addresses: HashMap, + // Ongoing inbound requests, where no response has been sent back to the remote yet. + ongoing_inbound: HashMap>, + // Ongoing outbound dial-requests, where no response has been received from the remote yet. + ongoing_outbound: HashSet, + // Recently connected peers to which we want to send a dial-request. + pending_requests: VecDeque, } impl Default for Behaviour { @@ -81,18 +91,27 @@ impl Behaviour { let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); Self { inner, - local_addresses: HashMap::default(), - pending_inbound: HashMap::default(), - pending_outbound: HashSet::default(), - send_request: VecDeque::default(), + addresses: HashMap::default(), + ongoing_inbound: HashMap::default(), + ongoing_outbound: HashSet::default(), + pending_requests: VecDeque::default(), } } + /// Add a new address to the address list that is send to remote peers in a dial-request. pub fn add_local_address(&mut self, address: Multiaddr) { - if self.local_addresses.get(&address).is_none() { - self.local_addresses.insert(address, 1); + if self.addresses.get(&address).is_none() { + self.addresses.insert(address, 1); } } + + /// Get the list of local addresses with their current score. + /// + /// The score of an address increases each time a remote peer successfully dialed us via this address. + /// Therefore higher scores indicate a higher reachability. + pub fn address_list(&self) -> impl Iterator { + self.addresses.iter() + } } impl NetworkBehaviour for Behaviour { @@ -112,7 +131,8 @@ impl NetworkBehaviour for Behaviour { } fn inject_disconnected(&mut self, peer: &PeerId) { - self.inner.inject_disconnected(peer) + self.inner.inject_disconnected(peer); + self.ongoing_inbound.remove(peer); } fn inject_connection_established( @@ -123,11 +143,16 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_established(peer, conn, endpoint); - if !self.pending_outbound.contains(peer) { - self.send_request.push_back(*peer); + + // Initiate a new dial request if there is none pending. + if !self.ongoing_outbound.contains(peer) { + self.pending_requests.push_back(*peer); } + if let ConnectedPoint::Dialer { address } = endpoint { - if let Some(channel) = self.pending_inbound.remove(peer) { + if let Some(channel) = self.ongoing_inbound.remove(peer) { + // Successfully dialed one of the addresses from the remote peer. + // TODO: Check if the address was part of the list received in the dial-request. let _ = self .inner .send_response(channel, DialResponse::Ok(address.clone())); @@ -144,9 +169,6 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_closed(peer, conn, endpoint, handler); - // Channel can be dropped, as the underlying substream already closed. - self.pending_inbound.remove(peer); - self.send_request.retain(|p| p != peer); } fn inject_address_change( @@ -157,24 +179,6 @@ impl NetworkBehaviour for Behaviour { new: &ConnectedPoint, ) { self.inner.inject_address_change(peer, conn, old, new); - if let ConnectedPoint::Listener { - local_addr: old_addr, - .. - } = old - { - match new { - ConnectedPoint::Listener { - local_addr: new_addr, - .. - } if old_addr != new_addr => { - self.local_addresses.remove(old_addr); - if !self.local_addresses.contains_key(new_addr) { - self.local_addresses.insert(new_addr.clone(), 1); - } - } - _ => {} - } - } } fn inject_event( @@ -202,7 +206,8 @@ impl NetworkBehaviour for Behaviour { error: libp2p_swarm::DialError, ) { self.inner.inject_dial_failure(peer_id, handler, error); - if let Some(channel) = self.pending_inbound.remove(peer_id) { + if let Some(channel) = self.ongoing_inbound.remove(peer_id) { + // Failed to dial any of the addresses sent by the remote peer in their dial-request. let _ = self .inner .send_response(channel, DialResponse::Err(ResponseError::DialError)); @@ -225,14 +230,14 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - if !self.local_addresses.contains_key(addr) { - self.local_addresses.insert(addr.clone(), 0); + if !self.addresses.contains_key(addr) { + self.addresses.insert(addr.clone(), 0); } } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_expired_listen_addr(id, addr); - self.local_addresses.remove(addr); + self.addresses.remove(addr); } fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { @@ -245,18 +250,19 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - match self.local_addresses.get_mut(addr) { + // Add the address to the local address list. + match self.addresses.get_mut(addr) { Some(score) if *score == 0 => *score = 1, Some(_) => {} None => { - self.local_addresses.insert(addr.clone(), 1); + self.addresses.insert(addr.clone(), 1); } } } fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_expired_external_addr(addr); - self.local_addresses.remove(addr); + self.addresses.remove(addr); } fn poll( @@ -265,9 +271,8 @@ impl NetworkBehaviour for Behaviour { params: &mut impl PollParameters, ) -> Poll> { loop { - if let Some(peer_id) = self.send_request.pop_front() { - let mut scores: Vec<(Multiaddr, FiniteAddrScore)> = - self.local_addresses.clone().into_iter().collect(); + if let Some(peer_id) = self.pending_requests.pop_front() { + let mut scores: Vec<_> = self.addresses.clone().into_iter().collect(); // Sort so that the address with the highest score will be dialed first by the remote. scores.sort_by(|(_, score_a), (_, score_b)| score_b.cmp(score_a)); let addrs = scores.into_iter().map(|(a, _)| a).collect(); @@ -283,11 +288,12 @@ impl NetworkBehaviour for Behaviour { request: DialRequest { peer_id, addrs }, channel, } => { + // Add all addresses to the address book. for addr in addrs { self.inner.add_address(&peer, addr) } - // TODO: Handle if there is already a pending request. - self.pending_inbound.insert(peer_id, channel); + // TODO: Handle if there is already a ongoing request. + self.ongoing_inbound.insert(peer_id, channel); return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id: peer, handler: self.inner.new_handler(), @@ -298,9 +304,10 @@ impl NetworkBehaviour for Behaviour { request_id: _, response, } => { - self.pending_outbound.remove(&peer); + self.ongoing_outbound.remove(&peer); if let DialResponse::Ok(address) = response { - let score = self.local_addresses.entry(address.clone()).or_insert(1); + // Increase score of the successfully dialed address. + let score = self.addresses.entry(address.clone()).or_insert(1); *score += 1; return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, @@ -315,12 +322,12 @@ impl NetworkBehaviour for Behaviour { Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::OutboundFailure { peer, .. }, )) => { - self.pending_outbound.remove(&peer); + self.ongoing_outbound.remove(&peer); } Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::InboundFailure { peer, .. }, )) => { - self.pending_inbound.remove(&peer); + self.ongoing_inbound.remove(&peer); } Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) => { return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) From 806baaa446582b0f7fd32fdd724683abe961e8f9 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 25 Oct 2021 02:57:36 +0200 Subject: [PATCH 09/69] protocols/autonat/behaviour: refactor behaviour --- protocols/autonat/src/behaviour.rs | 295 +++++++++++++++++++++-------- 1 file changed, 212 insertions(+), 83 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 4df272e4ee5..0794f9c38c2 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -28,95 +28,168 @@ use libp2p_request_response::{ RequestResponseEvent, RequestResponseMessage, ResponseChannel, }; use libp2p_swarm::{ - AddressScore, DialError, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, + DialError, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, + PollParameters, }; use std::{ - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, VecDeque}, iter, task::{Context, Poll}, - time::Duration, + time::{Duration, Instant}, }; -type FiniteAddrScore = u32; - +#[derive(Debug, Clone)] /// Config for the [`Behaviour`]. pub struct Config { // Timeout for requests. timeout: Duration, + + // Config if the peer should frequently re-determine its status. + auto_retry: Option, + + // Config if the current peer also serves as server for other peers./ + // In case of `None`, the local peer will never do dial-attempts to other peers. + server: Option, } -impl Default for Config { - fn default() -> Self { - Config { - timeout: Duration::from_secs(5), - } - } +#[derive(Debug, Clone)] +pub struct AutoRetry { + interval: Duration, + min_peers: usize, + max_peers: usize, + required_success: usize, } -impl Config { - /// Set the timeout for dial-requests. - pub fn with_timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } +#[derive(Debug, Clone)] +pub struct ServerConfig { + max_addresses: usize, + max_ongoing: usize, +} + +#[derive(Debug, Clone)] +pub enum Reachability { + Public(Multiaddr), + Private, + Unknown, +} + +#[derive(Debug, Clone)] +pub struct Probe { + server_count: usize, + pending_servers: Vec, + addresses: HashMap, + required_success: usize, +} + +#[derive(Debug, Clone)] +pub enum NatStatus { + Public { + address: Multiaddr, + server_count: usize, + successes: usize, + }, + Private { + server_count: usize, + tried_addresses: Vec<(Multiaddr, usize)>, + }, } /// Network Behaviour for AutoNAT. pub struct Behaviour { // Inner protocol for sending requests and receiving the response. inner: RequestResponse, - // Local listening addresses with a score indicating their reachability. - // The score increases each time a remote peer successfully dials this address. - addresses: HashMap, + // Ongoing inbound requests, where no response has been sent back to the remote yet. ongoing_inbound: HashMap>, + // Ongoing outbound dial-requests, where no response has been received from the remote yet. - ongoing_outbound: HashSet, - // Recently connected peers to which we want to send a dial-request. - pending_requests: VecDeque, -} + ongoing_outbound: Option, -impl Default for Behaviour { - fn default() -> Self { - Behaviour::new(Config::default()) - } + // Manually initiated probe. + pending_probe: Option, + + // List of trusted public peers that are probed when attempting to determine the auto-nat status. + static_servers: Vec, + + connected: Vec, + + status: Reachability, + + server_config: Option, + + auto_retry: Option, + + last_probe: Instant, } impl Behaviour { pub fn new(config: Config) -> Self { - let protocols = iter::once((AutoNatProtocol, ProtocolSupport::Full)); + let proto_support = match config.server { + Some(_) => ProtocolSupport::Full, + None => ProtocolSupport::Outbound, + }; + let protocols = iter::once((AutoNatProtocol, proto_support)); let mut cfg = RequestResponseConfig::default(); cfg.set_request_timeout(config.timeout); let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); Self { inner, - addresses: HashMap::default(), ongoing_inbound: HashMap::default(), - ongoing_outbound: HashSet::default(), - pending_requests: VecDeque::default(), + ongoing_outbound: None, + pending_probe: None, + static_servers: Vec::default(), + connected: Vec::default(), + status: Reachability::Unknown, + server_config: config.server, + auto_retry: None, + last_probe: Instant::now(), } } - /// Add a new address to the address list that is send to remote peers in a dial-request. - pub fn add_local_address(&mut self, address: Multiaddr) { - if self.addresses.get(&address).is_none() { - self.addresses.insert(address, 1); + // Manually retry determination of NAT status. + // + // Return whether there is already an ongoing Probe. + pub fn retry_nat_status( + &mut self, + required_success: usize, + mut servers: Vec, + extend_with_static: bool, + extend_with_connected: Option, + ) -> bool { + if extend_with_static { + servers.extend(self.static_servers.clone()) + } + if let Some(count) = extend_with_connected { + // TODO: use random set instead + for i in 0..count { + match self.connected.get(i) { + Some(peer) => servers.push(*peer), + None => break, + } + } } + let probe = Probe { + server_count: servers.len(), + pending_servers: servers, + addresses: HashMap::new(), + required_success, + }; + self.pending_probe.replace(probe); + self.ongoing_outbound.is_some() + } + + pub fn reachability(&self) -> Reachability { + self.status.clone() } - /// Get the list of local addresses with their current score. - /// - /// The score of an address increases each time a remote peer successfully dialed us via this address. - /// Therefore higher scores indicate a higher reachability. - pub fn address_list(&self) -> impl Iterator { - self.addresses.iter() + pub fn ongoing_probe(&self) -> Option<&Probe> { + self.ongoing_outbound.as_ref() } } impl NetworkBehaviour for Behaviour { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; - type OutEvent = (); + type OutEvent = NatStatus; fn new_handler(&mut self) -> Self::ProtocolsHandler { self.inner.new_handler() @@ -144,11 +217,7 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_established(peer, conn, endpoint, failed_addresses); - - // Initiate a new dial request if there is none pending. - if !self.ongoing_outbound.contains(peer) { - self.pending_requests.push_back(*peer); - } + self.connected.push(*peer); if let ConnectedPoint::Dialer { address } = endpoint { if let Some(channel) = self.ongoing_inbound.remove(peer) { @@ -170,6 +239,7 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_closed(peer, conn, endpoint, handler); + self.connected.retain(|p| p != peer); } fn inject_address_change( @@ -222,14 +292,10 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - if !self.addresses.contains_key(addr) { - self.addresses.insert(addr.clone(), 0); - } } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_expired_listen_addr(id, addr); - self.addresses.remove(addr); } fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { @@ -242,19 +308,10 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - // Add the address to the local address list. - match self.addresses.get_mut(addr) { - Some(score) if *score == 0 => *score = 1, - Some(_) => {} - None => { - self.addresses.insert(addr.clone(), 1); - } - } } fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_expired_external_addr(addr); - self.addresses.remove(addr); } fn poll( @@ -263,29 +320,41 @@ impl NetworkBehaviour for Behaviour { params: &mut impl PollParameters, ) -> Poll> { loop { - if let Some(peer_id) = self.pending_requests.pop_front() { - let mut scores: Vec<_> = self.addresses.clone().into_iter().collect(); - // Sort so that the address with the highest score will be dialed first by the remote. - scores.sort_by(|(_, score_a), (_, score_b)| score_b.cmp(score_a)); - let addrs = scores.into_iter().map(|(a, _)| a).collect(); - self.inner - .send_request(&peer_id, DialRequest { peer_id, addrs }); - } match self.inner.poll(cx, params) { Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::Message { peer, message }, )) => match message { RequestResponseMessage::Request { request_id: _, - request: DialRequest { peer_id, addrs }, + request: + DialRequest { + peer_id: _peer, + addrs: _addrs, + }, channel, } => { + let server_config = self.server_config.as_ref().unwrap(); + if self.ongoing_inbound.len() >= server_config.max_ongoing { + let response = DialResponse::Err(ResponseError::DialRefused); + let _ = self.inner.send_response(channel, response); + continue; + } + let _observed_remote_at = todo!(); + let mut _addrs = valid_addresses(_addrs, _observed_remote_at); + if _addrs.len() > server_config.max_addresses { + _addrs.drain(server_config.max_addresses..); + } + if _addrs.is_empty() { + let response = DialResponse::Err(ResponseError::DialRefused); + let _ = self.inner.send_response(channel, response); + continue; + } // Add all addresses to the address book. - for addr in addrs { + for addr in _addrs { self.inner.add_address(&peer, addr) } // TODO: Handle if there is already a ongoing request. - self.ongoing_inbound.insert(peer_id, channel); + self.ongoing_inbound.insert(_peer, channel); return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id: peer, handler: self.inner.new_handler(), @@ -296,15 +365,46 @@ impl NetworkBehaviour for Behaviour { request_id: _, response, } => { - self.ongoing_outbound.remove(&peer); - if let DialResponse::Ok(address) = response { - // Increase score of the successfully dialed address. - let score = self.addresses.entry(address.clone()).or_insert(1); - *score += 1; - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score: AddressScore::Finite(*score), - }); + let probe = match self.ongoing_outbound.as_mut() { + Some(p) if p.pending_servers.contains(&peer) => p, + _ => continue, + }; + probe.pending_servers.retain(|p| p != &peer); + match response { + DialResponse::Ok(addr) => { + if let Some(score) = probe.addresses.get_mut(&addr) { + *score += 1; + if *score >= probe.required_success { + let score = *score; + let probe = self.ongoing_outbound.take().unwrap(); + self.last_probe = Instant::now(); + let status = NatStatus::Public { + address: addr.clone(), + server_count: probe.server_count, + successes: score, + }; + self.status = Reachability::Public(addr); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent( + status, + )); + } + } + } + // TODO: Handle errors due to bad request, rejected, etc. + DialResponse::Err(_) => { + if probe.pending_servers.is_empty() { + let probe = self.ongoing_outbound.take().unwrap(); + self.last_probe = Instant::now(); + let status = NatStatus::Private { + server_count: probe.server_count, + tried_addresses: probe.addresses.into_iter().collect(), + }; + self.status = Reachability::Private; + return Poll::Ready(NetworkBehaviourAction::GenerateEvent( + status, + )); + } + } } } }, @@ -314,7 +414,11 @@ impl NetworkBehaviour for Behaviour { Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::OutboundFailure { peer, .. }, )) => { - self.ongoing_outbound.remove(&peer); + let probe = match self.ongoing_outbound.as_mut() { + Some(p) if p.pending_servers.contains(&peer) => p, + _ => continue, + }; + probe.pending_servers.retain(|p| p != &peer); } Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::InboundFailure { peer, .. }, @@ -363,6 +467,31 @@ impl NetworkBehaviour for Behaviour { } Poll::Pending => return Poll::Pending, } + if let Some(probe) = self.pending_probe.take() { + let mut records: Vec<_> = params.external_addresses().collect(); + // Sort so that the address with the highest score will be dialed first by the remote. + records.sort_by(|record_a, record_b| record_b.score.cmp(&record_a.score)); + let addrs: Vec = records.into_iter().map(|r| r.addr).collect(); + for peer_id in probe.pending_servers.clone() { + self.inner.send_request( + &peer_id, + DialRequest { + peer_id, + addrs: addrs.clone(), + }, + ); + } + let _ = self.ongoing_outbound.insert(probe); + } else if let Some(auto_retry) = self.auto_retry.as_ref() { + if Instant::now().duration_since(self.last_probe) > auto_retry.interval { + // TODO: auto retry nat status + } + } } } } + +// Filter demanded dial addresses for validity, to prevent abuse. +fn valid_addresses(_demanded: Vec, _observed_remote_at: Multiaddr) -> Vec { + todo!() +} From ae63515ec2aa92736ded08d753825c0637251ca9 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 28 Oct 2021 23:17:11 +0200 Subject: [PATCH 10/69] protocols/autonat/behaviour: config, filtering of addrs, errors --- protocols/autonat/Cargo.toml | 8 +- protocols/autonat/src/behaviour.rs | 319 ++++++++++++++++++++--------- 2 files changed, 228 insertions(+), 99 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index da22e162605..385ecfa7381 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libp2p-autonat" version = "0.20.0" -authors = ["David Craven "] +authors = ["David Craven ", "Elena Frank "] edition = "2018" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,9 +12,9 @@ categories = ["network-programming", "asynchronous"] prost-build = "0.6" [dependencies] -async-trait = "0.1.51" -futures = "0.3.17" +async-trait = "0.1" +futures = "0.3" libp2p-core = { version = "0.30.0-rc.1", path = "../../core" } libp2p-swarm = { version = "0.31.0-rc.1", path = "../../swarm" } libp2p-request-response = { version = "0.13.0-rc.1", path = "../request-response" } -prost = "0.8.0" +prost = "0.8" diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 0794f9c38c2..a38ebd339ea 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -21,6 +21,7 @@ use crate::protocol::{AutoNatCodec, AutoNatProtocol, DialRequest, DialResponse, ResponseError}; use libp2p_core::{ connection::{ConnectionId, ListenerId}, + multiaddr::Protocol, ConnectedPoint, Multiaddr, PeerId, }; use libp2p_request_response::{ @@ -28,11 +29,11 @@ use libp2p_request_response::{ RequestResponseEvent, RequestResponseMessage, ResponseChannel, }; use libp2p_swarm::{ - DialError, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, + AddressRecord, DialError, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }; use std::{ - collections::{HashMap, VecDeque}, + collections::HashMap, iter, task::{Context, Poll}, time::{Duration, Instant}, @@ -42,28 +43,39 @@ use std::{ /// Config for the [`Behaviour`]. pub struct Config { // Timeout for requests. - timeout: Duration, + pub timeout: Duration, // Config if the peer should frequently re-determine its status. - auto_retry: Option, + pub auto_retry: Option, - // Config if the current peer also serves as server for other peers./ - // In case of `None`, the local peer will never do dial-attempts to other peers. - server: Option, + // Config if the current peer also serves as server for other peers. + // In case of `None`, the local peer will never do dial-attempts for other peers. + pub server: Option, } +/// Automatically retry the current NAT status at a certain frequency. #[derive(Debug, Clone)] pub struct AutoRetry { - interval: Duration, - min_peers: usize, - max_peers: usize, - required_success: usize, + // Interval in which the NAT should be tested. + pub interval: Duration, + // Max peers to send a dial-request to. + pub max_peers: usize, + // Whether the static list of servers should be extended with currently connected peers, up to `max_peers`. + pub extend_with_connected: bool, + // Minimum amount of valid response (DialResponse::Ok || ResponseError::DialError) + // from a remote, for it to count as proper attempt. + pub min_valid: usize, + // Required amount of successful dials for an address for it to count as public. + pub required_success: usize, } +/// Config if the local peer is a server for other peers and does dial-attempts for them. #[derive(Debug, Clone)] pub struct ServerConfig { - max_addresses: usize, - max_ongoing: usize, + /// Max addresses that are tried per peer. + pub max_addresses: usize, + /// Max simultaneous autonat dial-attempts. + pub max_ongoing: usize, } #[derive(Debug, Clone)] @@ -75,10 +87,14 @@ pub enum Reachability { #[derive(Debug, Clone)] pub struct Probe { + required_success: usize, + min_valid: usize, server_count: usize, - pending_servers: Vec, addresses: HashMap, - required_success: usize, + /// Disqualified dial-requests where the remote could not be reached, or rejected the dial-request. + dismissed: usize, + pending_servers: Vec, + errors: Vec<(PeerId, ResponseError)>, } #[derive(Debug, Clone)] @@ -91,6 +107,12 @@ pub enum NatStatus { Private { server_count: usize, tried_addresses: Vec<(Multiaddr, usize)>, + errors: Vec<(PeerId, ResponseError)>, + }, + Unknown { + server_count: usize, + tried_addresses: Vec<(Multiaddr, usize)>, + errors: Vec<(PeerId, ResponseError)>, }, } @@ -100,7 +122,7 @@ pub struct Behaviour { inner: RequestResponse, // Ongoing inbound requests, where no response has been sent back to the remote yet. - ongoing_inbound: HashMap>, + ongoing_inbound: HashMap, ResponseChannel)>, // Ongoing outbound dial-requests, where no response has been received from the remote yet. ongoing_outbound: Option, @@ -111,9 +133,10 @@ pub struct Behaviour { // List of trusted public peers that are probed when attempting to determine the auto-nat status. static_servers: Vec, - connected: Vec, + // List of connected peers and the address we last observed them at. + connected: HashMap, - status: Reachability, + reachability: Reachability, server_config: Option, @@ -138,8 +161,8 @@ impl Behaviour { ongoing_outbound: None, pending_probe: None, static_servers: Vec::default(), - connected: Vec::default(), - status: Reachability::Unknown, + connected: HashMap::default(), + reachability: Reachability::Unknown, server_config: config.server, auto_retry: None, last_probe: Instant::now(), @@ -152,38 +175,81 @@ impl Behaviour { pub fn retry_nat_status( &mut self, required_success: usize, - mut servers: Vec, + servers: Vec, extend_with_static: bool, - extend_with_connected: Option, + extend_with_connected: bool, + max_peers: usize, + min_valid: usize, ) -> bool { - if extend_with_static { + let probe = self.new_probe( + required_success, + servers, + extend_with_static, + extend_with_connected, + max_peers, + min_valid, + ); + self.pending_probe.replace(probe); + self.ongoing_outbound.is_some() + } + + pub fn reachability(&self) -> Reachability { + self.reachability.clone() + } + + pub fn ongoing_probe(&self) -> Option<&Probe> { + self.ongoing_outbound.as_ref() + } + + fn new_probe( + &self, + required_success: usize, + mut servers: Vec, + extend_with_static: bool, + extend_with_connected: bool, + max_peers: usize, + min_valid: usize, + ) -> Probe { + servers.truncate(max_peers); + if servers.len() < max_peers && extend_with_static { servers.extend(self.static_servers.clone()) } - if let Some(count) = extend_with_connected { - // TODO: use random set instead - for i in 0..count { - match self.connected.get(i) { - Some(peer) => servers.push(*peer), - None => break, + if servers.len() < max_peers && extend_with_connected { + let mut connected = self.connected.iter(); + // TODO: use random set + for _ in 0..connected.len() { + let (peer, _) = connected.next().unwrap(); + servers.push(*peer); + if servers.len() >= max_peers { + break; } } } - let probe = Probe { + Probe { server_count: servers.len(), pending_servers: servers, addresses: HashMap::new(), required_success, - }; - self.pending_probe.replace(probe); - self.ongoing_outbound.is_some() - } - - pub fn reachability(&self) -> Reachability { - self.status.clone() + dismissed: 0, + errors: Vec::new(), + min_valid, + } } - pub fn ongoing_probe(&self) -> Option<&Probe> { - self.ongoing_outbound.as_ref() + fn do_probe(&mut self, probe: Probe, mut addr_records: Vec) { + // Sort so that the address with the highest score will be dialed first by the remote. + addr_records.sort_by(|record_a, record_b| record_b.score.cmp(&record_a.score)); + let addrs: Vec = addr_records.into_iter().map(|r| r.addr).collect(); + for peer_id in probe.pending_servers.clone() { + self.inner.send_request( + &peer_id, + DialRequest { + peer_id, + addrs: addrs.clone(), + }, + ); + } + let _ = self.ongoing_outbound.insert(probe); } } @@ -196,7 +262,11 @@ impl NetworkBehaviour for Behaviour { } fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { - self.inner.addresses_of_peer(peer) + if let Some(addrs) = self.ongoing_inbound.get(peer).map(|(a, _)| a.clone()) { + addrs + } else { + self.inner.addresses_of_peer(peer) + } } fn inject_connected(&mut self, peer: &PeerId) { @@ -217,17 +287,21 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_established(peer, conn, endpoint, failed_addresses); - self.connected.push(*peer); if let ConnectedPoint::Dialer { address } = endpoint { - if let Some(channel) = self.ongoing_inbound.remove(peer) { - // Successfully dialed one of the addresses from the remote peer. - // TODO: Check if the address was part of the list received in the dial-request. - let _ = self - .inner - .send_response(channel, DialResponse::Ok(address.clone())); + if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { + if addrs.contains(address) { + // Successfully dialed one of the addresses from the remote peer. + let channel = self.ongoing_inbound.remove(peer).unwrap().1; + let _ = self + .inner + .send_response(channel, DialResponse::Ok(address.clone())); + return; + } } } + self.connected + .insert(*peer, endpoint.get_remote_address().clone()); } fn inject_connection_closed( @@ -239,7 +313,7 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_closed(peer, conn, endpoint, handler); - self.connected.retain(|p| p != peer); + self.connected.retain(|p, _| p != peer); } fn inject_address_change( @@ -268,7 +342,7 @@ impl NetworkBehaviour for Behaviour { error: &DialError, ) { self.inner.inject_dial_failure(peer_id, handler, error); - if let Some(channel) = peer_id.and_then(|p| self.ongoing_inbound.remove(&p)) { + if let Some((_, channel)) = peer_id.and_then(|p| self.ongoing_inbound.remove(&p)) { // Failed to dial any of the addresses sent by the remote peer in their dial-request. let _ = self .inner @@ -326,35 +400,39 @@ impl NetworkBehaviour for Behaviour { )) => match message { RequestResponseMessage::Request { request_id: _, - request: - DialRequest { - peer_id: _peer, - addrs: _addrs, - }, + request: DialRequest { peer_id, addrs }, channel, } => { - let server_config = self.server_config.as_ref().unwrap(); - if self.ongoing_inbound.len() >= server_config.max_ongoing { + let server_config = self + .server_config + .as_ref() + .expect("Server config is present."); + + // Refuse dial if: + // - the peer-id in the dial request is not the remote peer, + // - there is already an ongoing request to the remote + // - max simultaneous autonat dial-requests are reached. + if peer != peer_id + || self.ongoing_inbound.contains_key(&peer) + || self.ongoing_inbound.len() >= server_config.max_ongoing + { let response = DialResponse::Err(ResponseError::DialRefused); let _ = self.inner.send_response(channel, response); continue; } - let _observed_remote_at = todo!(); - let mut _addrs = valid_addresses(_addrs, _observed_remote_at); - if _addrs.len() > server_config.max_addresses { - _addrs.drain(server_config.max_addresses..); - } - if _addrs.is_empty() { + + let observed_remote_at = + self.connected.get(&peer).expect("Peer is connected"); + let mut addrs = filter_invalid_addrs(peer, addrs, observed_remote_at); + addrs.truncate(server_config.max_addresses); + if addrs.is_empty() { let response = DialResponse::Err(ResponseError::DialRefused); let _ = self.inner.send_response(channel, response); continue; } - // Add all addresses to the address book. - for addr in _addrs { - self.inner.add_address(&peer, addr) - } - // TODO: Handle if there is already a ongoing request. - self.ongoing_inbound.insert(_peer, channel); + + self.ongoing_inbound.insert(peer, (addrs, channel)); + return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id: peer, handler: self.inner.new_handler(), @@ -383,23 +461,51 @@ impl NetworkBehaviour for Behaviour { server_count: probe.server_count, successes: score, }; - self.status = Reachability::Public(addr); + self.reachability = Reachability::Public(addr); return Poll::Ready(NetworkBehaviourAction::GenerateEvent( status, )); } } } - // TODO: Handle errors due to bad request, rejected, etc. - DialResponse::Err(_) => { - if probe.pending_servers.is_empty() { + DialResponse::Err(err) => { + if !matches!(err, ResponseError::DialError) { + probe.dismissed += 1; + } + probe.errors.push((peer, err)); + let remaining = probe.pending_servers.len(); + let current_highest = + probe.addresses.iter().fold(0, |acc, (_, &curr)| { + if acc > curr { + acc + } else { + curr + } + }); + if remaining < probe.required_success - current_highest + || probe.min_valid > probe.server_count - probe.dismissed + { let probe = self.ongoing_outbound.take().unwrap(); + let valid_attempts = + probe.server_count - remaining - probe.dismissed; + let status; + if valid_attempts >= probe.min_valid { + self.reachability = Reachability::Private; + status = NatStatus::Private { + server_count: probe.server_count, + errors: probe.errors, + tried_addresses: probe.addresses.into_iter().collect(), + }; + } else { + self.reachability = Reachability::Unknown; + status = NatStatus::Unknown { + server_count: probe.server_count, + errors: probe.errors, + tried_addresses: probe.addresses.into_iter().collect(), + }; + } + self.last_probe = Instant::now(); - let status = NatStatus::Private { - server_count: probe.server_count, - tried_addresses: probe.addresses.into_iter().collect(), - }; - self.status = Reachability::Private; return Poll::Ready(NetworkBehaviourAction::GenerateEvent( status, )); @@ -468,23 +574,20 @@ impl NetworkBehaviour for Behaviour { Poll::Pending => return Poll::Pending, } if let Some(probe) = self.pending_probe.take() { - let mut records: Vec<_> = params.external_addresses().collect(); - // Sort so that the address with the highest score will be dialed first by the remote. - records.sort_by(|record_a, record_b| record_b.score.cmp(&record_a.score)); - let addrs: Vec = records.into_iter().map(|r| r.addr).collect(); - for peer_id in probe.pending_servers.clone() { - self.inner.send_request( - &peer_id, - DialRequest { - peer_id, - addrs: addrs.clone(), - }, - ); - } - let _ = self.ongoing_outbound.insert(probe); + let records: Vec<_> = params.external_addresses().collect(); + self.do_probe(probe, records) } else if let Some(auto_retry) = self.auto_retry.as_ref() { if Instant::now().duration_since(self.last_probe) > auto_retry.interval { - // TODO: auto retry nat status + let probe = self.new_probe( + auto_retry.required_success, + Vec::new(), + true, + auto_retry.extend_with_connected, + auto_retry.max_peers, + auto_retry.min_valid, + ); + let records: Vec<_> = params.external_addresses().collect(); + self.do_probe(probe, records); } } } @@ -492,6 +595,32 @@ impl NetworkBehaviour for Behaviour { } // Filter demanded dial addresses for validity, to prevent abuse. -fn valid_addresses(_demanded: Vec, _observed_remote_at: Multiaddr) -> Vec { - todo!() +fn filter_invalid_addrs( + peer: PeerId, + demanded: Vec, + observed_remote_at: &Multiaddr, +) -> Vec { + let observed_ip = match observed_remote_at.into_iter().next() { + Some(Protocol::Ip4(ip)) => Protocol::Ip4(ip), + Some(Protocol::Ip6(ip)) => Protocol::Ip6(ip), + _ => return Vec::new(), + }; + demanded + .into_iter() + .filter_map(|addr| { + addr.replace(0, |proto| match proto { + Protocol::Ip4(_) => Some(observed_ip.clone()), + Protocol::Ip6(_) => Some(observed_ip.clone()), + _ => None, + })?; + // TODO: Add more filters + addr.iter() + .all(|proto| match proto { + Protocol::P2pCircuit => false, + Protocol::P2p(hash) => hash == peer.into(), + _ => true, + }) + .then(|| addr) + }) + .collect() } From 6b79e667cb2f71cd3f2ce66e28fe3cffe8a80e59 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 6 Nov 2021 21:16:57 +0100 Subject: [PATCH 11/69] protocols/autonat/behaviour: clean Behaviour::poll --- protocols/autonat/src/behaviour.rs | 321 +++++++++++++---------------- 1 file changed, 149 insertions(+), 172 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index a38ebd339ea..b92fe176d1b 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -85,6 +85,16 @@ pub enum Reachability { Unknown, } +impl From<&NatStatus> for Reachability { + fn from(status: &NatStatus) -> Self { + match status { + NatStatus::Public { address, .. } => Reachability::Public(address.clone()), + NatStatus::Private { .. } => Reachability::Private, + NatStatus::Unknown { .. } => Reachability::Unknown, + } + } +} + #[derive(Debug, Clone)] pub struct Probe { required_success: usize, @@ -251,6 +261,105 @@ impl Behaviour { } let _ = self.ongoing_outbound.insert(probe); } + + // Handle the inbound request and collect the valid addresses to be dialed. + fn handle_request(&mut self, sender: PeerId, request: DialRequest) -> Option> { + let config = self + .server_config + .as_ref() + .expect("Server config is present."); + + // Validate that the peer to be dialed is the request's sender. + if request.peer_id != sender { + return None; + } + // Check that there is no ongoing dial to the remote. + if self.ongoing_inbound.contains_key(&sender) { + return None; + } + // Check if max simultaneous autonat dial-requests are reached. + if self.ongoing_inbound.len() >= config.max_ongoing { + return None; + } + + // Filter valid addresses. + let observed_addr = self.connected.get(&sender).expect("Peer is connected"); + let mut addrs = filter_valid_addrs(sender, request.addrs, observed_addr); + addrs.truncate(config.max_addresses); + + if addrs.is_empty() { + return None; + } + + Some(addrs) + } + + fn handle_response( + &mut self, + sender: PeerId, + response: Option, + ) -> Option { + let mut probe = self.ongoing_outbound.take()?; + if !probe.pending_servers.contains(&sender) { + let _ = self.ongoing_outbound.insert(probe); + return None; + }; + probe.pending_servers.retain(|p| p != &sender); + match response { + Some(DialResponse::Ok(addr)) => self.handle_dial_ok(probe, addr), + Some(DialResponse::Err(err)) => { + if !matches!(err, ResponseError::DialError) { + probe.dismissed += 1; + } + probe.errors.push((sender, err)); + self.handle_dial_err(probe) + } + None => { + probe.dismissed += 1; + self.handle_dial_err(probe) + } + } + } + + fn handle_dial_ok(&mut self, mut probe: Probe, address: Multiaddr) -> Option { + let score = probe.addresses.get_mut(&address)?; + *score += 1; + if *score < probe.required_success { + let _ = self.ongoing_outbound.insert(probe); + return None; + } + Some(NatStatus::Public { + address: address.clone(), + server_count: probe.server_count, + successes: *score, + }) + } + + fn handle_dial_err(&mut self, probe: Probe) -> Option { + let remaining = probe.pending_servers.len(); + let current_highest = probe.addresses.iter().max_by(|(_, a), (_, b)| a.cmp(b))?.1; + + // Check if the probe can still be successful. + if remaining >= probe.required_success - current_highest { + let _ = self.ongoing_outbound.insert(probe); + return None; + } + + let valid_attempts = probe.server_count - remaining - probe.dismissed; + if valid_attempts >= probe.min_valid { + Some(NatStatus::Private { + server_count: probe.server_count, + errors: probe.errors, + tried_addresses: probe.addresses.into_iter().collect(), + }) + } else { + Some(NatStatus::Unknown { + server_count: probe.server_count, + errors: probe.errors, + tried_addresses: probe.addresses.into_iter().collect(), + }) + } + } } impl NetworkBehaviour for Behaviour { @@ -395,182 +504,50 @@ impl NetworkBehaviour for Behaviour { ) -> Poll> { loop { match self.inner.poll(cx, params) { - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::Message { peer, message }, - )) => match message { - RequestResponseMessage::Request { - request_id: _, - request: DialRequest { peer_id, addrs }, - channel, - } => { - let server_config = self - .server_config - .as_ref() - .expect("Server config is present."); - - // Refuse dial if: - // - the peer-id in the dial request is not the remote peer, - // - there is already an ongoing request to the remote - // - max simultaneous autonat dial-requests are reached. - if peer != peer_id - || self.ongoing_inbound.contains_key(&peer) - || self.ongoing_inbound.len() >= server_config.max_ongoing - { - let response = DialResponse::Err(ResponseError::DialRefused); - let _ = self.inner.send_response(channel, response); - continue; - } - - let observed_remote_at = - self.connected.get(&peer).expect("Peer is connected"); - let mut addrs = filter_invalid_addrs(peer, addrs, observed_remote_at); - addrs.truncate(server_config.max_addresses); - if addrs.is_empty() { - let response = DialResponse::Err(ResponseError::DialRefused); - let _ = self.inner.send_response(channel, response); - continue; - } - - self.ongoing_inbound.insert(peer, (addrs, channel)); - - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id: peer, - handler: self.inner.new_handler(), - condition: DialPeerCondition::Always, - }); - } - RequestResponseMessage::Response { - request_id: _, - response, - } => { - let probe = match self.ongoing_outbound.as_mut() { - Some(p) if p.pending_servers.contains(&peer) => p, - _ => continue, - }; - probe.pending_servers.retain(|p| p != &peer); - match response { - DialResponse::Ok(addr) => { - if let Some(score) = probe.addresses.get_mut(&addr) { - *score += 1; - if *score >= probe.required_success { - let score = *score; - let probe = self.ongoing_outbound.take().unwrap(); - self.last_probe = Instant::now(); - let status = NatStatus::Public { - address: addr.clone(), - server_count: probe.server_count, - successes: score, - }; - self.reachability = Reachability::Public(addr); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent( - status, - )); - } - } + Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) => match event { + RequestResponseEvent::Message { peer, message } => match message { + RequestResponseMessage::Request { + request_id: _, + request, + channel, + } => match self.handle_request(peer, request) { + Some(addrs) => { + self.ongoing_inbound.insert(peer, (addrs, channel)); + return Poll::Ready(NetworkBehaviourAction::DialPeer { + peer_id: peer, + handler: self.inner.new_handler(), + condition: DialPeerCondition::Always, + }); } - DialResponse::Err(err) => { - if !matches!(err, ResponseError::DialError) { - probe.dismissed += 1; - } - probe.errors.push((peer, err)); - let remaining = probe.pending_servers.len(); - let current_highest = - probe.addresses.iter().fold(0, |acc, (_, &curr)| { - if acc > curr { - acc - } else { - curr - } - }); - if remaining < probe.required_success - current_highest - || probe.min_valid > probe.server_count - probe.dismissed - { - let probe = self.ongoing_outbound.take().unwrap(); - let valid_attempts = - probe.server_count - remaining - probe.dismissed; - let status; - if valid_attempts >= probe.min_valid { - self.reachability = Reachability::Private; - status = NatStatus::Private { - server_count: probe.server_count, - errors: probe.errors, - tried_addresses: probe.addresses.into_iter().collect(), - }; - } else { - self.reachability = Reachability::Unknown; - status = NatStatus::Unknown { - server_count: probe.server_count, - errors: probe.errors, - tried_addresses: probe.addresses.into_iter().collect(), - }; - } - - self.last_probe = Instant::now(); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent( - status, - )); - } + None => { + let response = DialResponse::Err(ResponseError::DialRefused); + let _ = self.inner.send_response(channel, response); } + }, + RequestResponseMessage::Response { + request_id: _, + response, + } => { + if let Some(status) = self.handle_response(peer, Some(response)) { + self.reachability = Reachability::from(&status); + self.last_probe = Instant::now(); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); + } + } + }, + RequestResponseEvent::ResponseSent { .. } => {} + RequestResponseEvent::OutboundFailure { peer, .. } => { + if let Some(status) = self.handle_response(peer, None) { + self.reachability = Reachability::from(&status); + self.last_probe = Instant::now(); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); } } + RequestResponseEvent::InboundFailure { peer, .. } => { + self.ongoing_inbound.remove(&peer); + } }, - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::ResponseSent { .. }, - )) => {} - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { peer, .. }, - )) => { - let probe = match self.ongoing_outbound.as_mut() { - Some(p) if p.pending_servers.contains(&peer) => p, - _ => continue, - }; - probe.pending_servers.retain(|p| p != &peer); - } - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::InboundFailure { peer, .. }, - )) => { - self.ongoing_inbound.remove(&peer); - } - Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) => { - return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) - } - Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - handler, - }) => { - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - handler, - }) - } - Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, - }) => { - return Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, - }) - } - Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event, - }) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event, - }) - } - Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }) => { - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }) - } + Poll::Ready(action) => return Poll::Ready(action.map_out(|_| unreachable!())), Poll::Pending => return Poll::Pending, } if let Some(probe) = self.pending_probe.take() { @@ -595,7 +572,7 @@ impl NetworkBehaviour for Behaviour { } // Filter demanded dial addresses for validity, to prevent abuse. -fn filter_invalid_addrs( +fn filter_valid_addrs( peer: PeerId, demanded: Vec, observed_remote_at: &Multiaddr, From 48ad51dd73489e75ef67bea63523088c42914c8e Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 15 Nov 2021 01:09:31 +0100 Subject: [PATCH 12/69] protocols/autonat: enhance logic for filtering addrs --- protocols/autonat/src/behaviour.rs | 67 +++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index b92fe176d1b..a5f7601f782 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -33,7 +33,7 @@ use libp2p_swarm::{ NetworkBehaviourAction, PollParameters, }; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, iter, task::{Context, Poll}, time::{Duration, Instant}, @@ -577,27 +577,66 @@ fn filter_valid_addrs( demanded: Vec, observed_remote_at: &Multiaddr, ) -> Vec { - let observed_ip = match observed_remote_at.into_iter().next() { - Some(Protocol::Ip4(ip)) => Protocol::Ip4(ip), - Some(Protocol::Ip6(ip)) => Protocol::Ip6(ip), - _ => return Vec::new(), + let observed_ip = observed_remote_at.into_iter().find(|p| match p { + Protocol::Ip4(ip) => { + // NOTE: The below logic is copied from `std::net::Ipv4Addr::is_global`, which at the current + // point is behind the unstable `ip` feature. + // See https://github.com/rust-lang/rust/issues/27709 for more info. + // The check for the unstable `Ipv4Addr::is_benchmarking` and `Ipv4Addr::is_reserved ` + // were skipped, because they should never occur in an observed address. + + // check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two + // globally routable addresses in the 192.0.0.0/24 range. + if u32::from_be_bytes(ip.octets()) == 0xc0000009 + || u32::from_be_bytes(ip.octets()) == 0xc000000a + { + return true; + } + + let is_shared = ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000); + + !ip.is_private() + && !ip.is_loopback() + && !ip.is_link_local() + && !ip.is_broadcast() + && !ip.is_documentation() + && !is_shared + } + Protocol::Ip6(_) => { + // TODO: filter addresses for global ones + true + } + _ => false, + }); + let observed_ip = match observed_ip { + Some(ip) => ip, + None => return Vec::new(), }; + let mut distinct = HashSet::new(); demanded .into_iter() .filter_map(|addr| { - addr.replace(0, |proto| match proto { + // Replace the demanded ip with the observed one. + let i = addr + .iter() + .position(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))?; + addr.replace(i, |proto| match proto { Protocol::Ip4(_) => Some(observed_ip.clone()), Protocol::Ip6(_) => Some(observed_ip.clone()), _ => None, })?; - // TODO: Add more filters - addr.iter() - .all(|proto| match proto { - Protocol::P2pCircuit => false, - Protocol::P2p(hash) => hash == peer.into(), - _ => true, - }) - .then(|| addr) + // Filter relay addresses and addresses with invalid peer id. + let is_valid = addr.iter().all(|proto| match proto { + Protocol::P2pCircuit => false, + Protocol::P2p(hash) => hash == peer.into(), + _ => true, + }); + + if !is_valid { + return None; + } + // Only collect distinct addresses. + distinct.insert(addr.clone()).then(|| addr) }) .collect() } From f4c5c346b082ccf66c06bcd2fc54eebd97e7e21e Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 15 Nov 2021 10:15:45 +0100 Subject: [PATCH 13/69] protocol/autonat/behaviour: small fix --- protocols/autonat/src/behaviour.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index a5f7601f782..63d775866aa 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -620,11 +620,7 @@ fn filter_valid_addrs( let i = addr .iter() .position(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))?; - addr.replace(i, |proto| match proto { - Protocol::Ip4(_) => Some(observed_ip.clone()), - Protocol::Ip6(_) => Some(observed_ip.clone()), - _ => None, - })?; + addr.replace(i, |_| Some(observed_ip.clone()))?; // Filter relay addresses and addresses with invalid peer id. let is_valid = addr.iter().all(|proto| match proto { Protocol::P2pCircuit => false, From 310b086423e243696b23b0afc3751d2242999b8c Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 21 Nov 2021 20:44:42 +0100 Subject: [PATCH 14/69] protocols/autonat/behaviour: set addrs via DialOpts --- protocols/autonat/src/behaviour.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index d6e00f77f6f..374ed372931 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -247,9 +247,7 @@ impl Behaviour { } } - fn do_probe(&mut self, probe: Probe, mut addr_records: Vec) { - // Sort so that the address with the highest score will be dialed first by the remote. - addr_records.sort_by(|record_a, record_b| record_b.score.cmp(&record_a.score)); + fn do_probe(&mut self, probe: Probe, addr_records: Vec) { let addrs: Vec = addr_records.into_iter().map(|r| r.addr).collect(); for peer_id in probe.pending_servers.clone() { self.inner.send_request( @@ -372,11 +370,7 @@ impl NetworkBehaviour for Behaviour { } fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { - if let Some(addrs) = self.ongoing_inbound.get(peer).map(|(a, _)| a.clone()) { - addrs - } else { - self.inner.addresses_of_peer(peer) - } + self.inner.addresses_of_peer(peer) } fn inject_connected(&mut self, peer: &PeerId) { @@ -513,10 +507,11 @@ impl NetworkBehaviour for Behaviour { channel, } => match self.handle_request(peer, request) { Some(addrs) => { - self.ongoing_inbound.insert(peer, (addrs, channel)); + self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); return Poll::Ready(NetworkBehaviourAction::Dial { opts: DialOpts::peer_id(peer) .condition(PeerCondition::Always) + .addresses(addrs) .build(), handler: self.inner.new_handler(), }); From 0a612c07cd1d7f985f4e20b44a8817bcb57defac Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 21 Nov 2021 20:46:30 +0100 Subject: [PATCH 15/69] protocols/autonat/behaviour: methods for managing servers --- protocols/autonat/src/behaviour.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 374ed372931..b71b5bce199 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -212,6 +212,17 @@ impl Behaviour { self.ongoing_outbound.as_ref() } + pub fn add_server(&mut self, peer: PeerId, address: Option) { + self.static_servers.push(peer); + if let Some(addr) = address { + self.inner.add_address(&peer, addr); + } + } + + pub fn remove_server(&mut self, peer: &PeerId) { + self.static_servers.retain(|p| p != peer) + } + fn new_probe( &self, required_success: usize, From 99418f85f829a6a00d93c8a530d23e524c233ab0 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 21 Nov 2021 21:21:34 +0100 Subject: [PATCH 16/69] protocols/autonat/behaviour: wake task for auto-retry --- protocols/autonat/Cargo.toml | 1 + protocols/autonat/src/behaviour.rs | 60 +++++++++++++++++------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index e54dc023935..f1fe04efb2c 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -14,6 +14,7 @@ prost-build = "0.6" [dependencies] async-trait = "0.1" futures = "0.3" +futures-timer = "3.0.2" libp2p-core = { version = "0.30.1", path = "../../core" } libp2p-swarm = { version = "0.32.0", path = "../../swarm" } libp2p-request-response = { version = "0.14.0", path = "../request-response" } diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index b71b5bce199..4de5782922f 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -19,6 +19,8 @@ // DEALINGS IN THE SOFTWARE. use crate::protocol::{AutoNatCodec, AutoNatProtocol, DialRequest, DialResponse, ResponseError}; +use futures::FutureExt; +use futures_timer::Delay; use libp2p_core::{ connection::{ConnectionId, ListenerId}, multiaddr::Protocol, @@ -37,7 +39,7 @@ use std::{ collections::{HashMap, HashSet}, iter, task::{Context, Poll}, - time::{Duration, Instant}, + time::Duration, }; #[derive(Debug, Clone)] @@ -151,9 +153,7 @@ pub struct Behaviour { server_config: Option, - auto_retry: Option, - - last_probe: Instant, + auto_retry: Option<(AutoRetry, Delay)>, } impl Behaviour { @@ -166,6 +166,9 @@ impl Behaviour { let mut cfg = RequestResponseConfig::default(); cfg.set_request_timeout(config.timeout); let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); + let auto_retry = config + .auto_retry + .map(|a| (a, Delay::new(Duration::new(0, 0)))); Self { inner, ongoing_inbound: HashMap::default(), @@ -175,8 +178,7 @@ impl Behaviour { connected: HashMap::default(), reachability: Reachability::Unknown, server_config: config.server, - auto_retry: None, - last_probe: Instant::now(), + auto_retry, } } @@ -508,6 +510,27 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, params: &mut impl PollParameters, ) -> Poll> { + if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { + if delay.poll_unpin(cx).is_ready() + && self.ongoing_outbound.is_none() + && self.pending_probe.is_none() + { + let probe = self.new_probe( + auto_retry.required_success, + Vec::new(), + true, + auto_retry.extend_with_connected, + auto_retry.max_peers, + auto_retry.min_valid, + ); + let records: Vec<_> = params.external_addresses().collect(); + self.do_probe(probe, records); + } + }; + if let Some(probe) = self.pending_probe.take() { + let records: Vec<_> = params.external_addresses().collect(); + self.do_probe(probe, records); + } loop { match self.inner.poll(cx, params) { Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) => match event { @@ -538,7 +561,9 @@ impl NetworkBehaviour for Behaviour { } => { if let Some(status) = self.handle_response(peer, Some(response)) { self.reachability = Reachability::from(&status); - self.last_probe = Instant::now(); + if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { + delay.reset(auto_retry.interval); + } return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); } } @@ -547,7 +572,9 @@ impl NetworkBehaviour for Behaviour { RequestResponseEvent::OutboundFailure { peer, .. } => { if let Some(status) = self.handle_response(peer, None) { self.reachability = Reachability::from(&status); - self.last_probe = Instant::now(); + if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { + delay.reset(auto_retry.interval); + } return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); } } @@ -558,23 +585,6 @@ impl NetworkBehaviour for Behaviour { Poll::Ready(action) => return Poll::Ready(action.map_out(|_| unreachable!())), Poll::Pending => return Poll::Pending, } - if let Some(probe) = self.pending_probe.take() { - let records: Vec<_> = params.external_addresses().collect(); - self.do_probe(probe, records) - } else if let Some(auto_retry) = self.auto_retry.as_ref() { - if Instant::now().duration_since(self.last_probe) > auto_retry.interval { - let probe = self.new_probe( - auto_retry.required_success, - Vec::new(), - true, - auto_retry.extend_with_connected, - auto_retry.max_peers, - auto_retry.min_valid, - ); - let records: Vec<_> = params.external_addresses().collect(); - self.do_probe(probe, records); - } - } } } } From 1d3cdbdcefaefded1be3bc49e01e41a37af21998 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 21 Nov 2021 22:41:57 +0100 Subject: [PATCH 17/69] protocols/autonat/behaviour: get observed addr from inner --- protocols/autonat/src/behaviour.rs | 46 +++++++++++++++++++----------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 4de5782922f..9056ab6ce24 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -146,8 +146,8 @@ pub struct Behaviour { // List of trusted public peers that are probed when attempting to determine the auto-nat status. static_servers: Vec, - // List of connected peers and the address we last observed them at. - connected: HashMap, + // List of connected peers. + connected: Vec, reachability: Reachability, @@ -175,7 +175,7 @@ impl Behaviour { ongoing_outbound: None, pending_probe: None, static_servers: Vec::default(), - connected: HashMap::default(), + connected: Vec::default(), reachability: Reachability::Unknown, server_config: config.server, auto_retry, @@ -242,7 +242,7 @@ impl Behaviour { let mut connected = self.connected.iter(); // TODO: use random set for _ in 0..connected.len() { - let (peer, _) = connected.next().unwrap(); + let peer = connected.next().unwrap(); servers.push(*peer); if servers.len() >= max_peers { break; @@ -294,8 +294,12 @@ impl Behaviour { return None; } + let known_addrs = self.inner.addresses_of_peer(&sender); + // At least one observed address was added, either in the `RequestResponse` protocol + // if we dialed the remote, or in Self::inject_connection_established if the + // remote dialed us. + let observed_addr = known_addrs.first().expect("An address is known."); // Filter valid addresses. - let observed_addr = self.connected.get(&sender).expect("Peer is connected"); let mut addrs = filter_valid_addrs(sender, request.addrs, observed_addr); addrs.truncate(config.max_addresses); @@ -405,20 +409,25 @@ impl NetworkBehaviour for Behaviour { self.inner .inject_connection_established(peer, conn, endpoint, failed_addresses); - if let ConnectedPoint::Dialer { address } = endpoint { - if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { - if addrs.contains(address) { - // Successfully dialed one of the addresses from the remote peer. - let channel = self.ongoing_inbound.remove(peer).unwrap().1; - let _ = self - .inner - .send_response(channel, DialResponse::Ok(address.clone())); - return; + match endpoint { + ConnectedPoint::Dialer { address } => { + if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { + if addrs.contains(address) { + // Successfully dialed one of the addresses from the remote peer. + let channel = self.ongoing_inbound.remove(peer).unwrap().1; + let _ = self + .inner + .send_response(channel, DialResponse::Ok(address.clone())); + } } } + ConnectedPoint::Listener { send_back_addr, .. } => { + // `RequestResponse::addresses_of_peer` only includes addresses from connected peers + // where the remote is ConnectedPoint::Dialer. + // Add the send_back_addr so its observed ip can be used for `filter_valid_addrs`. + self.inner.add_address(peer, send_back_addr.clone()); + } } - self.connected - .insert(*peer, endpoint.get_remote_address().clone()); } fn inject_connection_closed( @@ -430,7 +439,7 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_closed(peer, conn, endpoint, handler); - self.connected.retain(|p, _| p != peer); + self.connected.retain(|p| p != peer); } fn inject_address_change( @@ -441,6 +450,9 @@ impl NetworkBehaviour for Behaviour { new: &ConnectedPoint, ) { self.inner.inject_address_change(peer, conn, old, new); + if let ConnectedPoint::Listener { send_back_addr, .. } = new { + self.inner.add_address(peer, send_back_addr.clone()); + } } fn inject_event( From 9a3feb2978d4104bd2bb9d0b534cccfa755d1667 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 22 Nov 2021 00:33:07 +0100 Subject: [PATCH 18/69] protocols/autonat/behaviour: report addr score on dial ok --- protocols/autonat/src/behaviour.rs | 35 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 9056ab6ce24..8793f6e8050 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -32,11 +32,11 @@ use libp2p_request_response::{ }; use libp2p_swarm::{ dial_opts::{DialOpts, PeerCondition}, - AddressRecord, DialError, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, + AddressRecord, AddressScore, DialError, IntoProtocolsHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }; use std::{ - collections::{HashMap, HashSet}, + collections::{HashMap, HashSet, VecDeque}, iter, task::{Context, Poll}, time::Duration, @@ -154,6 +154,8 @@ pub struct Behaviour { server_config: Option, auto_retry: Option<(AutoRetry, Delay)>, + + pending_out_event: VecDeque, } impl Behaviour { @@ -179,6 +181,7 @@ impl Behaviour { reachability: Reachability::Unknown, server_config: config.server, auto_retry, + pending_out_event: VecDeque::new(), } } @@ -544,6 +547,9 @@ impl NetworkBehaviour for Behaviour { self.do_probe(probe, records); } loop { + if let Some(event) = self.pending_out_event.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } match self.inner.poll(cx, params) { Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) => match event { RequestResponseEvent::Message { peer, message } => match message { @@ -571,12 +577,31 @@ impl NetworkBehaviour for Behaviour { request_id: _, response, } => { + let mut report_addr = None; + if let DialResponse::Ok(ref addr) = response { + // Update observed address score if it is finite. + let score = params + .external_addresses() + .find_map(|r| (&r.addr == addr).then(|| r.score)) + .unwrap_or(AddressScore::Finite(0)); + if let AddressScore::Finite(finite_score) = score { + report_addr = + Some(NetworkBehaviourAction::ReportObservedAddr { + address: addr.clone(), + score: AddressScore::Finite(finite_score + 1), + }); + } + } if let Some(status) = self.handle_response(peer, Some(response)) { - self.reachability = Reachability::from(&status); if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { delay.reset(auto_retry.interval); } - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); + self.reachability = Reachability::from(&status); + // Enqueue event, as only one event can be returned at a time. + self.pending_out_event.push_back(status) + } + if let Some(action) = report_addr { + return Poll::Ready(action); } } }, From 79ce52073cc2601c9430346d6e5d26e5ef17cd76 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 22 Nov 2021 00:44:02 +0100 Subject: [PATCH 19/69] protocols/autonat/behaviour: retry status on new external addr --- protocols/autonat/src/behaviour.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 8793f6e8050..540fae6d3ad 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -88,6 +88,12 @@ pub enum Reachability { Unknown, } +impl Reachability { + pub fn is_public(&self) -> bool { + matches!(self, Reachability::Public(_)) + } +} + impl From<&NatStatus> for Reachability { fn from(status: &NatStatus) -> Self { match status { @@ -514,6 +520,20 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); + if self.reachability.is_public() || self.pending_probe.is_some() { + return; + } + if let Some((ref auto_retry, _)) = self.auto_retry { + let probe = self.new_probe( + auto_retry.required_success, + Vec::new(), + true, + auto_retry.extend_with_connected, + auto_retry.max_peers, + auto_retry.min_valid, + ); + self.pending_probe.replace(probe); + } } fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { From cf7aa75b377d7ca1c577a92577685f15a5a0a80b Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 25 Nov 2021 02:37:54 +0100 Subject: [PATCH 20/69] protocols/autonat/behaviour: clean types, apply review, add comments --- protocols/autonat/src/behaviour.rs | 644 ++++++++++++++++------------- protocols/autonat/src/protocol.rs | 14 +- 2 files changed, 367 insertions(+), 291 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 540fae6d3ad..d0863c77138 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -27,13 +27,14 @@ use libp2p_core::{ ConnectedPoint, Multiaddr, PeerId, }; use libp2p_request_response::{ - handler::RequestResponseHandlerEvent, ProtocolSupport, RequestResponse, RequestResponseConfig, - RequestResponseEvent, RequestResponseMessage, ResponseChannel, + handler::RequestResponseHandlerEvent, OutboundFailure, ProtocolSupport, RequestId, + RequestResponse, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, + ResponseChannel, }; use libp2p_swarm::{ dial_opts::{DialOpts, PeerCondition}, - AddressRecord, AddressScore, DialError, IntoProtocolsHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, + AddressScore, DialError, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, + PollParameters, }; use std::{ collections::{HashMap, HashSet, VecDeque}, @@ -42,38 +43,31 @@ use std::{ time::Duration, }; -#[derive(Debug, Clone)] /// Config for the [`Behaviour`]. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { - // Timeout for requests. + /// Timeout for requests. pub timeout: Duration, - // Config if the peer should frequently re-determine its status. + /// Config if the peer should frequently re-determine its status. pub auto_retry: Option, - // Config if the current peer also serves as server for other peers. - // In case of `None`, the local peer will never do dial-attempts for other peers. + /// Config if the current peer also serves as server for other peers. + /// In case of `None`, the local peer will never do dial-attempts for other peers. pub server: Option, } /// Automatically retry the current NAT status at a certain frequency. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct AutoRetry { - // Interval in which the NAT should be tested. + /// Interval in which the NAT should be tested. pub interval: Duration, - // Max peers to send a dial-request to. - pub max_peers: usize, - // Whether the static list of servers should be extended with currently connected peers, up to `max_peers`. - pub extend_with_connected: bool, - // Minimum amount of valid response (DialResponse::Ok || ResponseError::DialError) - // from a remote, for it to count as proper attempt. - pub min_valid: usize, - // Required amount of successful dials for an address for it to count as public. - pub required_success: usize, + /// Config for the frequent probes. + pub config: ProbeConfig, } -/// Config if the local peer is a server for other peers and does dial-attempts for them. -#[derive(Debug, Clone)] +/// Config if the local peer may serve a server for other peers and do dial-attempts for them. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ServerConfig { /// Max addresses that are tried per peer. pub max_addresses: usize, @@ -81,7 +75,17 @@ pub struct ServerConfig { pub max_ongoing: usize, } -#[derive(Debug, Clone)] +impl Default for ServerConfig { + fn default() -> Self { + ServerConfig { + max_addresses: 10, + max_ongoing: 10, + } + } +} + +/// Current reachability derived from the most recent probe. +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Reachability { Public(Multiaddr), Private, @@ -89,6 +93,7 @@ pub enum Reachability { } impl Reachability { + /// Whether we can assume ourself to be public pub fn is_public(&self) -> bool { matches!(self, Reachability::Public(_)) } @@ -96,43 +101,169 @@ impl Reachability { impl From<&NatStatus> for Reachability { fn from(status: &NatStatus) -> Self { - match status { - NatStatus::Public { address, .. } => Reachability::Public(address.clone()), - NatStatus::Private { .. } => Reachability::Private, - NatStatus::Unknown { .. } => Reachability::Unknown, - } + status.reachability.clone() } } -#[derive(Debug, Clone)] +/// Identifier for a NAT-status probe. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ProbeId(u64); + +/// Single probe for determining out NAT-Status +#[derive(Debug, Clone, PartialEq)] pub struct Probe { - required_success: usize, - min_valid: usize, + id: ProbeId, + // Min. required Dial successes or failures for us to determine out status. + // If the confidence can not be reached, out reachability will be `Unknown` + min_confidence: usize, + // Number of servers to which we sent a dial request. server_count: usize, + // External addresses and the current number ob successful dials to them reported by remote peers. + // + // Apart from our demanded addresses this may also contains addresses that the remote observed us at. addresses: HashMap, - /// Disqualified dial-requests where the remote could not be reached, or rejected the dial-request. - dismissed: usize, - pending_servers: Vec, + // Serves from which we did not receive a response yet. + pending_servers: Vec<(PeerId, Option)>, + // Received response errors. errors: Vec<(PeerId, ResponseError)>, + // Failed requests. + outbound_failures: Vec<(PeerId, OutboundFailure)>, +} + +impl Probe { + // Evaluate current state to whether we already have enough results to derive the NAT status. + fn evaluate(self) -> Result { + // Find highest score of successful dials for an address. + let (address, highest) = self + .addresses + .iter() + .max_by(|(_, a), (_, b)| a.cmp(b)) + .expect("At least one external address was added."); + + // Check if the required amount of successful dials was reached. + if *highest >= self.min_confidence { + let addr = address.clone(); + return Ok(self.into_nat_status(Reachability::Public(addr))); + } + + // Check if the probe can still be successful. + if self.pending_servers.len() >= self.min_confidence - highest { + return Err(self); + } + + let error_responses = self + .errors + .iter() + .filter(|(_, e)| matches!(e, ResponseError::DialError)) + .count(); + if error_responses >= self.min_confidence { + Ok(self.into_nat_status(Reachability::Private)) + } else { + Ok(self.into_nat_status(Reachability::Unknown)) + } + } + + fn into_nat_status(self, reachability: Reachability) -> NatStatus { + NatStatus { + reachability, + errors: self.errors, + tried_addresses: self.addresses.into_iter().collect(), + outbound_failures: self.outbound_failures, + } + } +} + +// Configuration for a single probe. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProbeConfig { + servers: Vec, + extend_with_connected: bool, + max_peers: usize, + min_confidence: usize, +} + +impl Default for ProbeConfig { + fn default() -> Self { + ProbeConfig { + servers: Vec::new(), + extend_with_connected: true, + max_peers: 10, + min_confidence: 3, + } + } +} + +impl ProbeConfig { + pub fn new() -> Self { + Self::default() + } + + /// List of trusted public peers that are probed when attempting to determine the auto-nat status. + pub fn servers(&mut self, servers: Vec) -> &mut Self { + self.servers = servers; + self + } + + /// Whether the list of servers should be extended with currently connected peers, up to `max_peers`. + pub fn extend_with_connected(&mut self, extend: bool) -> &mut Self { + self.extend_with_connected = extend; + self + } + + /// Max peers to send a dial-request to. + pub fn max_peers(&mut self, max: usize) -> &mut Self { + self.max_peers = max; + self + } + + /// Minimum amount of DialResponse::Ok / ResponseError::DialFailure from different remote peers, + /// for it to count as a valid result. If the minimum confidence was not reached, the reachability will be + /// [`Reachability::Unknown`]. + pub fn min_confidence(&mut self, min: usize) -> &mut Self { + self.min_confidence = min; + self + } + + fn build(&self, id: ProbeId, addresses: Vec, connected: Vec<&PeerId>) -> Probe { + let mut pending_servers = self.servers.clone(); + pending_servers.truncate(self.max_peers); + if pending_servers.len() < self.max_peers && self.extend_with_connected { + // TODO: use random set + for peer in connected { + pending_servers.push(*peer); + if pending_servers.len() >= self.max_peers { + break; + } + } + } + let addresses = addresses.into_iter().map(|a| (a, 0)).collect(); + let pending_servers: Vec<_> = pending_servers.into_iter().map(|p| (p, None)).collect(); + + Probe { + id, + min_confidence: self.min_confidence, + server_count: pending_servers.len(), + pending_servers, + addresses, + errors: Vec::new(), + outbound_failures: Vec::new(), + } + } } -#[derive(Debug, Clone)] -pub enum NatStatus { - Public { - address: Multiaddr, - server_count: usize, - successes: usize, - }, - Private { - server_count: usize, - tried_addresses: Vec<(Multiaddr, usize)>, - errors: Vec<(PeerId, ResponseError)>, - }, - Unknown { - server_count: usize, - tried_addresses: Vec<(Multiaddr, usize)>, - errors: Vec<(PeerId, ResponseError)>, - }, +/// Outcome of a [`Probe`]. +#[derive(Debug, Clone, PartialEq)] +pub struct NatStatus { + /// Our assumed reachability derived from the dial responses we received + pub reachability: Reachability, + // External addresses and the number ob successful dials to them reported by remote peers. + // + // Apart from our demanded addresses this may also contains addresses that the remote observed us at. + pub tried_addresses: Vec<(Multiaddr, usize)>, + // Received dial-response errors. + pub errors: Vec<(PeerId, ResponseError)>, + // Failed requests. + pub outbound_failures: Vec<(PeerId, OutboundFailure)>, } /// Network Behaviour for AutoNAT. @@ -147,21 +278,24 @@ pub struct Behaviour { ongoing_outbound: Option, // Manually initiated probe. - pending_probe: Option, - - // List of trusted public peers that are probed when attempting to determine the auto-nat status. - static_servers: Vec, + pending_probe: Option<(ProbeId, ProbeConfig)>, - // List of connected peers. - connected: Vec, + // Connected peers with their observed address + connected: HashMap, + // Assumed reachability derived from the most recent probe. reachability: Reachability, + // See `Config::server` server_config: Option, + // See `Config::auto_retry` auto_retry: Option<(AutoRetry, Delay)>, + // Out events that should be reported to the user pending_out_event: VecDeque, + + next_probe_id: ProbeId, } impl Behaviour { @@ -174,111 +308,78 @@ impl Behaviour { let mut cfg = RequestResponseConfig::default(); cfg.set_request_timeout(config.timeout); let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); - let auto_retry = config - .auto_retry - .map(|a| (a, Delay::new(Duration::new(0, 0)))); + let auto_retry = config.auto_retry.map(|a| (a, Delay::new(Duration::ZERO))); Self { inner, ongoing_inbound: HashMap::default(), ongoing_outbound: None, pending_probe: None, - static_servers: Vec::default(), - connected: Vec::default(), + connected: HashMap::default(), reachability: Reachability::Unknown, server_config: config.server, auto_retry, pending_out_event: VecDeque::new(), + next_probe_id: ProbeId(1), } } // Manually retry determination of NAT status. - // - // Return whether there is already an ongoing Probe. - pub fn retry_nat_status( - &mut self, - required_success: usize, - servers: Vec, - extend_with_static: bool, - extend_with_connected: bool, - max_peers: usize, - min_valid: usize, - ) -> bool { - let probe = self.new_probe( - required_success, - servers, - extend_with_static, - extend_with_connected, - max_peers, - min_valid, - ); - self.pending_probe.replace(probe); - self.ongoing_outbound.is_some() + pub fn retry_nat_status(&mut self, probe_config: ProbeConfig) -> ProbeId { + let id = self.next_probe_id(); + self.pending_probe.replace((id, probe_config)); + id } + // Assumed reachability derived from the most recent probe. pub fn reachability(&self) -> Reachability { self.reachability.clone() } - pub fn ongoing_probe(&self) -> Option<&Probe> { - self.ongoing_outbound.as_ref() - } - - pub fn add_server(&mut self, peer: PeerId, address: Option) { - self.static_servers.push(peer); - if let Some(addr) = address { - self.inner.add_address(&peer, addr); + /// Add peer to the list of trusted public peers that are probed in the auto-retry nat status. + /// + /// Return false if auto-retry is `None`. + pub fn add_server(&mut self, peer: PeerId, address: Option) -> bool { + match self.auto_retry { + Some((ref mut retry, _)) => { + retry.config.servers.push(peer); + if let Some(addr) = address { + self.inner.add_address(&peer, addr); + } + true + } + None => false, } } - pub fn remove_server(&mut self, peer: &PeerId) { - self.static_servers.retain(|p| p != peer) - } - - fn new_probe( - &self, - required_success: usize, - mut servers: Vec, - extend_with_static: bool, - extend_with_connected: bool, - max_peers: usize, - min_valid: usize, - ) -> Probe { - servers.truncate(max_peers); - if servers.len() < max_peers && extend_with_static { - servers.extend(self.static_servers.clone()) - } - if servers.len() < max_peers && extend_with_connected { - let mut connected = self.connected.iter(); - // TODO: use random set - for _ in 0..connected.len() { - let peer = connected.next().unwrap(); - servers.push(*peer); - if servers.len() >= max_peers { - break; - } + /// Remove a peer from the list of servers tried in the auto-retry. + /// + /// Return false if auto-retry is `None`. + pub fn remove_server(&mut self, peer: &PeerId) -> bool { + match self.auto_retry { + Some((ref mut retry, _)) => { + retry.config.servers.retain(|p| p != peer); + true } + None => false, } - Probe { - server_count: servers.len(), - pending_servers: servers, - addresses: HashMap::new(), - required_success, - dismissed: 0, - errors: Vec::new(), - min_valid, - } } - fn do_probe(&mut self, probe: Probe, addr_records: Vec) { - let addrs: Vec = addr_records.into_iter().map(|r| r.addr).collect(); - for peer_id in probe.pending_servers.clone() { - self.inner.send_request( - &peer_id, + fn next_probe_id(&mut self) -> ProbeId { + let probe_id = self.next_probe_id; + self.next_probe_id.0 += 1; + probe_id + } + + fn do_probe(&mut self, mut probe: Probe) { + for (peer_id, id) in probe.pending_servers.iter_mut() { + let request_id = self.inner.send_request( + peer_id, DialRequest { - peer_id, - addrs: addrs.clone(), + peer_id: *peer_id, + addrs: probe.addresses.keys().cloned().collect(), }, ); + let _ = id.insert(request_id); } let _ = self.ongoing_outbound.insert(probe); } @@ -319,70 +420,46 @@ impl Behaviour { Some(addrs) } + // Update the ongoing outbound probe according to the result of our dial-request. + // If the minimum confidence was reached it returns the nat status. fn handle_response( &mut self, sender: PeerId, - response: Option, + request_id: RequestId, + response: Result, ) -> Option { let mut probe = self.ongoing_outbound.take()?; - if !probe.pending_servers.contains(&sender) { + + // Ignore the response if the peer or the request is not part of the ongoing probe. + // This could be the case if we received a late response for a previous probe that already resolved. + if !probe + .pending_servers + .iter() + .any(|(p, id)| *p == sender && id.unwrap() == request_id) + { let _ = self.ongoing_outbound.insert(probe); return None; }; - probe.pending_servers.retain(|p| p != &sender); + + probe.pending_servers.retain(|(p, _)| p != &sender); match response { - Some(DialResponse::Ok(addr)) => self.handle_dial_ok(probe, addr), - Some(DialResponse::Err(err)) => { - if !matches!(err, ResponseError::DialError) { - probe.dismissed += 1; - } + Ok(DialResponse::Ok(addr)) => { + let score = probe.addresses.entry(addr).or_insert(0); + *score += 1; + } + Ok(DialResponse::Err(err)) => { probe.errors.push((sender, err)); - self.handle_dial_err(probe) } - None => { - probe.dismissed += 1; - self.handle_dial_err(probe) + Err(err) => { + probe.outbound_failures.push((sender, err)); } } - } - - fn handle_dial_ok(&mut self, mut probe: Probe, address: Multiaddr) -> Option { - let score = probe.addresses.get_mut(&address)?; - *score += 1; - if *score < probe.required_success { - let _ = self.ongoing_outbound.insert(probe); - return None; - } - Some(NatStatus::Public { - address: address.clone(), - server_count: probe.server_count, - successes: *score, - }) - } - - fn handle_dial_err(&mut self, probe: Probe) -> Option { - let remaining = probe.pending_servers.len(); - let current_highest = probe.addresses.iter().max_by(|(_, a), (_, b)| a.cmp(b))?.1; - - // Check if the probe can still be successful. - if remaining >= probe.required_success - current_highest { - let _ = self.ongoing_outbound.insert(probe); - return None; - } - - let valid_attempts = probe.server_count - remaining - probe.dismissed; - if valid_attempts >= probe.min_valid { - Some(NatStatus::Private { - server_count: probe.server_count, - errors: probe.errors, - tried_addresses: probe.addresses.into_iter().collect(), - }) - } else { - Some(NatStatus::Unknown { - server_count: probe.server_count, - errors: probe.errors, - tried_addresses: probe.addresses.into_iter().collect(), - }) + match probe.evaluate() { + Ok(status) => Some(status), + Err(probe) => { + let _ = self.ongoing_outbound.insert(probe); + None + } } } } @@ -418,25 +495,20 @@ impl NetworkBehaviour for Behaviour { self.inner .inject_connection_established(peer, conn, endpoint, failed_addresses); - match endpoint { - ConnectedPoint::Dialer { address } => { - if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { - if addrs.contains(address) { - // Successfully dialed one of the addresses from the remote peer. - let channel = self.ongoing_inbound.remove(peer).unwrap().1; - let _ = self - .inner - .send_response(channel, DialResponse::Ok(address.clone())); - } + if let ConnectedPoint::Dialer { address } = endpoint { + if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { + // Check if the dialed address was among the requested addresses. + if addrs.contains(address) { + // Successfully dialed one of the addresses from the remote peer. + let channel = self.ongoing_inbound.remove(peer).unwrap().1; + let _ = self + .inner + .send_response(channel, DialResponse::Ok(address.clone())); } } - ConnectedPoint::Listener { send_back_addr, .. } => { - // `RequestResponse::addresses_of_peer` only includes addresses from connected peers - // where the remote is ConnectedPoint::Dialer. - // Add the send_back_addr so its observed ip can be used for `filter_valid_addrs`. - self.inner.add_address(peer, send_back_addr.clone()); - } } + self.connected + .insert(*peer, endpoint.get_remote_address().clone()); } fn inject_connection_closed( @@ -448,7 +520,7 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_closed(peer, conn, endpoint, handler); - self.connected.retain(|p| p != peer); + self.connected.remove(peer); } fn inject_address_change( @@ -524,15 +596,10 @@ impl NetworkBehaviour for Behaviour { return; } if let Some((ref auto_retry, _)) = self.auto_retry { - let probe = self.new_probe( - auto_retry.required_success, - Vec::new(), - true, - auto_retry.extend_with_connected, - auto_retry.max_peers, - auto_retry.min_valid, - ); - self.pending_probe.replace(probe); + let probe_id = self.next_probe_id; + self.pending_probe + .replace((probe_id, auto_retry.config.clone())); + self.next_probe_id.0 += 1; } } @@ -550,95 +617,110 @@ impl NetworkBehaviour for Behaviour { && self.ongoing_outbound.is_none() && self.pending_probe.is_none() { - let probe = self.new_probe( - auto_retry.required_success, - Vec::new(), - true, - auto_retry.extend_with_connected, - auto_retry.max_peers, - auto_retry.min_valid, + let external_addrs = params + .external_addresses() + .map(|record| record.addr) + .collect(); + let probe_id = self.next_probe_id; + let probe = auto_retry.config.build( + probe_id, + external_addrs, + self.connected.keys().collect(), ); - let records: Vec<_> = params.external_addresses().collect(); - self.do_probe(probe, records); + self.do_probe(probe); + self.next_probe_id.0 += 1; } }; - if let Some(probe) = self.pending_probe.take() { - let records: Vec<_> = params.external_addresses().collect(); - self.do_probe(probe, records); + if let Some((probe_id, config)) = self.pending_probe.take() { + let external_addrs = params + .external_addresses() + .map(|record| record.addr) + .collect(); + let probe = config.build(probe_id, external_addrs, self.connected.keys().collect()); + self.do_probe(probe); } loop { if let Some(event) = self.pending_out_event.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } match self.inner.poll(cx, params) { - Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) => match event { - RequestResponseEvent::Message { peer, message } => match message { - RequestResponseMessage::Request { - request_id: _, - request, - channel, - } => match self.handle_request(peer, request) { - Some(addrs) => { - self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); - return Poll::Ready(NetworkBehaviourAction::Dial { - opts: DialOpts::peer_id(peer) - .condition(PeerCondition::Always) - .addresses(addrs) - .build(), - handler: self.inner.new_handler(), + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::Message { peer, message }, + )) => match message { + RequestResponseMessage::Request { + request_id: _, + request, + channel, + } => match self.handle_request(peer, request) { + Some(addrs) => { + self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); + return Poll::Ready(NetworkBehaviourAction::Dial { + opts: DialOpts::peer_id(peer) + .condition(PeerCondition::Always) + .addresses(addrs) + .build(), + handler: self.inner.new_handler(), + }); + } + None => { + let response = DialResponse::Err(ResponseError::DialRefused); + let _ = self.inner.send_response(channel, response); + } + }, + RequestResponseMessage::Response { + request_id, + response, + } => { + let mut report_addr = None; + if let DialResponse::Ok(ref addr) = response { + // Update observed address score if it is finite. + let score = params + .external_addresses() + .find_map(|r| (&r.addr == addr).then(|| r.score)) + .unwrap_or(AddressScore::Finite(0)); + if let AddressScore::Finite(finite_score) = score { + report_addr = Some(NetworkBehaviourAction::ReportObservedAddr { + address: addr.clone(), + score: AddressScore::Finite(finite_score + 1), }); } - None => { - let response = DialResponse::Err(ResponseError::DialRefused); - let _ = self.inner.send_response(channel, response); - } - }, - RequestResponseMessage::Response { - request_id: _, - response, - } => { - let mut report_addr = None; - if let DialResponse::Ok(ref addr) = response { - // Update observed address score if it is finite. - let score = params - .external_addresses() - .find_map(|r| (&r.addr == addr).then(|| r.score)) - .unwrap_or(AddressScore::Finite(0)); - if let AddressScore::Finite(finite_score) = score { - report_addr = - Some(NetworkBehaviourAction::ReportObservedAddr { - address: addr.clone(), - score: AddressScore::Finite(finite_score + 1), - }); - } - } - if let Some(status) = self.handle_response(peer, Some(response)) { - if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { - delay.reset(auto_retry.interval); - } - self.reachability = Reachability::from(&status); - // Enqueue event, as only one event can be returned at a time. - self.pending_out_event.push_back(status) - } - if let Some(action) = report_addr { - return Poll::Ready(action); - } } - }, - RequestResponseEvent::ResponseSent { .. } => {} - RequestResponseEvent::OutboundFailure { peer, .. } => { - if let Some(status) = self.handle_response(peer, None) { - self.reachability = Reachability::from(&status); + if let Some(status) = self.handle_response(peer, request_id, Ok(response)) { if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { delay.reset(auto_retry.interval); } - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); + self.reachability = Reachability::from(&status); + // Enqueue event, as only one event can be returned at a time. + self.pending_out_event.push_back(status) + } + if let Some(action) = report_addr { + return Poll::Ready(action); } - } - RequestResponseEvent::InboundFailure { peer, .. } => { - self.ongoing_inbound.remove(&peer); } }, + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::ResponseSent { .. }, + )) => {} + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::OutboundFailure { + request_id, + peer, + error, + }, + )) => { + if let Some(status) = self.handle_response(peer, request_id, Err(error)) { + self.reachability = Reachability::from(&status); + if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { + delay.reset(auto_retry.interval); + } + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); + } + } + Poll::Ready(NetworkBehaviourAction::GenerateEvent( + RequestResponseEvent::InboundFailure { peer, .. }, + )) => { + self.ongoing_inbound.remove(&peer); + } Poll::Ready(action) => return Poll::Ready(action.map_out(|_| unreachable!())), Poll::Pending => return Poll::Pending, } diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index b7ddd037764..5ffeb95c4de 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -52,11 +52,8 @@ impl RequestResponseCodec for AutoNatCodec { where T: AsyncRead + Send + Unpin, { - let bytes = upgrade::read_length_prefixed(io, 1024) - .await - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let request = DialRequest::from_bytes(&bytes) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let bytes = upgrade::read_length_prefixed(io, 1024).await?; + let request = DialRequest::from_bytes(&bytes)?; Ok(request) } @@ -68,11 +65,8 @@ impl RequestResponseCodec for AutoNatCodec { where T: AsyncRead + Send + Unpin, { - let bytes = upgrade::read_length_prefixed(io, 1024) - .await - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - let response = DialResponse::from_bytes(&bytes) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let bytes = upgrade::read_length_prefixed(io, 1024).await?; + let response = DialResponse::from_bytes(&bytes)?; Ok(response) } From cc1435786e6a68e4921a42cec89e1364db5d450b Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 25 Nov 2021 09:10:27 +0100 Subject: [PATCH 21/69] protocols/autonat/behaviour: track observed address --- protocols/autonat/src/behaviour.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index d0863c77138..1ee63554208 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -404,11 +404,10 @@ impl Behaviour { return None; } - let known_addrs = self.inner.addresses_of_peer(&sender); - // At least one observed address was added, either in the `RequestResponse` protocol - // if we dialed the remote, or in Self::inject_connection_established if the - // remote dialed us. - let observed_addr = known_addrs.first().expect("An address is known."); + let observed_addr = self + .connected + .get(&sender) + .expect("We are connected to the peer."); // Filter valid addresses. let mut addrs = filter_valid_addrs(sender, request.addrs, observed_addr); addrs.truncate(config.max_addresses); @@ -483,6 +482,7 @@ impl NetworkBehaviour for Behaviour { fn inject_disconnected(&mut self, peer: &PeerId) { self.inner.inject_disconnected(peer); self.ongoing_inbound.remove(peer); + self.connected.remove(peer); } fn inject_connection_established( @@ -520,7 +520,6 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_closed(peer, conn, endpoint, handler); - self.connected.remove(peer); } fn inject_address_change( @@ -531,9 +530,9 @@ impl NetworkBehaviour for Behaviour { new: &ConnectedPoint, ) { self.inner.inject_address_change(peer, conn, old, new); - if let ConnectedPoint::Listener { send_back_addr, .. } = new { - self.inner.add_address(peer, send_back_addr.clone()); - } + // Update observed address. + self.connected + .insert(*peer, new.get_remote_address().clone()); } fn inject_event( From ddcd9ce0ad88b435e69cb991a14c89123c73b482 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 25 Nov 2021 09:51:23 +0100 Subject: [PATCH 22/69] protocols/autonat/behaviour: skip dial if observed addr is relayed --- protocols/autonat/src/behaviour.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 1ee63554208..a9485995d59 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -733,6 +733,10 @@ fn filter_valid_addrs( demanded: Vec, observed_remote_at: &Multiaddr, ) -> Vec { + // Skip if the observed address is a relay address. + if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) { + return Vec::new(); + } let observed_ip = observed_remote_at.into_iter().find(|p| match p { Protocol::Ip4(ip) => { // NOTE: The below logic is copied from `std::net::Ipv4Addr::is_global`, which at the current From b07ef43fbd9ab886ec3c0d91de9bb7ba34f7e34c Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 26 Nov 2021 20:24:37 +0100 Subject: [PATCH 23/69] protocols/autonat/examples: Add client example --- protocols/autonat/Cargo.toml | 6 ++ protocols/autonat/examples/client.rs | 119 +++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 protocols/autonat/examples/client.rs diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index f1fe04efb2c..60234dec2ee 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -19,3 +19,9 @@ libp2p-core = { version = "0.30.1", path = "../../core" } libp2p-swarm = { version = "0.32.0", path = "../../swarm" } libp2p-request-response = { version = "0.14.0", path = "../request-response" } prost = "0.8" + +[dev-dependencies] +async-std = { version = "1.7.0", features = ["attributes"] } +env_logger = "0.9.0" +libp2p = { path = "../.." } +structopt = "0.3.21" diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs new file mode 100644 index 00000000000..4eefaa6cc45 --- /dev/null +++ b/protocols/autonat/examples/client.rs @@ -0,0 +1,119 @@ +// Copyright 2021 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::prelude::*; +use libp2p::autonat; +use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; +use libp2p::swarm::{AddressScore, Swarm, SwarmEvent}; +use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId}; +use std::error::Error; +use std::time::Duration; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "libp2p relay")] +struct Opt { + #[structopt(long)] + server_address: Multiaddr, + + #[structopt(long)] + server_peer_id: PeerId, +} + +#[async_std::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let opt = Opt::from_args(); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + println!("Local peer id: {:?}", local_peer_id); + + let transport = libp2p::development_transport(local_key.clone()).await?; + + let behaviour = Behaviour::new(local_key.public()); + + let mut swarm = Swarm::new(transport, behaviour, local_peer_id); + swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + + swarm + .behaviour_mut() + .auto_nat + .add_server(opt.server_peer_id, Some(opt.server_address)) + .then(|| Some(())) + .expect("Auto retry to be enabled."); + + swarm.add_external_address(Multiaddr::empty(), AddressScore::Infinite); + + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {:?}", address), + SwarmEvent::Behaviour(event) => println!("{:?}", event), + e => println!("{:?}", e), + } + } +} + +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "Event")] +struct Behaviour { + identify: Identify, + auto_nat: autonat::behaviour::Behaviour, +} + +impl Behaviour { + fn new(local_public_key: identity::PublicKey) -> Self { + Self { + identify: Identify::new(IdentifyConfig::new( + "/ipfs/0.1.0".into(), + local_public_key.clone(), + )), + auto_nat: autonat::behaviour::Behaviour::new( + local_public_key.to_peer_id(), + autonat::behaviour::Config { + auto_retry: Some(autonat::behaviour::AutoRetry { + interval: Duration::from_secs(10), + config: autonat::behaviour::ProbeConfig::default(), + }), + ..autonat::behaviour::Config::default() + }, + ), + } + } +} + +#[derive(Debug)] +enum Event { + AutoNat(autonat::behaviour::NatStatus), + Identify(IdentifyEvent), +} + +impl From for Event { + fn from(v: IdentifyEvent) -> Self { + Self::Identify(v) + } +} + +impl From for Event { + fn from(v: autonat::behaviour::NatStatus) -> Self { + Self::AutoNat(v) + } +} From 494aed1e079f4ca92fd35a13d60f20cf9686c8ad Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 27 Nov 2021 00:56:13 +0100 Subject: [PATCH 24/69] protocols/autonat: fix bugs, clean code --- protocols/autonat/src/behaviour.rs | 219 ++++++++++++++++------------- protocols/autonat/src/lib.rs | 8 +- 2 files changed, 125 insertions(+), 102 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index a9485995d59..d09390695a2 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -50,22 +50,41 @@ pub struct Config { pub timeout: Duration, /// Config if the peer should frequently re-determine its status. - pub auto_retry: Option, + pub auto_probe: Option, /// Config if the current peer also serves as server for other peers. /// In case of `None`, the local peer will never do dial-attempts for other peers. pub server: Option, } +impl Default for Config { + fn default() -> Self { + Config { + timeout: Duration::from_secs(30), + auto_probe: Some(AutoProbe::default()), + server: Some(ServerConfig::default()), + } + } +} + /// Automatically retry the current NAT status at a certain frequency. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct AutoRetry { +pub struct AutoProbe { /// Interval in which the NAT should be tested. pub interval: Duration, /// Config for the frequent probes. pub config: ProbeConfig, } +impl Default for AutoProbe { + fn default() -> Self { + AutoProbe { + interval: Duration::from_secs(90), + config: ProbeConfig::default(), + } + } +} + /// Config if the local peer may serve a server for other peers and do dial-attempts for them. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ServerConfig { @@ -73,6 +92,8 @@ pub struct ServerConfig { pub max_addresses: usize, /// Max simultaneous autonat dial-attempts. pub max_ongoing: usize, + /// Dial only addresses in public IP range. + pub only_public: bool, } impl Default for ServerConfig { @@ -80,6 +101,7 @@ impl Default for ServerConfig { ServerConfig { max_addresses: 10, max_ongoing: 10, + only_public: true, } } } @@ -134,11 +156,10 @@ impl Probe { // Evaluate current state to whether we already have enough results to derive the NAT status. fn evaluate(self) -> Result { // Find highest score of successful dials for an address. - let (address, highest) = self - .addresses - .iter() - .max_by(|(_, a), (_, b)| a.cmp(b)) - .expect("At least one external address was added."); + let (address, highest) = match self.addresses.iter().max_by(|(_, a), (_, b)| a.cmp(b)) { + Some(max) => max, + None => return Ok(self.into_nat_status(Reachability::Unknown)), + }; // Check if the required amount of successful dials was reached. if *highest >= self.min_confidence { @@ -169,6 +190,7 @@ impl Probe { errors: self.errors, tried_addresses: self.addresses.into_iter().collect(), outbound_failures: self.outbound_failures, + probe_id: self.id, } } } @@ -176,10 +198,16 @@ impl Probe { // Configuration for a single probe. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProbeConfig { - servers: Vec, - extend_with_connected: bool, - max_peers: usize, - min_confidence: usize, + /// List of trusted public peers that are probed when attempting to determine the auto-nat status. + pub servers: Vec, + /// Whether the list of servers should be extended with currently connected peers, up to `max_peers`. + pub extend_with_connected: bool, + /// Max peers to send a dial-request to. + pub max_peers: usize, + /// Minimum amount of DialResponse::Ok / ResponseError::DialFailure from different remote peers, + /// for it to count as a valid result. If the minimum confidence was not reached, the reachability will be + /// [`Reachability::Unknown`]. + pub min_confidence: usize, } impl Default for ProbeConfig { @@ -194,36 +222,6 @@ impl Default for ProbeConfig { } impl ProbeConfig { - pub fn new() -> Self { - Self::default() - } - - /// List of trusted public peers that are probed when attempting to determine the auto-nat status. - pub fn servers(&mut self, servers: Vec) -> &mut Self { - self.servers = servers; - self - } - - /// Whether the list of servers should be extended with currently connected peers, up to `max_peers`. - pub fn extend_with_connected(&mut self, extend: bool) -> &mut Self { - self.extend_with_connected = extend; - self - } - - /// Max peers to send a dial-request to. - pub fn max_peers(&mut self, max: usize) -> &mut Self { - self.max_peers = max; - self - } - - /// Minimum amount of DialResponse::Ok / ResponseError::DialFailure from different remote peers, - /// for it to count as a valid result. If the minimum confidence was not reached, the reachability will be - /// [`Reachability::Unknown`]. - pub fn min_confidence(&mut self, min: usize) -> &mut Self { - self.min_confidence = min; - self - } - fn build(&self, id: ProbeId, addresses: Vec, connected: Vec<&PeerId>) -> Probe { let mut pending_servers = self.servers.clone(); pending_servers.truncate(self.max_peers); @@ -256,18 +254,24 @@ impl ProbeConfig { pub struct NatStatus { /// Our assumed reachability derived from the dial responses we received pub reachability: Reachability, - // External addresses and the number ob successful dials to them reported by remote peers. - // - // Apart from our demanded addresses this may also contains addresses that the remote observed us at. + /// External addresses and the number ob successful dials to them reported by remote peers. + /// + /// Apart from our demanded addresses this may also contains addresses that the remote observed us at. pub tried_addresses: Vec<(Multiaddr, usize)>, - // Received dial-response errors. + /// Received dial-response errors. pub errors: Vec<(PeerId, ResponseError)>, - // Failed requests. + /// Failed requests. pub outbound_failures: Vec<(PeerId, OutboundFailure)>, + + /// Id of the probe that resulted in this status. + pub probe_id: ProbeId, } /// Network Behaviour for AutoNAT. pub struct Behaviour { + // Own peer id + local_peer_id: PeerId, + // Inner protocol for sending requests and receiving the response. inner: RequestResponse, @@ -278,19 +282,22 @@ pub struct Behaviour { ongoing_outbound: Option, // Manually initiated probe. - pending_probe: Option<(ProbeId, ProbeConfig)>, + pending_probes: VecDeque<(ProbeId, ProbeConfig)>, // Connected peers with their observed address connected: HashMap, + // Tracked listening addresses. + listen_addrs: Vec, + // Assumed reachability derived from the most recent probe. reachability: Reachability, // See `Config::server` server_config: Option, - // See `Config::auto_retry` - auto_retry: Option<(AutoRetry, Delay)>, + // See `Config::auto_probe` + auto_probe: Option<(AutoProbe, Delay)>, // Out events that should be reported to the user pending_out_event: VecDeque, @@ -299,7 +306,7 @@ pub struct Behaviour { } impl Behaviour { - pub fn new(config: Config) -> Self { + pub fn new(local_peer_id: PeerId, config: Config) -> Self { let proto_support = match config.server { Some(_) => ProtocolSupport::Full, None => ProtocolSupport::Outbound, @@ -308,16 +315,18 @@ impl Behaviour { let mut cfg = RequestResponseConfig::default(); cfg.set_request_timeout(config.timeout); let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); - let auto_retry = config.auto_retry.map(|a| (a, Delay::new(Duration::ZERO))); + let auto_probe = config.auto_probe.map(|a| (a, Delay::new(Duration::ZERO))); Self { + local_peer_id, inner, ongoing_inbound: HashMap::default(), ongoing_outbound: None, - pending_probe: None, + pending_probes: VecDeque::new(), connected: HashMap::default(), + listen_addrs: Vec::new(), reachability: Reachability::Unknown, server_config: config.server, - auto_retry, + auto_probe, pending_out_event: VecDeque::new(), next_probe_id: ProbeId(1), } @@ -326,7 +335,7 @@ impl Behaviour { // Manually retry determination of NAT status. pub fn retry_nat_status(&mut self, probe_config: ProbeConfig) -> ProbeId { let id = self.next_probe_id(); - self.pending_probe.replace((id, probe_config)); + self.pending_probes.push_back((id, probe_config)); id } @@ -339,7 +348,7 @@ impl Behaviour { /// /// Return false if auto-retry is `None`. pub fn add_server(&mut self, peer: PeerId, address: Option) -> bool { - match self.auto_retry { + match self.auto_probe { Some((ref mut retry, _)) => { retry.config.servers.push(peer); if let Some(addr) = address { @@ -355,7 +364,7 @@ impl Behaviour { /// /// Return false if auto-retry is `None`. pub fn remove_server(&mut self, peer: &PeerId) -> bool { - match self.auto_retry { + match self.auto_probe { Some((ref mut retry, _)) => { retry.config.servers.retain(|p| p != peer); true @@ -370,12 +379,17 @@ impl Behaviour { probe_id } - fn do_probe(&mut self, mut probe: Probe) { + fn do_probe(&mut self, probe: Probe) { + // Check if the probe can already be resolved, e.g because there are no external addresses. + let mut probe = match probe.evaluate() { + Ok(status) => return self.inject_status(status), + Err(probe) => probe, + }; for (peer_id, id) in probe.pending_servers.iter_mut() { let request_id = self.inner.send_request( peer_id, DialRequest { - peer_id: *peer_id, + peer_id: self.local_peer_id, addrs: probe.addresses.keys().cloned().collect(), }, ); @@ -384,6 +398,14 @@ impl Behaviour { let _ = self.ongoing_outbound.insert(probe); } + fn inject_status(&mut self, status: NatStatus) { + self.reachability = Reachability::from(&status); + self.pending_out_event.push_back(status); + if let Some((ref auto_probe, ref mut delay)) = self.auto_probe { + delay.reset(auto_probe.interval); + } + } + // Handle the inbound request and collect the valid addresses to be dialed. fn handle_request(&mut self, sender: PeerId, request: DialRequest) -> Option> { let config = self @@ -409,7 +431,12 @@ impl Behaviour { .get(&sender) .expect("We are connected to the peer."); // Filter valid addresses. - let mut addrs = filter_valid_addrs(sender, request.addrs, observed_addr); + let mut addrs = filter_valid_addrs( + sender, + request.addrs, + observed_addr, + self.server_config.as_ref().unwrap().only_public, + ); addrs.truncate(config.max_addresses); if addrs.is_empty() { @@ -575,10 +602,12 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); + self.listen_addrs.push(addr.clone()); } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_expired_listen_addr(id, addr); + self.listen_addrs.retain(|a| a != addr); } fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { @@ -591,14 +620,13 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - if self.reachability.is_public() || self.pending_probe.is_some() { + if self.reachability.is_public() || !self.pending_probes.is_empty() { return; } - if let Some((ref auto_retry, _)) = self.auto_retry { - let probe_id = self.next_probe_id; - self.pending_probe - .replace((probe_id, auto_retry.config.clone())); - self.next_probe_id.0 += 1; + if let Some((ref auto_probe, _)) = self.auto_probe { + let config = auto_probe.config.clone(); + let probe_id = self.next_probe_id(); + self.pending_probes.push_back((probe_id, config)); } } @@ -611,34 +639,29 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, params: &mut impl PollParameters, ) -> Poll> { - if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { + if let Some((ref auto_probe, ref mut delay)) = self.auto_probe { if delay.poll_unpin(cx).is_ready() && self.ongoing_outbound.is_none() - && self.pending_probe.is_none() + && self.pending_probes.is_empty() { - let external_addrs = params - .external_addresses() - .map(|record| record.addr) - .collect(); - let probe_id = self.next_probe_id; - let probe = auto_retry.config.build( - probe_id, - external_addrs, - self.connected.keys().collect(), - ); - self.do_probe(probe); - self.next_probe_id.0 += 1; + let config = auto_probe.config.clone(); + let id = self.next_probe_id(); + self.pending_probes.push_back((id, config)); } }; - if let Some((probe_id, config)) = self.pending_probe.take() { - let external_addrs = params - .external_addresses() - .map(|record| record.addr) - .collect(); - let probe = config.build(probe_id, external_addrs, self.connected.keys().collect()); - self.do_probe(probe); - } loop { + if self.ongoing_outbound.is_none() { + if let Some((probe_id, config)) = self.pending_probes.pop_front() { + let mut external_addrs: Vec = params + .external_addresses() + .map(|record| record.addr) + .collect(); + external_addrs.extend(self.listen_addrs.clone()); + let probe = + config.build(probe_id, external_addrs, self.connected.keys().collect()); + self.do_probe(probe); + } + } if let Some(event) = self.pending_out_event.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } @@ -685,12 +708,7 @@ impl NetworkBehaviour for Behaviour { } } if let Some(status) = self.handle_response(peer, request_id, Ok(response)) { - if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { - delay.reset(auto_retry.interval); - } - self.reachability = Reachability::from(&status); - // Enqueue event, as only one event can be returned at a time. - self.pending_out_event.push_back(status) + self.inject_status(status); } if let Some(action) = report_addr { return Poll::Ready(action); @@ -708,11 +726,7 @@ impl NetworkBehaviour for Behaviour { }, )) => { if let Some(status) = self.handle_response(peer, request_id, Err(error)) { - self.reachability = Reachability::from(&status); - if let Some((ref auto_retry, ref mut delay)) = self.auto_retry { - delay.reset(auto_retry.interval); - } - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(status)); + self.inject_status(status); } } Poll::Ready(NetworkBehaviourAction::GenerateEvent( @@ -732,6 +746,7 @@ fn filter_valid_addrs( peer: PeerId, demanded: Vec, observed_remote_at: &Multiaddr, + only_public: bool, ) -> Vec { // Skip if the observed address is a relay address. if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) { @@ -739,6 +754,9 @@ fn filter_valid_addrs( } let observed_ip = observed_remote_at.into_iter().find(|p| match p { Protocol::Ip4(ip) => { + if !only_public { + return true; + } // NOTE: The below logic is copied from `std::net::Ipv4Addr::is_global`, which at the current // point is behind the unstable `ip` feature. // See https://github.com/rust-lang/rust/issues/27709 for more info. @@ -775,7 +793,7 @@ fn filter_valid_addrs( let mut distinct = HashSet::new(); demanded .into_iter() - .filter_map(|addr| { + .filter_map(|mut addr| { // Replace the demanded ip with the observed one. let i = addr .iter() @@ -791,6 +809,9 @@ fn filter_valid_addrs( if !is_valid { return None; } + if !addr.iter().any(|p| matches!(p, Protocol::P2p(_))) { + addr.push(Protocol::P2p(peer.into())) + } // Only collect distinct addresses. distinct.insert(addr.clone()).then(|| addr) }) diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 60984000fbe..489061e7324 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -19,12 +19,14 @@ // DEALINGS IN THE SOFTWARE. //! Implementation of the AutoNAT protocol. - -pub use self::behaviour::{Behaviour, Config}; - mod behaviour; mod protocol; +pub use self::{ + behaviour::{AutoProbe, Behaviour, Config, NatStatus, ProbeConfig, Reachability, ServerConfig}, + protocol::ResponseError, +}; + mod structs_proto { include!(concat!(env!("OUT_DIR"), "/structs.rs")); } From 4560e3a22c57e1f859ac4ae051562380450023c3 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 27 Nov 2021 00:57:29 +0100 Subject: [PATCH 25/69] protocols/autonat/tests: add test --- protocols/autonat/Cargo.toml | 7 ++ protocols/autonat/tests/autonat.rs | 188 +++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 protocols/autonat/tests/autonat.rs diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index f1fe04efb2c..bc06e288d3f 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -19,3 +19,10 @@ libp2p-core = { version = "0.30.1", path = "../../core" } libp2p-swarm = { version = "0.32.0", path = "../../swarm" } libp2p-request-response = { version = "0.14.0", path = "../request-response" } prost = "0.8" + +[dev-dependencies] +async-process = "1.3" +async-std = { version = "1.10", features = ["attributes"] } +env_logger = "0.9" +libp2p = { path = "../../", default-features = false, features = ["dns-async-std", "mplex", "noise", "tcp-async-io", "websocket", "yamux"]} +netsim-embed = "0.5.2" diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs new file mode 100644 index 00000000000..1e135b0e28d --- /dev/null +++ b/protocols/autonat/tests/autonat.rs @@ -0,0 +1,188 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::{channel::oneshot, FutureExt, StreamExt}; +use libp2p::{ + development_transport, + identity::Keypair, + swarm::{NetworkBehaviour, Swarm, SwarmEvent}, + Multiaddr, PeerId, +}; +use libp2p_autonat::{ + AutoProbe, Behaviour, Config, ProbeConfig, Reachability, ResponseError, ServerConfig, +}; +use std::time::Duration; + +const SERVER_COUNT: usize = 5; +const RETRY_CONFIG: AutoProbe = AutoProbe { + interval: Duration::from_millis(100), + config: ProbeConfig { + max_peers: SERVER_COUNT, + min_confidence: 3, + servers: Vec::new(), + extend_with_connected: false, + }, +}; + +async fn init_swarm(config: Config) -> Swarm { + let keypair = Keypair::generate_ed25519(); + let local_id = PeerId::from_public_key(&keypair.public()); + let transport = development_transport(keypair).await.unwrap(); + let behaviour = Behaviour::new(local_id, config); + Swarm::new(transport, behaviour, local_id) +} + +async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { + let (tx, rx) = oneshot::channel(); + async_std::task::spawn(async move { + let mut swarm = init_swarm(Config { + auto_probe: None, + server: Some(ServerConfig { + only_public: false, + ..Default::default() + }), + ..Default::default() + }) + .await; + let peer_id = *swarm.local_peer_id(); + swarm + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + let addr = loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => break address, + _ => {} + }; + }; + tx.send((peer_id, addr)).unwrap(); + let mut kill = kill.fuse(); + loop { + futures::select! { + _ = swarm.select_next_some() => {}, + _ = kill => return, + + } + } + }); + rx.await.unwrap() +} + +#[async_std::test] +async fn test_public() { + let mut handles = Vec::new(); + + let mut client = init_swarm(Config { + auto_probe: Some(RETRY_CONFIG), + ..Default::default() + }) + .await; + + for _ in 0..SERVER_COUNT { + let (tx, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); + handles.push(tx); + } + + // Auto-Probe should directly resolve to `Unknown` as the local peer has no listening addresses + loop { + match client.select_next_some().await { + SwarmEvent::Behaviour(status) => { + assert!(status.errors.is_empty()); + assert!(status.outbound_failures.is_empty()); + assert_eq!(status.reachability, Reachability::Unknown); + assert_eq!(client.behaviour().reachability(), Reachability::Unknown); + break; + } + _ => {} + } + } + + // Hack to create a dummy ListenerId + let listener_id = init_swarm(Config::default()) + .await + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + // Artificially add a faulty address. + let unreachable_addr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); + client + .behaviour_mut() + .inject_new_listen_addr(listener_id, &unreachable_addr); + + // Auto-Probe should resolve to private since no server can reach the client at this address. + loop { + match client.select_next_some().await { + SwarmEvent::Behaviour(status) => { + assert!(status.outbound_failures.is_empty()); + let errors: Vec = + status.errors.into_iter().map(|(_, e)| e).collect(); + let expect_error_count = SERVER_COUNT - RETRY_CONFIG.config.min_confidence + 1; + assert_eq!(errors, vec![ResponseError::DialError; expect_error_count]); + assert_eq!(status.reachability, Reachability::Private); + assert_eq!(client.behaviour().reachability(), Reachability::Private); + break; + } + _ => {} + } + } + + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { .. } => break, + _ => {} + } + } + + // Client should be reachable by all servers + loop { + match client.select_next_some().await { + SwarmEvent::Behaviour(status) => { + assert!(status.errors.is_empty()); + assert!(status.outbound_failures.is_empty()); + assert!(status.reachability.is_public()); + assert!(client.behaviour().reachability().is_public()); + break; + } + _ => {} + } + } + + // Drop enough severs so that min confidence can not be reached anymore. + handles.truncate(RETRY_CONFIG.config.min_confidence - 1); + + loop { + match client.select_next_some().await { + SwarmEvent::Behaviour(status) => { + assert!(status.errors.is_empty()); + assert_eq!( + status.outbound_failures.len(), + SERVER_COUNT - RETRY_CONFIG.config.min_confidence + 1 + ); + assert_eq!(status.reachability, Reachability::Unknown); + assert_eq!(client.behaviour().reachability(), Reachability::Unknown); + break; + } + _ => {} + } + } +} From be155c74ecdaa35fd93ed675e4e1aafd23dd836b Mon Sep 17 00:00:00 2001 From: Elena Frank <57632201+elenaf9@users.noreply.github.com> Date: Sat, 27 Nov 2021 00:59:20 +0100 Subject: [PATCH 26/69] Apply suggestions from code review Co-authored-by: Max Inden --- protocols/autonat/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index bc06e288d3f..58526c81cca 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -1,5 +1,7 @@ [package] name = "libp2p-autonat" +edition = "2021" +rust-version = "1.56.1" version = "0.20.0" authors = ["David Craven ", "Elena Frank "] edition = "2018" @@ -15,7 +17,7 @@ prost-build = "0.6" async-trait = "0.1" futures = "0.3" futures-timer = "3.0.2" -libp2p-core = { version = "0.30.1", path = "../../core" } +libp2p-core = { version = "0.30.1", path = "../../core", default-features = false } libp2p-swarm = { version = "0.32.0", path = "../../swarm" } libp2p-request-response = { version = "0.14.0", path = "../request-response" } prost = "0.8" From 753a6fb768c7c2a8a5168ec242f0299461296ca5 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 27 Nov 2021 01:37:00 +0100 Subject: [PATCH 27/69] protocols/autonat/behaviour: don't track listen addrs --- protocols/autonat/src/behaviour.rs | 10 +++------- protocols/autonat/tests/autonat.rs | 15 +++++---------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index d09390695a2..940bae00ebd 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -287,9 +287,6 @@ pub struct Behaviour { // Connected peers with their observed address connected: HashMap, - // Tracked listening addresses. - listen_addrs: Vec, - // Assumed reachability derived from the most recent probe. reachability: Reachability, @@ -323,7 +320,6 @@ impl Behaviour { ongoing_outbound: None, pending_probes: VecDeque::new(), connected: HashMap::default(), - listen_addrs: Vec::new(), reachability: Reachability::Unknown, server_config: config.server, auto_probe, @@ -602,12 +598,10 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - self.listen_addrs.push(addr.clone()); } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_expired_listen_addr(id, addr); - self.listen_addrs.retain(|a| a != addr); } fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { @@ -656,7 +650,9 @@ impl NetworkBehaviour for Behaviour { .external_addresses() .map(|record| record.addr) .collect(); - external_addrs.extend(self.listen_addrs.clone()); + if external_addrs.is_empty() { + external_addrs.extend(params.listened_addresses()); + } let probe = config.build(probe_id, external_addrs, self.connected.keys().collect()); self.do_probe(probe); diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 1e135b0e28d..d0cbf15ebc7 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -22,7 +22,7 @@ use futures::{channel::oneshot, FutureExt, StreamExt}; use libp2p::{ development_transport, identity::Keypair, - swarm::{NetworkBehaviour, Swarm, SwarmEvent}, + swarm::{AddressScore, Swarm, SwarmEvent}, Multiaddr, PeerId, }; use libp2p_autonat::{ @@ -115,16 +115,9 @@ async fn test_public() { } } - // Hack to create a dummy ListenerId - let listener_id = init_swarm(Config::default()) - .await - .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) - .unwrap(); // Artificially add a faulty address. - let unreachable_addr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); - client - .behaviour_mut() - .inject_new_listen_addr(listener_id, &unreachable_addr); + let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); + client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); // Auto-Probe should resolve to private since no server can reach the client at this address. loop { @@ -142,6 +135,7 @@ async fn test_public() { _ => {} } } + client.remove_external_address(&unreachable_addr); client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) @@ -157,6 +151,7 @@ async fn test_public() { loop { match client.select_next_some().await { SwarmEvent::Behaviour(status) => { + println!("{:?}", status); assert!(status.errors.is_empty()); assert!(status.outbound_failures.is_empty()); assert!(status.reachability.is_public()); From 8a23e0054b70afec3d9fd6abf4d6d5e1f227dacf Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 27 Nov 2021 01:38:14 +0100 Subject: [PATCH 28/69] protocols/autonat/behaviour: reorder trait methods --- protocols/autonat/src/behaviour.rs | 161 ++++++++++++++--------------- 1 file changed, 80 insertions(+), 81 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 940bae00ebd..a2cc962f03b 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -490,24 +490,6 @@ impl NetworkBehaviour for Behaviour { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; type OutEvent = NatStatus; - fn new_handler(&mut self) -> Self::ProtocolsHandler { - self.inner.new_handler() - } - - fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { - self.inner.addresses_of_peer(peer) - } - - fn inject_connected(&mut self, peer: &PeerId) { - self.inner.inject_connected(peer) - } - - fn inject_disconnected(&mut self, peer: &PeerId) { - self.inner.inject_disconnected(peer); - self.ongoing_inbound.remove(peer); - self.connected.remove(peer); - } - fn inject_connection_established( &mut self, peer: &PeerId, @@ -534,39 +516,6 @@ impl NetworkBehaviour for Behaviour { .insert(*peer, endpoint.get_remote_address().clone()); } - fn inject_connection_closed( - &mut self, - peer: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - handler: ::Handler, - ) { - self.inner - .inject_connection_closed(peer, conn, endpoint, handler); - } - - fn inject_address_change( - &mut self, - peer: &PeerId, - conn: &ConnectionId, - old: &ConnectedPoint, - new: &ConnectedPoint, - ) { - self.inner.inject_address_change(peer, conn, old, new); - // Update observed address. - self.connected - .insert(*peer, new.get_remote_address().clone()); - } - - fn inject_event( - &mut self, - peer_id: PeerId, - conn: ConnectionId, - event: RequestResponseHandlerEvent, - ) { - self.inner.inject_event(peer_id, conn, event) - } - fn inject_dial_failure( &mut self, peer_id: Option, @@ -582,34 +531,22 @@ impl NetworkBehaviour for Behaviour { } } - fn inject_listen_failure( - &mut self, - local_addr: &Multiaddr, - send_back_addr: &Multiaddr, - handler: Self::ProtocolsHandler, - ) { - self.inner - .inject_listen_failure(local_addr, send_back_addr, handler) - } - - fn inject_new_listener(&mut self, id: ListenerId) { - self.inner.inject_new_listener(id) - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_new_listen_addr(id, addr); - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_expired_listen_addr(id, addr); - } - - fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { - self.inner.inject_listener_error(id, err) + fn inject_disconnected(&mut self, peer: &PeerId) { + self.inner.inject_disconnected(peer); + self.connected.remove(peer); } - fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &std::io::Error>) { - self.inner.inject_listener_closed(id, reason) + fn inject_address_change( + &mut self, + peer: &PeerId, + conn: &ConnectionId, + old: &ConnectedPoint, + new: &ConnectedPoint, + ) { + self.inner.inject_address_change(peer, conn, old, new); + // Update observed address. + self.connected + .insert(*peer, new.get_remote_address().clone()); } fn inject_new_external_addr(&mut self, addr: &Multiaddr) { @@ -624,10 +561,6 @@ impl NetworkBehaviour for Behaviour { } } - fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - self.inner.inject_expired_external_addr(addr); - } - fn poll( &mut self, cx: &mut Context<'_>, @@ -735,6 +668,72 @@ impl NetworkBehaviour for Behaviour { } } } + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + self.inner.new_handler() + } + + fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { + self.inner.addresses_of_peer(peer) + } + + fn inject_connected(&mut self, peer: &PeerId) { + self.inner.inject_connected(peer) + } + + fn inject_connection_closed( + &mut self, + peer: &PeerId, + conn: &ConnectionId, + endpoint: &ConnectedPoint, + handler: ::Handler, + ) { + self.inner + .inject_connection_closed(peer, conn, endpoint, handler); + } + + fn inject_event( + &mut self, + peer_id: PeerId, + conn: ConnectionId, + event: RequestResponseHandlerEvent, + ) { + self.inner.inject_event(peer_id, conn, event) + } + + fn inject_listen_failure( + &mut self, + local_addr: &Multiaddr, + send_back_addr: &Multiaddr, + handler: Self::ProtocolsHandler, + ) { + self.inner + .inject_listen_failure(local_addr, send_back_addr, handler) + } + + fn inject_new_listener(&mut self, id: ListenerId) { + self.inner.inject_new_listener(id) + } + + fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { + self.inner.inject_new_listen_addr(id, addr); + } + + fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { + self.inner.inject_expired_listen_addr(id, addr); + } + + fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { + self.inner.inject_listener_error(id, err) + } + + fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &std::io::Error>) { + self.inner.inject_listener_closed(id, reason) + } + + fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { + self.inner.inject_expired_external_addr(addr); + } } // Filter demanded dial addresses for validity, to prevent abuse. From 897188251e9530d350d2758ce80e70e36da311c7 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 27 Nov 2021 01:54:12 +0100 Subject: [PATCH 29/69] protocols/autonat: remove unused dev-deps --- protocols/autonat/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index a8d761ceea3..5ef953d4734 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -22,8 +22,5 @@ libp2p-request-response = { version = "0.15.0", path = "../request-response" } prost = "0.8" [dev-dependencies] -async-process = "1.3" async-std = { version = "1.10", features = ["attributes"] } -env_logger = "0.9" libp2p = { path = "../../", default-features = false, features = ["dns-async-std", "mplex", "noise", "tcp-async-io", "websocket", "yamux"]} -netsim-embed = "0.5.2" From bd0215c9ca2d706d2fd93ad2950962008be7d20e Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 28 Nov 2021 13:48:43 +0100 Subject: [PATCH 30/69] protocols/autonat/behaviour: fix duplicated server in probe --- protocols/autonat/src/behaviour.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index a2cc962f03b..cee99b03f56 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -228,7 +228,9 @@ impl ProbeConfig { if pending_servers.len() < self.max_peers && self.extend_with_connected { // TODO: use random set for peer in connected { - pending_servers.push(*peer); + if !pending_servers.contains(peer) { + pending_servers.push(*peer); + } if pending_servers.len() >= self.max_peers { break; } From ab36bfd8fd7148a820a00303a6e41d46f1e42438 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 28 Nov 2021 18:11:45 +0100 Subject: [PATCH 31/69] protocols/autonat/behaviour: fix prove evaluation Return `Reachability::Unknown` only if min_confidence for errors can not be reached anymore. --- protocols/autonat/src/behaviour.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index cee99b03f56..df6809f5763 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -172,16 +172,23 @@ impl Probe { return Err(self); } - let error_responses = self + let error_response_count = self .errors .iter() .filter(|(_, e)| matches!(e, ResponseError::DialError)) .count(); - if error_responses >= self.min_confidence { - Ok(self.into_nat_status(Reachability::Private)) - } else { - Ok(self.into_nat_status(Reachability::Unknown)) + + // Check if enough errors were received to reach min confidence. + if error_response_count >= self.min_confidence { + return Ok(self.into_nat_status(Reachability::Private)); } + + // Check if min confidence for errors can still be reached. + if self.pending_servers.len() >= self.min_confidence - error_response_count { + return Err(self); + } + + Ok(self.into_nat_status(Reachability::Unknown)) } fn into_nat_status(self, reachability: Reachability) -> NatStatus { From 960a2ed766dd90f6c836de3fb37ac429bee5f7e6 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 28 Nov 2021 18:24:54 +0100 Subject: [PATCH 32/69] protocols/autonat/behaviour: use random set of connected peers --- protocols/autonat/Cargo.toml | 1 + protocols/autonat/src/behaviour.rs | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index 5ef953d4734..db535067914 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -19,6 +19,7 @@ futures-timer = "3.0.2" libp2p-core = { version = "0.31.0", path = "../../core", default-features = false } libp2p-swarm = { version = "0.33.0", path = "../../swarm" } libp2p-request-response = { version = "0.15.0", path = "../request-response" } +rand = "0.8" prost = "0.8" [dev-dependencies] diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index df6809f5763..8574a66855b 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -233,13 +233,11 @@ impl ProbeConfig { let mut pending_servers = self.servers.clone(); pending_servers.truncate(self.max_peers); if pending_servers.len() < self.max_peers && self.extend_with_connected { - // TODO: use random set - for peer in connected { - if !pending_servers.contains(peer) { - pending_servers.push(*peer); - } - if pending_servers.len() >= self.max_peers { - break; + let mut connected: Vec<_> = connected.into_iter().copied().collect(); + while connected.len() > 0 && pending_servers.len() >= self.max_peers { + let peer = connected.remove(rand::random::() % connected.len()); + if !pending_servers.contains(&peer) { + pending_servers.push(peer); } } } From 778fbf3afc4b928d80b5d2d4f679cf15a155d49c Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 28 Nov 2021 19:33:28 +0100 Subject: [PATCH 33/69] protocols/autonat/behaviour: align ResponseErrors with Go impl --- protocols/autonat/src/behaviour.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 8574a66855b..053b75ccb54 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -410,7 +410,11 @@ impl Behaviour { } // Handle the inbound request and collect the valid addresses to be dialed. - fn handle_request(&mut self, sender: PeerId, request: DialRequest) -> Option> { + fn handle_request( + &mut self, + sender: PeerId, + request: DialRequest, + ) -> Result, ResponseError> { let config = self .server_config .as_ref() @@ -418,15 +422,15 @@ impl Behaviour { // Validate that the peer to be dialed is the request's sender. if request.peer_id != sender { - return None; + return Err(ResponseError::BadRequest); } // Check that there is no ongoing dial to the remote. if self.ongoing_inbound.contains_key(&sender) { - return None; + return Err(ResponseError::DialRefused); } // Check if max simultaneous autonat dial-requests are reached. if self.ongoing_inbound.len() >= config.max_ongoing { - return None; + return Err(ResponseError::DialRefused); } let observed_addr = self @@ -443,10 +447,10 @@ impl Behaviour { addrs.truncate(config.max_addresses); if addrs.is_empty() { - return None; + return Err(ResponseError::DialError); } - Some(addrs) + Ok(addrs) } // Update the ongoing outbound probe according to the result of our dial-request. @@ -610,7 +614,7 @@ impl NetworkBehaviour for Behaviour { request, channel, } => match self.handle_request(peer, request) { - Some(addrs) => { + Ok(addrs) => { self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); return Poll::Ready(NetworkBehaviourAction::Dial { opts: DialOpts::peer_id(peer) @@ -620,8 +624,8 @@ impl NetworkBehaviour for Behaviour { handler: self.inner.new_handler(), }); } - None => { - let response = DialResponse::Err(ResponseError::DialRefused); + Err(e) => { + let response = DialResponse::Err(e); let _ = self.inner.send_response(channel, response); } }, From 2f23e4b871421dd4f40241e7b4df2f27862c7c77 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 28 Nov 2021 20:02:45 +0100 Subject: [PATCH 34/69] protocols/autonat/behaviour: add status_text to DialResponse --- protocols/autonat/src/behaviour.rs | 60 +++++++++++++++++++++--------- protocols/autonat/src/protocol.rs | 45 +++++++++++++--------- 2 files changed, 69 insertions(+), 36 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 053b75ccb54..6d5c44a81c5 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -234,7 +234,7 @@ impl ProbeConfig { pending_servers.truncate(self.max_peers); if pending_servers.len() < self.max_peers && self.extend_with_connected { let mut connected: Vec<_> = connected.into_iter().copied().collect(); - while connected.len() > 0 && pending_servers.len() >= self.max_peers { + while !connected.is_empty() && pending_servers.len() >= self.max_peers { let peer = connected.remove(rand::random::() % connected.len()); if !pending_servers.contains(&peer) { pending_servers.push(peer); @@ -414,7 +414,7 @@ impl Behaviour { &mut self, sender: PeerId, request: DialRequest, - ) -> Result, ResponseError> { + ) -> Result, DialResponse> { let config = self .server_config .as_ref() @@ -422,15 +422,27 @@ impl Behaviour { // Validate that the peer to be dialed is the request's sender. if request.peer_id != sender { - return Err(ResponseError::BadRequest); + let response = DialResponse { + response: Err(ResponseError::BadRequest), + status_text: Some("peer id mismatch".to_string()), + }; + return Err(response); } // Check that there is no ongoing dial to the remote. if self.ongoing_inbound.contains_key(&sender) { - return Err(ResponseError::DialRefused); + let response = DialResponse { + response: Err(ResponseError::DialRefused), + status_text: Some("too many dials".to_string()), + }; + return Err(response); } // Check if max simultaneous autonat dial-requests are reached. if self.ongoing_inbound.len() >= config.max_ongoing { - return Err(ResponseError::DialRefused); + let response = DialResponse { + response: Err(ResponseError::DialRefused), + status_text: Some("too many dials".to_string()), + }; + return Err(response); } let observed_addr = self @@ -447,7 +459,11 @@ impl Behaviour { addrs.truncate(config.max_addresses); if addrs.is_empty() { - return Err(ResponseError::DialError); + let response = DialResponse { + response: Err(ResponseError::DialError), + status_text: Some("no dialable addresses".to_string()), + }; + return Err(response); } Ok(addrs) @@ -476,12 +492,17 @@ impl Behaviour { probe.pending_servers.retain(|(p, _)| p != &sender); match response { - Ok(DialResponse::Ok(addr)) => { + Ok(DialResponse { + response: Ok(addr), .. + }) => { let score = probe.addresses.entry(addr).or_insert(0); *score += 1; } - Ok(DialResponse::Err(err)) => { - probe.errors.push((sender, err)); + Ok(DialResponse { + response: Err(error), + .. + }) => { + probe.errors.push((sender, error)); } Err(err) => { probe.outbound_failures.push((sender, err)); @@ -517,9 +538,11 @@ impl NetworkBehaviour for Behaviour { if addrs.contains(address) { // Successfully dialed one of the addresses from the remote peer. let channel = self.ongoing_inbound.remove(peer).unwrap().1; - let _ = self - .inner - .send_response(channel, DialResponse::Ok(address.clone())); + let response = DialResponse { + response: Ok(address.clone()), + status_text: None, + }; + let _ = self.inner.send_response(channel, response); } } } @@ -536,9 +559,11 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_dial_failure(peer_id, handler, error); if let Some((_, channel)) = peer_id.and_then(|p| self.ongoing_inbound.remove(&p)) { // Failed to dial any of the addresses sent by the remote peer in their dial-request. - let _ = self - .inner - .send_response(channel, DialResponse::Err(ResponseError::DialError)); + let response = DialResponse { + response: Err(ResponseError::DialError), + status_text: Some("dial failed".to_string()), + }; + let _ = self.inner.send_response(channel, response); } } @@ -624,8 +649,7 @@ impl NetworkBehaviour for Behaviour { handler: self.inner.new_handler(), }); } - Err(e) => { - let response = DialResponse::Err(e); + Err(response) => { let _ = self.inner.send_response(channel, response); } }, @@ -634,7 +658,7 @@ impl NetworkBehaviour for Behaviour { response, } => { let mut report_addr = None; - if let DialResponse::Ok(ref addr) = response { + if let Ok(ref addr) = response.response { // Update observed address score if it is finite. let score = params .external_addresses() diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index 5ffeb95c4de..218bbf0f1b1 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -201,9 +201,9 @@ impl TryFrom for ResponseError { } #[derive(Clone, Debug, Eq, PartialEq)] -pub enum DialResponse { - Ok(Multiaddr), - Err(ResponseError), +pub struct DialResponse { + pub status_text: Option, + pub response: Result, } impl DialResponse { @@ -217,21 +217,24 @@ impl DialResponse { Ok(match msg.dial_response { Some(structs_proto::message::DialResponse { status: Some(0), - status_text: None, + status_text, addr: Some(addr), }) => { let addr = Multiaddr::try_from(addr) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - Self::Ok(addr) + Self { + status_text, + response: Ok(addr), + } } Some(structs_proto::message::DialResponse { status: Some(status), - status_text: _, + status_text, addr: None, - }) => { - let status = ResponseError::try_from(status)?; - Self::Err(status) - } + }) => Self { + status_text, + response: Err(ResponseError::try_from(status)?), + }, _ => { return Err(io::Error::new( io::ErrorKind::InvalidData, @@ -242,15 +245,15 @@ impl DialResponse { } pub fn into_bytes(self) -> Vec { - let dial_response = match self { - Self::Ok(addr) => structs_proto::message::DialResponse { + let dial_response = match self.response { + Ok(addr) => structs_proto::message::DialResponse { status: Some(0), - status_text: None, + status_text: self.status_text, addr: Some(addr.to_vec()), }, - Self::Err(status) => structs_proto::message::DialResponse { - status: Some(status.into()), - status_text: None, + Err(error) => structs_proto::message::DialResponse { + status: Some(error.into()), + status_text: self.status_text, addr: None, }, }; @@ -288,7 +291,10 @@ mod tests { #[test] fn test_response_ok_encode_decode() { - let response = DialResponse::Ok("/ip4/8.8.8.8/tcp/30333".parse().unwrap()); + let response = DialResponse { + response: Ok("/ip4/8.8.8.8/tcp/30333".parse().unwrap()), + status_text: None, + }; let bytes = response.clone().into_bytes(); let response2 = DialResponse::from_bytes(&bytes).unwrap(); assert_eq!(response, response2); @@ -296,7 +302,10 @@ mod tests { #[test] fn test_response_err_encode_decode() { - let response = DialResponse::Err(ResponseError::DialError); + let response = DialResponse { + response: Err(ResponseError::DialError), + status_text: Some("dial failed".to_string()), + }; let bytes = response.clone().into_bytes(); let response2 = DialResponse::from_bytes(&bytes).unwrap(); assert_eq!(response, response2); From ebd9d2a4d8b6f606a8ee5f3192a03e153743b9d0 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 28 Nov 2021 23:13:58 +0100 Subject: [PATCH 35/69] protocols/autonat/behaviour: fix & test filter_valid_addrs --- protocols/autonat/Cargo.toml | 1 + protocols/autonat/src/behaviour.rs | 86 +++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index db535067914..d347db346e5 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -19,6 +19,7 @@ futures-timer = "3.0.2" libp2p-core = { version = "0.31.0", path = "../../core", default-features = false } libp2p-swarm = { version = "0.33.0", path = "../../swarm" } libp2p-request-response = { version = "0.15.0", path = "../request-response" } +log = "0.4" rand = "0.8" prost = "0.8" diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 6d5c44a81c5..2afadd4c7f5 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -823,12 +823,12 @@ fn filter_valid_addrs( let mut distinct = HashSet::new(); demanded .into_iter() - .filter_map(|mut addr| { + .filter_map(|addr| { // Replace the demanded ip with the observed one. let i = addr .iter() .position(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))?; - addr.replace(i, |_| Some(observed_ip.clone()))?; + let mut addr = addr.replace(i, |_| Some(observed_ip.clone()))?; // Filter relay addresses and addresses with invalid peer id. let is_valid = addr.iter().all(|proto| match proto { Protocol::P2pCircuit => false, @@ -847,3 +847,85 @@ fn filter_valid_addrs( }) .collect() } + +#[cfg(test)] +mod test { + use super::*; + + use std::net::Ipv4Addr; + + fn random_ip<'a>() -> Protocol<'a> { + Protocol::Ip4(Ipv4Addr::new( + rand::random(), + rand::random(), + rand::random(), + rand::random(), + )) + } + fn random_port<'a>() -> Protocol<'a> { + Protocol::Tcp(rand::random()) + } + + #[test] + fn filter_addresses() { + let peer_id = PeerId::random(); + let observed_ip = random_ip(); + let observed_addr = Multiaddr::empty() + .with(observed_ip.clone()) + .with(random_port()) + .with(Protocol::P2p(peer_id.into())); + // Valid address with matching peer-id + let demanded_1 = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(peer_id.into())); + // Invalid because peer_id does not match + let demanded_2 = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(PeerId::random().into())); + // Valid address without peer-id + let demanded_3 = Multiaddr::empty().with(random_ip()).with(random_port()); + // Invalid because relayed + let demanded_4 = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(PeerId::random().into())) + .with(Protocol::P2pCircuit) + .with(Protocol::P2p(peer_id.into())); + let demanded = vec![ + demanded_1.clone(), + demanded_2, + demanded_3.clone(), + demanded_4, + ]; + let filtered = filter_valid_addrs(peer_id, demanded, &observed_addr, false); + let expected_1 = demanded_1 + .replace(0, |_| Some(observed_ip.clone())) + .unwrap(); + let expected_2 = demanded_3 + .replace(0, |_| Some(observed_ip)) + .unwrap() + .with(Protocol::P2p(peer_id.into())); + assert_eq!(filtered, vec![expected_1, expected_2]); + } + + #[test] + fn skip_relayed_addr() { + let peer_id = PeerId::random(); + let observed_ip = random_ip(); + // Observed address is relayed. + let observed_addr = Multiaddr::empty() + .with(observed_ip.clone()) + .with(random_port()) + .with(Protocol::P2p(PeerId::random().into())) + .with(Protocol::P2pCircuit) + .with(Protocol::P2p(peer_id.into())); + let demanded = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(peer_id.into())); + let filtered = filter_valid_addrs(peer_id, vec![demanded], &observed_addr, false); + assert!(filtered.is_empty()); + } +} From 44f684afd536e18e39377ba961c0fedb940fe816 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 29 Nov 2021 00:32:15 +0100 Subject: [PATCH 36/69] protocols/autonat: add debug messages --- protocols/autonat/src/behaviour.rs | 97 +++++++++++++++++++++++------- protocols/autonat/src/protocol.rs | 13 ++-- 2 files changed, 85 insertions(+), 25 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 2afadd4c7f5..496ad0111ea 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -192,13 +192,15 @@ impl Probe { } fn into_nat_status(self, reachability: Reachability) -> NatStatus { - NatStatus { + let status = NatStatus { reachability, errors: self.errors, tried_addresses: self.addresses.into_iter().collect(), outbound_failures: self.outbound_failures, probe_id: self.id, - } + }; + log::debug!("Probe {:?} resolved to NAT Status: {:?}.", self.id, status); + status } } @@ -422,25 +424,43 @@ impl Behaviour { // Validate that the peer to be dialed is the request's sender. if request.peer_id != sender { + let status_text = "peer id mismatch".to_string(); + log::debug!( + "Reject inbound dial request from peer {}: {}.", + sender, + status_text + ); let response = DialResponse { response: Err(ResponseError::BadRequest), - status_text: Some("peer id mismatch".to_string()), + status_text: Some(status_text), }; return Err(response); } // Check that there is no ongoing dial to the remote. if self.ongoing_inbound.contains_key(&sender) { + let status_text = "too many dials".to_string(); + log::debug!( + "Reject inbound dial request from peer {}: {}.", + sender, + status_text + ); let response = DialResponse { response: Err(ResponseError::DialRefused), - status_text: Some("too many dials".to_string()), + status_text: Some(status_text), }; return Err(response); } // Check if max simultaneous autonat dial-requests are reached. if self.ongoing_inbound.len() >= config.max_ongoing { + let status_text = "too many dials".to_string(); + log::debug!( + "Reject inbound dial request from peer {}: {}.", + sender, + status_text + ); let response = DialResponse { response: Err(ResponseError::DialRefused), - status_text: Some("too many dials".to_string()), + status_text: Some(status_text), }; return Err(response); } @@ -459,9 +479,15 @@ impl Behaviour { addrs.truncate(config.max_addresses); if addrs.is_empty() { + let status_text = "no dialable addresses".to_string(); + log::debug!( + "Reject inbound dial request from peer {}: {}.", + sender, + status_text + ); let response = DialResponse { response: Err(ResponseError::DialError), - status_text: Some("no dialable addresses".to_string()), + status_text: Some(status_text), }; return Err(response); } @@ -495,13 +521,24 @@ impl Behaviour { Ok(DialResponse { response: Ok(addr), .. }) => { + log::debug!( + "Successful dial-back from peer {} to address {:?}", + sender, + addr + ); let score = probe.addresses.entry(addr).or_insert(0); *score += 1; } Ok(DialResponse { response: Err(error), - .. + status_text, }) => { + log::debug!( + "Failed dial-back from peer {}: {:?} {:?}", + sender, + error, + status_text + ); probe.errors.push((sender, error)); } Err(err) => { @@ -536,6 +573,11 @@ impl NetworkBehaviour for Behaviour { if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { // Check if the dialed address was among the requested addresses. if addrs.contains(address) { + log::debug!( + "Dial-back to peer {} succeeded at addr {:?}.", + peer, + address + ); // Successfully dialed one of the addresses from the remote peer. let channel = self.ongoing_inbound.remove(peer).unwrap().1; let response = DialResponse { @@ -558,6 +600,11 @@ impl NetworkBehaviour for Behaviour { ) { self.inner.inject_dial_failure(peer_id, handler, error); if let Some((_, channel)) = peer_id.and_then(|p| self.ongoing_inbound.remove(&p)) { + log::debug!( + "Dial-back to peer {} failed with error {:?}.", + peer_id.unwrap(), + error + ); // Failed to dial any of the addresses sent by the remote peer in their dial-request. let response = DialResponse { response: Err(ResponseError::DialError), @@ -622,6 +669,11 @@ impl NetworkBehaviour for Behaviour { if external_addrs.is_empty() { external_addrs.extend(params.listened_addresses()); } + log::debug!( + "Starting outbound probe {:?} with dial-back addresses {:?}.", + probe_id, + external_addrs + ); let probe = config.build(probe_id, external_addrs, self.connected.keys().collect()); self.do_probe(probe); @@ -638,21 +690,24 @@ impl NetworkBehaviour for Behaviour { request_id: _, request, channel, - } => match self.handle_request(peer, request) { - Ok(addrs) => { - self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); - return Poll::Ready(NetworkBehaviourAction::Dial { - opts: DialOpts::peer_id(peer) - .condition(PeerCondition::Always) - .addresses(addrs) - .build(), - handler: self.inner.new_handler(), - }); - } - Err(response) => { - let _ = self.inner.send_response(channel, response); + } => { + match self.handle_request(peer, request) { + Ok(addrs) => { + log::debug!("Inbound dial request from Peer {} with dial-back addresses {:?}.", peer, addrs); + self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); + return Poll::Ready(NetworkBehaviourAction::Dial { + opts: DialOpts::peer_id(peer) + .condition(PeerCondition::Always) + .addresses(addrs) + .build(), + handler: self.inner.new_handler(), + }); + } + Err(response) => { + let _ = self.inner.send_response(channel, response); + } } - }, + } RequestResponseMessage::Response { request_id, response, diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index 218bbf0f1b1..9313bd92c66 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -120,6 +120,7 @@ impl DialRequest { { (peer_id, addrs) } else { + log::debug!("Received malformed dial message."); return Err(io::Error::new( io::ErrorKind::InvalidData, "invalid dial message", @@ -192,10 +193,13 @@ impl TryFrom for ResponseError { 101 => Ok(ResponseError::DialRefused), 200 => Ok(ResponseError::BadRequest), 300 => Ok(ResponseError::InternalError), - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - "invalid response error type", - )), + _ => { + log::debug!("Received response with invalid status code."); + Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid response error type", + )) + } } } } @@ -236,6 +240,7 @@ impl DialResponse { response: Err(ResponseError::try_from(status)?), }, _ => { + log::debug!("Received malformed response message."); return Err(io::Error::new( io::ErrorKind::InvalidData, "invalid dial response message", From b75f0ddde5c3fedafc60bde226e647a5442dfa41 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 29 Nov 2021 13:02:09 +0100 Subject: [PATCH 37/69] protocols/autonat/examples/client: Address review comments --- protocols/autonat/Cargo.toml | 2 +- protocols/autonat/examples/client.rs | 31 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index 9ba07b05e41..11964335a57 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -26,5 +26,5 @@ prost = "0.8" [dev-dependencies] async-std = { version = "1.10", features = ["attributes"] } env_logger = "0.9.0" -libp2p = { path = "../../", default-features = false, features = ["dns-async-std", "mplex", "noise", "tcp-async-io", "websocket", "yamux"]} +libp2p = { path = "../../", default-features = false, features = ["autonat", "dns-async-std", "identify", "mplex", "noise", "tcp-async-io", "websocket", "yamux"]} structopt = "0.3.21" diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index 4eefaa6cc45..5853f8f9ce0 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -21,14 +21,14 @@ use futures::prelude::*; use libp2p::autonat; use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; -use libp2p::swarm::{AddressScore, Swarm, SwarmEvent}; +use libp2p::swarm::{Swarm, SwarmEvent}; use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId}; use std::error::Error; use std::time::Duration; use structopt::StructOpt; #[derive(Debug, StructOpt)] -#[structopt(name = "libp2p relay")] +#[structopt(name = "libp2p autonat")] struct Opt { #[structopt(long)] server_address: Multiaddr, @@ -57,11 +57,7 @@ async fn main() -> Result<(), Box> { swarm .behaviour_mut() .auto_nat - .add_server(opt.server_peer_id, Some(opt.server_address)) - .then(|| Some(())) - .expect("Auto retry to be enabled."); - - swarm.add_external_address(Multiaddr::empty(), AddressScore::Infinite); + .add_server(opt.server_peer_id, Some(opt.server_address)); loop { match swarm.select_next_some().await { @@ -76,7 +72,7 @@ async fn main() -> Result<(), Box> { #[behaviour(out_event = "Event")] struct Behaviour { identify: Identify, - auto_nat: autonat::behaviour::Behaviour, + auto_nat: autonat::Behaviour, } impl Behaviour { @@ -86,14 +82,17 @@ impl Behaviour { "/ipfs/0.1.0".into(), local_public_key.clone(), )), - auto_nat: autonat::behaviour::Behaviour::new( + auto_nat: autonat::Behaviour::new( local_public_key.to_peer_id(), - autonat::behaviour::Config { - auto_retry: Some(autonat::behaviour::AutoRetry { + autonat::Config { + auto_probe: Some(autonat::AutoProbe { interval: Duration::from_secs(10), - config: autonat::behaviour::ProbeConfig::default(), + config: autonat::ProbeConfig { + min_confidence: 1, // set to 1 since we only have one server + ..Default::default() + }, }), - ..autonat::behaviour::Config::default() + ..autonat::Config::default() }, ), } @@ -102,7 +101,7 @@ impl Behaviour { #[derive(Debug)] enum Event { - AutoNat(autonat::behaviour::NatStatus), + AutoNat(autonat::NatStatus), Identify(IdentifyEvent), } @@ -112,8 +111,8 @@ impl From for Event { } } -impl From for Event { - fn from(v: autonat::behaviour::NatStatus) -> Self { +impl From for Event { + fn from(v: autonat::NatStatus) -> Self { Self::AutoNat(v) } } From 3dfaa1cfde148ad3cf61b37876a733b05303c808 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 5 Dec 2021 23:03:23 +0100 Subject: [PATCH 38/69] protocols/autonat: statefull evaluation of probes Refactor evaluation of probes: - only send a dial-request to a single server in a probe - evaluate the dial-response with respect to the current reachability and confidence - schedule next probe depending on confidence --- protocols/autonat/Cargo.toml | 7 +- protocols/autonat/examples/client.rs | 18 +- protocols/autonat/src/behaviour.rs | 760 ++++++++++----------------- protocols/autonat/src/lib.rs | 2 +- protocols/autonat/src/protocol.rs | 27 +- protocols/autonat/tests/autonat.rs | 91 +--- 6 files changed, 338 insertions(+), 567 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index 11964335a57..37f5a4230b9 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -15,7 +15,8 @@ prost-build = "0.6" [dependencies] async-trait = "0.1" futures = "0.3" -futures-timer = "3.0.2" +futures-timer = "3.0" +instant = "0.1" libp2p-core = { version = "0.31.0", path = "../../core", default-features = false } libp2p-swarm = { version = "0.33.0", path = "../../swarm" } libp2p-request-response = { version = "0.15.0", path = "../request-response" } @@ -25,6 +26,6 @@ prost = "0.8" [dev-dependencies] async-std = { version = "1.10", features = ["attributes"] } -env_logger = "0.9.0" +env_logger = "0.9" libp2p = { path = "../../", default-features = false, features = ["autonat", "dns-async-std", "identify", "mplex", "noise", "tcp-async-io", "websocket", "yamux"]} -structopt = "0.3.21" +structopt = "0.3" diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index 5853f8f9ce0..3c40f11b357 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -24,7 +24,6 @@ use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; use libp2p::swarm::{Swarm, SwarmEvent}; use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId}; use std::error::Error; -use std::time::Duration; use structopt::StructOpt; #[derive(Debug, StructOpt)] @@ -84,16 +83,7 @@ impl Behaviour { )), auto_nat: autonat::Behaviour::new( local_public_key.to_peer_id(), - autonat::Config { - auto_probe: Some(autonat::AutoProbe { - interval: Duration::from_secs(10), - config: autonat::ProbeConfig { - min_confidence: 1, // set to 1 since we only have one server - ..Default::default() - }, - }), - ..autonat::Config::default() - }, + autonat::Config::default(), ), } } @@ -101,7 +91,7 @@ impl Behaviour { #[derive(Debug)] enum Event { - AutoNat(autonat::NatStatus), + AutoNat(autonat::Reachability), Identify(IdentifyEvent), } @@ -111,8 +101,8 @@ impl From for Event { } } -impl From for Event { - fn from(v: autonat::NatStatus) -> Self { +impl From for Event { + fn from(v: autonat::Reachability) -> Self { Self::AutoNat(v) } } diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 496ad0111ea..6d959c181da 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -21,15 +21,15 @@ use crate::protocol::{AutoNatCodec, AutoNatProtocol, DialRequest, DialResponse, ResponseError}; use futures::FutureExt; use futures_timer::Delay; +use instant::Instant; use libp2p_core::{ connection::{ConnectionId, ListenerId}, multiaddr::Protocol, ConnectedPoint, Multiaddr, PeerId, }; use libp2p_request_response::{ - handler::RequestResponseHandlerEvent, OutboundFailure, ProtocolSupport, RequestId, - RequestResponse, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, - ResponseChannel, + handler::RequestResponseHandlerEvent, ProtocolSupport, RequestId, RequestResponse, + RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, }; use libp2p_swarm::{ dial_opts::{DialOpts, PeerCondition}, @@ -43,65 +43,58 @@ use std::{ time::Duration, }; +/// Configure whether probes should preferably select statically added servers for the next probe. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SelectServer { + /// Prioritize the usage of static servers. + /// Fallback on connected peers if there is no server, or the servers are throttled through [`Config::throttlePeerPeriod`]. + PrioritizeStatic, + /// Only use static servers. + /// Resolve to status unknown if there are none. + ExclusivelyStatic, + /// Randomly select a target from the list of static servers and connected peers. + Random, +} + /// Config for the [`Behaviour`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { /// Timeout for requests. pub timeout: Duration, - /// Config if the peer should frequently re-determine its status. - pub auto_probe: Option, - - /// Config if the current peer also serves as server for other peers. - /// In case of `None`, the local peer will never do dial-attempts for other peers. - pub server: Option, -} + // == Client Config + /// Delay on init before starting the fist probe + pub boot_delay: Duration, + /// Interval in which the NAT should be tested again if + pub refresh_interval: Duration, + /// Interval in which the NAT should be re-tried if the current status is unknown. + pub retry_interval: Duration, -impl Default for Config { - fn default() -> Self { - Config { - timeout: Duration::from_secs(30), - auto_probe: Some(AutoProbe::default()), - server: Some(ServerConfig::default()), - } - } -} + pub throttle_peer_period: Duration, -/// Automatically retry the current NAT status at a certain frequency. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AutoProbe { - /// Interval in which the NAT should be tested. - pub interval: Duration, - /// Config for the frequent probes. - pub config: ProbeConfig, -} + pub select_server: SelectServer, -impl Default for AutoProbe { - fn default() -> Self { - AutoProbe { - interval: Duration::from_secs(90), - config: ProbeConfig::default(), - } - } -} + pub confidence_max: usize, -/// Config if the local peer may serve a server for other peers and do dial-attempts for them. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ServerConfig { + //== Server Config /// Max addresses that are tried per peer. - pub max_addresses: usize, - /// Max simultaneous autonat dial-attempts. - pub max_ongoing: usize, - /// Dial only addresses in public IP range. - pub only_public: bool, + pub max_peer_addresses: usize, + /// Max total simultaneous dial-attempts. + pub throttle_global_max: usize, } -impl Default for ServerConfig { +impl Default for Config { fn default() -> Self { - ServerConfig { - max_addresses: 10, - max_ongoing: 10, - only_public: true, + Config { + timeout: Duration::from_secs(30), + boot_delay: Duration::from_secs(15), + retry_interval: Duration::from_secs(90), + refresh_interval: Duration::from_secs(15 * 60), + throttle_peer_period: Duration::from_secs(90), + select_server: SelectServer::Random, + confidence_max: 3, + max_peer_addresses: 16, + throttle_global_max: 30, } } } @@ -115,300 +108,151 @@ pub enum Reachability { } impl Reachability { - /// Whether we can assume ourself to be public - pub fn is_public(&self) -> bool { - matches!(self, Reachability::Public(_)) - } -} - -impl From<&NatStatus> for Reachability { - fn from(status: &NatStatus) -> Self { - status.reachability.clone() - } -} - -/// Identifier for a NAT-status probe. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ProbeId(u64); - -/// Single probe for determining out NAT-Status -#[derive(Debug, Clone, PartialEq)] -pub struct Probe { - id: ProbeId, - // Min. required Dial successes or failures for us to determine out status. - // If the confidence can not be reached, out reachability will be `Unknown` - min_confidence: usize, - // Number of servers to which we sent a dial request. - server_count: usize, - // External addresses and the current number ob successful dials to them reported by remote peers. - // - // Apart from our demanded addresses this may also contains addresses that the remote observed us at. - addresses: HashMap, - // Serves from which we did not receive a response yet. - pending_servers: Vec<(PeerId, Option)>, - // Received response errors. - errors: Vec<(PeerId, ResponseError)>, - // Failed requests. - outbound_failures: Vec<(PeerId, OutboundFailure)>, -} - -impl Probe { - // Evaluate current state to whether we already have enough results to derive the NAT status. - fn evaluate(self) -> Result { - // Find highest score of successful dials for an address. - let (address, highest) = match self.addresses.iter().max_by(|(_, a), (_, b)| a.cmp(b)) { - Some(max) => max, - None => return Ok(self.into_nat_status(Reachability::Unknown)), - }; - - // Check if the required amount of successful dials was reached. - if *highest >= self.min_confidence { - let addr = address.clone(); - return Ok(self.into_nat_status(Reachability::Public(addr))); - } - - // Check if the probe can still be successful. - if self.pending_servers.len() >= self.min_confidence - highest { - return Err(self); - } - - let error_response_count = self - .errors - .iter() - .filter(|(_, e)| matches!(e, ResponseError::DialError)) - .count(); - - // Check if enough errors were received to reach min confidence. - if error_response_count >= self.min_confidence { - return Ok(self.into_nat_status(Reachability::Private)); - } - - // Check if min confidence for errors can still be reached. - if self.pending_servers.len() >= self.min_confidence - error_response_count { - return Err(self); + pub fn public_addr(&self) -> Option<&Multiaddr> { + match self { + Reachability::Public(address) => Some(address), + _ => None, } - - Ok(self.into_nat_status(Reachability::Unknown)) } - - fn into_nat_status(self, reachability: Reachability) -> NatStatus { - let status = NatStatus { - reachability, - errors: self.errors, - tried_addresses: self.addresses.into_iter().collect(), - outbound_failures: self.outbound_failures, - probe_id: self.id, - }; - log::debug!("Probe {:?} resolved to NAT Status: {:?}.", self.id, status); - status - } -} - -// Configuration for a single probe. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ProbeConfig { - /// List of trusted public peers that are probed when attempting to determine the auto-nat status. - pub servers: Vec, - /// Whether the list of servers should be extended with currently connected peers, up to `max_peers`. - pub extend_with_connected: bool, - /// Max peers to send a dial-request to. - pub max_peers: usize, - /// Minimum amount of DialResponse::Ok / ResponseError::DialFailure from different remote peers, - /// for it to count as a valid result. If the minimum confidence was not reached, the reachability will be - /// [`Reachability::Unknown`]. - pub min_confidence: usize, } -impl Default for ProbeConfig { - fn default() -> Self { - ProbeConfig { - servers: Vec::new(), - extend_with_connected: true, - max_peers: 10, - min_confidence: 3, +impl From> for Reachability { + fn from(result: Result) -> Self { + match result { + Ok(addr) => Reachability::Public(addr), + Err(ResponseError::DialError) => Reachability::Private, + _ => Reachability::Unknown, } } } -impl ProbeConfig { - fn build(&self, id: ProbeId, addresses: Vec, connected: Vec<&PeerId>) -> Probe { - let mut pending_servers = self.servers.clone(); - pending_servers.truncate(self.max_peers); - if pending_servers.len() < self.max_peers && self.extend_with_connected { - let mut connected: Vec<_> = connected.into_iter().copied().collect(); - while !connected.is_empty() && pending_servers.len() >= self.max_peers { - let peer = connected.remove(rand::random::() % connected.len()); - if !pending_servers.contains(&peer) { - pending_servers.push(peer); - } - } - } - let addresses = addresses.into_iter().map(|a| (a, 0)).collect(); - let pending_servers: Vec<_> = pending_servers.into_iter().map(|p| (p, None)).collect(); - - Probe { - id, - min_confidence: self.min_confidence, - server_count: pending_servers.len(), - pending_servers, - addresses, - errors: Vec::new(), - outbound_failures: Vec::new(), - } - } -} - -/// Outcome of a [`Probe`]. -#[derive(Debug, Clone, PartialEq)] -pub struct NatStatus { - /// Our assumed reachability derived from the dial responses we received - pub reachability: Reachability, - /// External addresses and the number ob successful dials to them reported by remote peers. - /// - /// Apart from our demanded addresses this may also contains addresses that the remote observed us at. - pub tried_addresses: Vec<(Multiaddr, usize)>, - /// Received dial-response errors. - pub errors: Vec<(PeerId, ResponseError)>, - /// Failed requests. - pub outbound_failures: Vec<(PeerId, OutboundFailure)>, - - /// Id of the probe that resulted in this status. - pub probe_id: ProbeId, -} - /// Network Behaviour for AutoNAT. pub struct Behaviour { - // Own peer id + // Local peer id local_peer_id: PeerId, // Inner protocol for sending requests and receiving the response. inner: RequestResponse, - // Ongoing inbound requests, where no response has been sent back to the remote yet. - ongoing_inbound: HashMap, ResponseChannel)>, - - // Ongoing outbound dial-requests, where no response has been received from the remote yet. - ongoing_outbound: Option, - - // Manually initiated probe. - pending_probes: VecDeque<(ProbeId, ProbeConfig)>, + config: Config, - // Connected peers with their observed address - connected: HashMap, + // Peers that may not be connected but still can be used as server in a probe. + static_servers: Vec, // Assumed reachability derived from the most recent probe. reachability: Reachability, - // See `Config::server` - server_config: Option, + // Confidence in the assumed reachability. + confidence: usize, + + // Delay until next probe. `None` if there is an ongoing probe. + schedule_probe: Option, + + // Ongoing inbound requests, where no response has been sent back to the remote yet. + ongoing_inbound: HashMap, ResponseChannel)>, - // See `Config::auto_probe` - auto_probe: Option<(AutoProbe, Delay)>, + // Connected peers with their observed address. + connected: HashMap, - // Out events that should be reported to the user - pending_out_event: VecDeque, + // Used servers in recent outbound probes. + recent_probes: Vec<(PeerId, Instant)>, - next_probe_id: ProbeId, + pending_out_event: VecDeque<::OutEvent>, } impl Behaviour { pub fn new(local_peer_id: PeerId, config: Config) -> Self { - let proto_support = match config.server { - Some(_) => ProtocolSupport::Full, - None => ProtocolSupport::Outbound, - }; - let protocols = iter::once((AutoNatProtocol, proto_support)); + let protocols = iter::once((AutoNatProtocol, ProtocolSupport::Full)); let mut cfg = RequestResponseConfig::default(); cfg.set_request_timeout(config.timeout); let inner = RequestResponse::new(AutoNatCodec, protocols, cfg); - let auto_probe = config.auto_probe.map(|a| (a, Delay::new(Duration::ZERO))); Self { local_peer_id, inner, + schedule_probe: Some(Delay::new(config.boot_delay)), + config, + static_servers: Vec::new(), ongoing_inbound: HashMap::default(), - ongoing_outbound: None, - pending_probes: VecDeque::new(), connected: HashMap::default(), reachability: Reachability::Unknown, - server_config: config.server, - auto_probe, + confidence: 0, + recent_probes: Vec::new(), pending_out_event: VecDeque::new(), - next_probe_id: ProbeId(1), } } - // Manually retry determination of NAT status. - pub fn retry_nat_status(&mut self, probe_config: ProbeConfig) -> ProbeId { - let id = self.next_probe_id(); - self.pending_probes.push_back((id, probe_config)); - id + /// Address if we are public. + pub fn public_address(&self) -> Option<&Multiaddr> { + self.reachability.public_addr() } - // Assumed reachability derived from the most recent probe. + /// Currently assumed reachability. pub fn reachability(&self) -> Reachability { self.reachability.clone() } - /// Add peer to the list of trusted public peers that are probed in the auto-retry nat status. - /// - /// Return false if auto-retry is `None`. - pub fn add_server(&mut self, peer: PeerId, address: Option) -> bool { - match self.auto_probe { - Some((ref mut retry, _)) => { - retry.config.servers.push(peer); - if let Some(addr) = address { - self.inner.add_address(&peer, addr); - } - true - } - None => false, - } + /// Confidence in out current reachability status. + pub fn confidence(&self) -> usize { + self.confidence } - /// Remove a peer from the list of servers tried in the auto-retry. - /// - /// Return false if auto-retry is `None`. - pub fn remove_server(&mut self, peer: &PeerId) -> bool { - match self.auto_probe { - Some((ref mut retry, _)) => { - retry.config.servers.retain(|p| p != peer); - true - } - None => false, + pub fn add_server(&mut self, peer: PeerId, address: Option) { + self.static_servers.push(peer); + if let Some(addr) = address { + self.inner.add_address(&peer, addr); } } - fn next_probe_id(&mut self) -> ProbeId { - let probe_id = self.next_probe_id; - self.next_probe_id.0 += 1; - probe_id + pub fn remove_server(&mut self, peer: &PeerId) { + self.static_servers.retain(|p| p != peer); } - fn do_probe(&mut self, probe: Probe) { - // Check if the probe can already be resolved, e.g because there are no external addresses. - let mut probe = match probe.evaluate() { - Ok(status) => return self.inject_status(status), - Err(probe) => probe, - }; - for (peer_id, id) in probe.pending_servers.iter_mut() { - let request_id = self.inner.send_request( - peer_id, - DialRequest { - peer_id: self.local_peer_id, - addrs: probe.addresses.keys().cloned().collect(), - }, - ); - let _ = id.insert(request_id); + // Send a dial request to a randomly selected server. + // Return `None` if there are no qualified servers or no addresses. + fn do_probe(&mut self, addresses: Vec) -> Option { + if addresses.is_empty() { + return None; } - let _ = self.ongoing_outbound.insert(probe); - } + self.recent_probes + .retain(|(_, time)| *time + self.config.throttle_peer_period > Instant::now()); + let throttled: Vec<_> = self.recent_probes.iter().map(|(id, _)| id).collect(); + let mut servers = match self.config.select_server { + SelectServer::ExclusivelyStatic => self.static_servers.clone(), + SelectServer::PrioritizeStatic => { + let mut servers = Vec::new(); + for server in &self.static_servers { + if !throttled.contains(&server) { + servers.push(*server) + } + } + if servers.is_empty() { + servers.extend(self.connected.iter().map(|(id, _)| *id)); + } + servers + } + SelectServer::Random => { + let mut connected: Vec<_> = self.connected.iter().map(|(id, _)| *id).collect(); + connected.extend(&self.static_servers); + connected + } + }; - fn inject_status(&mut self, status: NatStatus) { - self.reachability = Reachability::from(&status); - self.pending_out_event.push_back(status); - if let Some((ref auto_probe, ref mut delay)) = self.auto_probe { - delay.reset(auto_probe.interval); + servers.retain(|s| !throttled.contains(&s)); + if servers.is_empty() { + return None; } + let server = servers + .get(rand::random::() % servers.len()) + .expect("Element is present."); + let request_id = self.inner.send_request( + server, + DialRequest { + peer_id: self.local_peer_id, + addresses, + }, + ); + self.schedule_probe = None; + Some(request_id) } // Handle the inbound request and collect the valid addresses to be dialed. @@ -417,12 +261,6 @@ impl Behaviour { sender: PeerId, request: DialRequest, ) -> Result, DialResponse> { - let config = self - .server_config - .as_ref() - .expect("Server config is present."); - - // Validate that the peer to be dialed is the request's sender. if request.peer_id != sender { let status_text = "peer id mismatch".to_string(); log::debug!( @@ -431,12 +269,12 @@ impl Behaviour { status_text ); let response = DialResponse { - response: Err(ResponseError::BadRequest), + result: Err(ResponseError::BadRequest), status_text: Some(status_text), }; return Err(response); } - // Check that there is no ongoing dial to the remote. + if self.ongoing_inbound.contains_key(&sender) { let status_text = "too many dials".to_string(); log::debug!( @@ -445,21 +283,21 @@ impl Behaviour { status_text ); let response = DialResponse { - response: Err(ResponseError::DialRefused), + result: Err(ResponseError::DialRefused), status_text: Some(status_text), }; return Err(response); } - // Check if max simultaneous autonat dial-requests are reached. - if self.ongoing_inbound.len() >= config.max_ongoing { - let status_text = "too many dials".to_string(); + + if self.ongoing_inbound.len() >= self.config.throttle_global_max { + let status_text = "too many total dials".to_string(); log::debug!( "Reject inbound dial request from peer {}: {}.", sender, status_text ); let response = DialResponse { - response: Err(ResponseError::DialRefused), + result: Err(ResponseError::DialRefused), status_text: Some(status_text), }; return Err(response); @@ -469,14 +307,9 @@ impl Behaviour { .connected .get(&sender) .expect("We are connected to the peer."); - // Filter valid addresses. - let mut addrs = filter_valid_addrs( - sender, - request.addrs, - observed_addr, - self.server_config.as_ref().unwrap().only_public, - ); - addrs.truncate(config.max_addresses); + + let mut addrs = filter_valid_addrs(sender, request.addresses, observed_addr); + addrs.truncate(self.config.max_peer_addresses); if addrs.is_empty() { let status_text = "no dialable addresses".to_string(); @@ -486,7 +319,7 @@ impl Behaviour { status_text ); let response = DialResponse { - response: Err(ResponseError::DialError), + result: Err(ResponseError::DialError), status_text: Some(status_text), }; return Err(response); @@ -495,69 +328,42 @@ impl Behaviour { Ok(addrs) } - // Update the ongoing outbound probe according to the result of our dial-request. - // If the minimum confidence was reached it returns the nat status. - fn handle_response( - &mut self, - sender: PeerId, - request_id: RequestId, - response: Result, - ) -> Option { - let mut probe = self.ongoing_outbound.take()?; - - // Ignore the response if the peer or the request is not part of the ongoing probe. - // This could be the case if we received a late response for a previous probe that already resolved. - if !probe - .pending_servers - .iter() - .any(|(p, id)| *p == sender && id.unwrap() == request_id) - { - let _ = self.ongoing_outbound.insert(probe); - return None; - }; + // Adapt confidence and reachability to the result. + // Return new reachability if it changed or if it is the first resolved probe after boot. + fn resolve_probe(&mut self, result: Result) -> Option { + let resolved_reachability = Reachability::from(result); + self.schedule_probe = Some(Delay::new(self.config.retry_interval)); - probe.pending_servers.retain(|(p, _)| p != &sender); - match response { - Ok(DialResponse { - response: Ok(addr), .. - }) => { - log::debug!( - "Successful dial-back from peer {} to address {:?}", - sender, - addr - ); - let score = probe.addresses.entry(addr).or_insert(0); - *score += 1; - } - Ok(DialResponse { - response: Err(error), - status_text, - }) => { - log::debug!( - "Failed dial-back from peer {}: {:?} {:?}", - sender, - error, - status_text - ); - probe.errors.push((sender, error)); - } - Err(err) => { - probe.outbound_failures.push((sender, err)); + if resolved_reachability == self.reachability { + if !matches!(self.reachability, Reachability::Unknown) { + if self.confidence < self.config.confidence_max { + self.confidence += 1; + } + // Delay with (usually longer) refresh-interval if we reached max confidence. + if self.confidence >= self.config.confidence_max { + self.schedule_probe = Some(Delay::new(self.config.refresh_interval)); + } } - } - match probe.evaluate() { - Ok(status) => Some(status), - Err(probe) => { - let _ = self.ongoing_outbound.insert(probe); + if self.recent_probes.is_empty() { + // This is the first probe after boot therefore the status should be reported. + Some(resolved_reachability) + } else { None } + } else if self.confidence > 0 { + // Reduce confidence but keep old reachability status. + self.confidence -= 1; + None + } else { + self.reachability = resolved_reachability; + Some(self.reachability.clone()) } } } impl NetworkBehaviour for Behaviour { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; - type OutEvent = NatStatus; + type OutEvent = Reachability; fn inject_connection_established( &mut self, @@ -568,28 +374,51 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_established(peer, conn, endpoint, failed_addresses); + self.connected + .insert(*peer, endpoint.get_remote_address().clone()); - if let ConnectedPoint::Dialer { address } = endpoint { - if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { - // Check if the dialed address was among the requested addresses. - if addrs.contains(address) { - log::debug!( - "Dial-back to peer {} succeeded at addr {:?}.", - peer, - address - ); - // Successfully dialed one of the addresses from the remote peer. - let channel = self.ongoing_inbound.remove(peer).unwrap().1; - let response = DialResponse { - response: Ok(address.clone()), - status_text: None, - }; - let _ = self.inner.send_response(channel, response); + match endpoint { + ConnectedPoint::Dialer { address } => { + if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { + // Check if the dialed address was among the requested addresses. + if addrs.contains(address) { + log::debug!( + "Dial-back to peer {} succeeded at addr {:?}.", + peer, + address + ); + + let channel = self.ongoing_inbound.remove(peer).unwrap().1; + let response = DialResponse { + result: Ok(address.clone()), + status_text: None, + }; + let _ = self.inner.send_response(channel, response); + } + } + } + // A remote peer dialing us may mean that we are public, therefore the delay to the next probe should be adjusted. + ConnectedPoint::Listener { .. } => { + if self.confidence < self.config.confidence_max { + // Retry status already scheduled. + return; + } + if let Some(delay) = self.schedule_probe.as_mut() { + let last_probe_instant = self + .recent_probes + .last() + .expect("Confidence > 0 implies that there was already a probe.") + .1; + if self.reachability.public_addr().is_some() { + let schedule_next = last_probe_instant + self.config.refresh_interval * 2; + delay.reset(schedule_next - Instant::now()); + } else { + let schedule_next = last_probe_instant + self.config.refresh_interval / 5; + delay.reset(schedule_next - Instant::now()); + } } } } - self.connected - .insert(*peer, endpoint.get_remote_address().clone()); } fn inject_dial_failure( @@ -605,9 +434,9 @@ impl NetworkBehaviour for Behaviour { peer_id.unwrap(), error ); - // Failed to dial any of the addresses sent by the remote peer in their dial-request. + let response = DialResponse { - response: Err(ResponseError::DialError), + result: Err(ResponseError::DialError), status_text: Some("dial failed".to_string()), }; let _ = self.inner.send_response(channel, response); @@ -627,20 +456,54 @@ impl NetworkBehaviour for Behaviour { new: &ConnectedPoint, ) { self.inner.inject_address_change(peer, conn, old, new); - // Update observed address. + self.connected .insert(*peer, new.get_remote_address().clone()); } + fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { + self.inner.inject_new_listen_addr(id, addr); + if self.reachability.public_addr().is_none() { + self.confidence = 0; + if let Some(delay) = self.schedule_probe.as_mut() { + delay.reset(Duration::ZERO); + } + } + } + + fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { + self.inner.inject_expired_listen_addr(id, addr); + if let Some(public_address) = self.public_address() { + if public_address == addr { + self.confidence = 0; + self.reachability = Reachability::Unknown; + if let Some(delay) = self.schedule_probe.as_mut() { + delay.reset(Duration::ZERO); + } + } + } + } + fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - if self.reachability.is_public() || !self.pending_probes.is_empty() { - return; + if self.reachability.public_addr().is_none() { + self.confidence = 0; + if let Some(delay) = self.schedule_probe.as_mut() { + delay.reset(Duration::ZERO); + } } - if let Some((ref auto_probe, _)) = self.auto_probe { - let config = auto_probe.config.clone(); - let probe_id = self.next_probe_id(); - self.pending_probes.push_back((probe_id, config)); + } + + fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { + self.inner.inject_expired_external_addr(addr); + if let Some(public_address) = self.public_address() { + if public_address == addr { + self.confidence = 0; + self.reachability = Reachability::Unknown; + if let Some(delay) = self.schedule_probe.as_mut() { + delay.reset(Duration::ZERO); + } + } } } @@ -649,34 +512,27 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, params: &mut impl PollParameters, ) -> Poll> { - if let Some((ref auto_probe, ref mut delay)) = self.auto_probe { - if delay.poll_unpin(cx).is_ready() - && self.ongoing_outbound.is_none() - && self.pending_probes.is_empty() - { - let config = auto_probe.config.clone(); - let id = self.next_probe_id(); - self.pending_probes.push_back((id, config)); + let mut is_probe_due = false; + if let Some(delay) = self.schedule_probe.as_mut() { + if delay.poll_unpin(cx).is_ready() { + is_probe_due = true; } - }; + } loop { - if self.ongoing_outbound.is_none() { - if let Some((probe_id, config)) = self.pending_probes.pop_front() { - let mut external_addrs: Vec = params - .external_addresses() - .map(|record| record.addr) - .collect(); - if external_addrs.is_empty() { - external_addrs.extend(params.listened_addresses()); + if is_probe_due { + let mut addresses = match self.reachability.public_addr() { + Some(a) => vec![a.clone()], // Remote should try our assumed public address first. + None => Vec::new(), + }; + addresses.extend(params.external_addresses().map(|r| r.addr)); + addresses.extend(params.listened_addresses()); + + if self.do_probe(addresses).is_none() { + if let Some(flipped_reachability) = + self.resolve_probe(Err(ResponseError::InternalError)) + { + self.pending_out_event.push_back(flipped_reachability); } - log::debug!( - "Starting outbound probe {:?} with dial-back addresses {:?}.", - probe_id, - external_addrs - ); - let probe = - config.build(probe_id, external_addrs, self.connected.keys().collect()); - self.do_probe(probe); } } if let Some(event) = self.pending_out_event.pop_front() { @@ -708,12 +564,9 @@ impl NetworkBehaviour for Behaviour { } } } - RequestResponseMessage::Response { - request_id, - response, - } => { + RequestResponseMessage::Response { response, .. } => { let mut report_addr = None; - if let Ok(ref addr) = response.response { + if let Ok(ref addr) = response.result { // Update observed address score if it is finite. let score = params .external_addresses() @@ -726,9 +579,10 @@ impl NetworkBehaviour for Behaviour { }); } } - if let Some(status) = self.handle_response(peer, request_id, Ok(response)) { - self.inject_status(status); + if let Some(flipped_reachability) = self.resolve_probe(response.result) { + self.pending_out_event.push_back(flipped_reachability); } + self.recent_probes.push((peer, Instant::now())); if let Some(action) = report_addr { return Poll::Ready(action); } @@ -738,15 +592,11 @@ impl NetworkBehaviour for Behaviour { RequestResponseEvent::ResponseSent { .. }, )) => {} Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { - request_id, - peer, - error, - }, + RequestResponseEvent::OutboundFailure { peer, .. }, )) => { - if let Some(status) = self.handle_response(peer, request_id, Err(error)) { - self.inject_status(status); - } + // Retry with a different peer. + self.recent_probes.push((peer, Instant::now())); + is_probe_due = true; } Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::InboundFailure { peer, .. }, @@ -805,14 +655,6 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_new_listener(id) } - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_new_listen_addr(id, addr); - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.inner.inject_expired_listen_addr(id, addr); - } - fn inject_listener_error(&mut self, id: ListenerId, err: &(dyn std::error::Error + 'static)) { self.inner.inject_listener_error(id, err) } @@ -820,10 +662,6 @@ impl NetworkBehaviour for Behaviour { fn inject_listener_closed(&mut self, id: ListenerId, reason: Result<(), &std::io::Error>) { self.inner.inject_listener_closed(id, reason) } - - fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { - self.inner.inject_expired_external_addr(addr); - } } // Filter demanded dial addresses for validity, to prevent abuse. @@ -831,46 +669,14 @@ fn filter_valid_addrs( peer: PeerId, demanded: Vec, observed_remote_at: &Multiaddr, - only_public: bool, ) -> Vec { // Skip if the observed address is a relay address. if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) { return Vec::new(); } - let observed_ip = observed_remote_at.into_iter().find(|p| match p { - Protocol::Ip4(ip) => { - if !only_public { - return true; - } - // NOTE: The below logic is copied from `std::net::Ipv4Addr::is_global`, which at the current - // point is behind the unstable `ip` feature. - // See https://github.com/rust-lang/rust/issues/27709 for more info. - // The check for the unstable `Ipv4Addr::is_benchmarking` and `Ipv4Addr::is_reserved ` - // were skipped, because they should never occur in an observed address. - - // check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two - // globally routable addresses in the 192.0.0.0/24 range. - if u32::from_be_bytes(ip.octets()) == 0xc0000009 - || u32::from_be_bytes(ip.octets()) == 0xc000000a - { - return true; - } - - let is_shared = ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000); - - !ip.is_private() - && !ip.is_loopback() - && !ip.is_link_local() - && !ip.is_broadcast() - && !ip.is_documentation() - && !is_shared - } - Protocol::Ip6(_) => { - // TODO: filter addresses for global ones - true - } - _ => false, - }); + let observed_ip = observed_remote_at + .into_iter() + .find(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_))); let observed_ip = match observed_ip { Some(ip) => ip, None => return Vec::new(), @@ -954,7 +760,7 @@ mod test { demanded_3.clone(), demanded_4, ]; - let filtered = filter_valid_addrs(peer_id, demanded, &observed_addr, false); + let filtered = filter_valid_addrs(peer_id, demanded, &observed_addr); let expected_1 = demanded_1 .replace(0, |_| Some(observed_ip.clone())) .unwrap(); @@ -980,7 +786,7 @@ mod test { .with(random_ip()) .with(random_port()) .with(Protocol::P2p(peer_id.into())); - let filtered = filter_valid_addrs(peer_id, vec![demanded], &observed_addr, false); + let filtered = filter_valid_addrs(peer_id, vec![demanded], &observed_addr); assert!(filtered.is_empty()); } } diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 489061e7324..ddd50758314 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -23,7 +23,7 @@ mod behaviour; mod protocol; pub use self::{ - behaviour::{AutoProbe, Behaviour, Config, NatStatus, ProbeConfig, Reachability, ServerConfig}, + behaviour::{Behaviour, Config, Reachability}, protocol::ResponseError, }; diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index 9313bd92c66..4ee10ebf083 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -100,7 +100,7 @@ impl RequestResponseCodec for AutoNatCodec { #[derive(Clone, Debug, Eq, PartialEq)] pub struct DialRequest { pub peer_id: PeerId, - pub addrs: Vec, + pub addresses: Vec, } impl DialRequest { @@ -140,12 +140,19 @@ impl DialRequest { } maddrs }; - Ok(Self { peer_id, addrs }) + Ok(Self { + peer_id, + addresses: addrs, + }) } pub fn into_bytes(self) -> Vec { let peer_id = self.peer_id.to_bytes(); - let addrs = self.addrs.into_iter().map(|addr| addr.to_vec()).collect(); + let addrs = self + .addresses + .into_iter() + .map(|addr| addr.to_vec()) + .collect(); let msg = structs_proto::Message { r#type: Some(structs_proto::message::MessageType::Dial as _), @@ -207,7 +214,7 @@ impl TryFrom for ResponseError { #[derive(Clone, Debug, Eq, PartialEq)] pub struct DialResponse { pub status_text: Option, - pub response: Result, + pub result: Result, } impl DialResponse { @@ -228,7 +235,7 @@ impl DialResponse { .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; Self { status_text, - response: Ok(addr), + result: Ok(addr), } } Some(structs_proto::message::DialResponse { @@ -237,7 +244,7 @@ impl DialResponse { addr: None, }) => Self { status_text, - response: Err(ResponseError::try_from(status)?), + result: Err(ResponseError::try_from(status)?), }, _ => { log::debug!("Received malformed response message."); @@ -250,7 +257,7 @@ impl DialResponse { } pub fn into_bytes(self) -> Vec { - let dial_response = match self.response { + let dial_response = match self.result { Ok(addr) => structs_proto::message::DialResponse { status: Some(0), status_text: self.status_text, @@ -284,7 +291,7 @@ mod tests { fn test_request_encode_decode() { let request = DialRequest { peer_id: PeerId::random(), - addrs: vec![ + addresses: vec![ "/ip4/8.8.8.8/tcp/30333".parse().unwrap(), "/ip4/192.168.1.42/tcp/30333".parse().unwrap(), ], @@ -297,7 +304,7 @@ mod tests { #[test] fn test_response_ok_encode_decode() { let response = DialResponse { - response: Ok("/ip4/8.8.8.8/tcp/30333".parse().unwrap()), + result: Ok("/ip4/8.8.8.8/tcp/30333".parse().unwrap()), status_text: None, }; let bytes = response.clone().into_bytes(); @@ -308,7 +315,7 @@ mod tests { #[test] fn test_response_err_encode_decode() { let response = DialResponse { - response: Err(ResponseError::DialError), + result: Err(ResponseError::DialError), status_text: Some("dial failed".to_string()), }; let bytes = response.clone().into_bytes(); diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index d0cbf15ebc7..d65aa280900 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -18,6 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::time::Duration; + use futures::{channel::oneshot, FutureExt, StreamExt}; use libp2p::{ development_transport, @@ -25,21 +27,9 @@ use libp2p::{ swarm::{AddressScore, Swarm, SwarmEvent}, Multiaddr, PeerId, }; -use libp2p_autonat::{ - AutoProbe, Behaviour, Config, ProbeConfig, Reachability, ResponseError, ServerConfig, -}; -use std::time::Duration; +use libp2p_autonat::{Behaviour, Config, Reachability}; const SERVER_COUNT: usize = 5; -const RETRY_CONFIG: AutoProbe = AutoProbe { - interval: Duration::from_millis(100), - config: ProbeConfig { - max_peers: SERVER_COUNT, - min_confidence: 3, - servers: Vec::new(), - extend_with_connected: false, - }, -}; async fn init_swarm(config: Config) -> Swarm { let keypair = Keypair::generate_ed25519(); @@ -52,15 +42,7 @@ async fn init_swarm(config: Config) -> Swarm { async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { let (tx, rx) = oneshot::channel(); async_std::task::spawn(async move { - let mut swarm = init_swarm(Config { - auto_probe: None, - server: Some(ServerConfig { - only_public: false, - ..Default::default() - }), - ..Default::default() - }) - .await; + let mut swarm = init_swarm(Config::default()).await; let peer_id = *swarm.local_peer_id(); swarm .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) @@ -89,7 +71,8 @@ async fn test_public() { let mut handles = Vec::new(); let mut client = init_swarm(Config { - auto_probe: Some(RETRY_CONFIG), + retry_interval: Duration::from_millis(100), + boot_delay: Duration::ZERO, ..Default::default() }) .await; @@ -104,11 +87,11 @@ async fn test_public() { // Auto-Probe should directly resolve to `Unknown` as the local peer has no listening addresses loop { match client.select_next_some().await { - SwarmEvent::Behaviour(status) => { - assert!(status.errors.is_empty()); - assert!(status.outbound_failures.is_empty()); - assert_eq!(status.reachability, Reachability::Unknown); + SwarmEvent::Behaviour(flipped) => { + assert_eq!(flipped, Reachability::Unknown); assert_eq!(client.behaviour().reachability(), Reachability::Unknown); + assert!(client.behaviour().public_address().is_none()); + assert_eq!(client.behaviour().confidence(), 0); break; } _ => {} @@ -119,17 +102,18 @@ async fn test_public() { let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); - // Auto-Probe should resolve to private since no server can reach the client at this address. + // Auto-Probe should resolve to private since the server can not reach us. + // Confidence should increase with each iteration loop { match client.select_next_some().await { - SwarmEvent::Behaviour(status) => { - assert!(status.outbound_failures.is_empty()); - let errors: Vec = - status.errors.into_iter().map(|(_, e)| e).collect(); - let expect_error_count = SERVER_COUNT - RETRY_CONFIG.config.min_confidence + 1; - assert_eq!(errors, vec![ResponseError::DialError; expect_error_count]); - assert_eq!(status.reachability, Reachability::Private); - assert_eq!(client.behaviour().reachability(), Reachability::Private); + SwarmEvent::Behaviour(flipped) => { + assert_eq!(flipped, Reachability::Private); + assert!(matches!( + client.behaviour().reachability(), + Reachability::Private + )); + assert!(client.behaviour().public_address().is_none()); + assert_eq!(client.behaviour().confidence(), 0); break; } _ => {} @@ -147,34 +131,17 @@ async fn test_public() { } } - // Client should be reachable by all servers + // Client should be reachable by the servers loop { match client.select_next_some().await { - SwarmEvent::Behaviour(status) => { - println!("{:?}", status); - assert!(status.errors.is_empty()); - assert!(status.outbound_failures.is_empty()); - assert!(status.reachability.is_public()); - assert!(client.behaviour().reachability().is_public()); - break; - } - _ => {} - } - } - - // Drop enough severs so that min confidence can not be reached anymore. - handles.truncate(RETRY_CONFIG.config.min_confidence - 1); - - loop { - match client.select_next_some().await { - SwarmEvent::Behaviour(status) => { - assert!(status.errors.is_empty()); - assert_eq!( - status.outbound_failures.len(), - SERVER_COUNT - RETRY_CONFIG.config.min_confidence + 1 - ); - assert_eq!(status.reachability, Reachability::Unknown); - assert_eq!(client.behaviour().reachability(), Reachability::Unknown); + SwarmEvent::Behaviour(flipped) => { + assert!(matches!(flipped, Reachability::Public(_))); + assert!(matches!( + client.behaviour().reachability(), + Reachability::Public(..) + )); + assert_eq!(client.behaviour().confidence(), 0); + assert!(client.behaviour().public_address().is_some()); break; } _ => {} From b5672686050ff61d2dbee8124a361e5cb8f6fefc Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 5 Dec 2021 23:46:41 +0100 Subject: [PATCH 39/69] protocols/autonat/behaviour: fix intra-doc link --- protocols/autonat/src/behaviour.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 6d959c181da..5ac26e198d2 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -47,7 +47,7 @@ use std::{ #[derive(Debug, Clone, PartialEq, Eq)] pub enum SelectServer { /// Prioritize the usage of static servers. - /// Fallback on connected peers if there is no server, or the servers are throttled through [`Config::throttlePeerPeriod`]. + /// Fallback on connected peers if there is no server, or the servers are throttled through [`Config::throttle_peer_period`]. PrioritizeStatic, /// Only use static servers. /// Resolve to status unknown if there are none. From f1f85497c81429359243c9b7b10000ed503416d5 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 6 Dec 2021 01:29:09 +0100 Subject: [PATCH 40/69] protocols/autonat/behaviour: clean code, improve docs --- protocols/autonat/src/behaviour.rs | 48 +++++++++++++++++------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 5ac26e198d2..33705ab1a3f 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -65,15 +65,21 @@ pub struct Config { // == Client Config /// Delay on init before starting the fist probe pub boot_delay: Duration, - /// Interval in which the NAT should be tested again if + /// Interval in which the NAT should be tested again if reached max confidence. pub refresh_interval: Duration, - /// Interval in which the NAT should be re-tried if the current status is unknown. + /// Interval in which the NAT should be re-tried if the current status is unknown + /// or max confidence was not reached yet. pub retry_interval: Duration, + /// Throttle period for re-using a peer as server for a dial-request. pub throttle_peer_period: Duration, - + /// Policy for selecting the server for a dial-request pub select_server: SelectServer, - + /// Max confidence that can be reached in a public / private reachability. + /// The confidence is increased each time a probe confirms the assumed reachability, and + /// reduced each time a different reachability is reported. On confidence 0 the reachability + /// is flipped if a different status is reported. + /// Note: for [`Reachability::Unknown`] the confidence is always 0. pub confidence_max: usize, //== Server Config @@ -99,7 +105,7 @@ impl Default for Config { } } -/// Current reachability derived from the most recent probe. +/// Assumed reachability. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Reachability { Public(Multiaddr), @@ -108,11 +114,8 @@ pub enum Reachability { } impl Reachability { - pub fn public_addr(&self) -> Option<&Multiaddr> { - match self { - Reachability::Public(address) => Some(address), - _ => None, - } + pub fn is_public(&self) -> bool { + matches!(self, Reachability::Public(..)) } } @@ -183,7 +186,10 @@ impl Behaviour { /// Address if we are public. pub fn public_address(&self) -> Option<&Multiaddr> { - self.reachability.public_addr() + match &self.reachability { + Reachability::Public(address) => Some(address), + _ => None, + } } /// Currently assumed reachability. @@ -397,7 +403,7 @@ impl NetworkBehaviour for Behaviour { } } } - // A remote peer dialing us may mean that we are public, therefore the delay to the next probe should be adjusted. + // An inbound connection can indicate that we are public; adjust the delay to the next probe. ConnectedPoint::Listener { .. } => { if self.confidence < self.config.confidence_max { // Retry status already scheduled. @@ -409,7 +415,7 @@ impl NetworkBehaviour for Behaviour { .last() .expect("Confidence > 0 implies that there was already a probe.") .1; - if self.reachability.public_addr().is_some() { + if self.reachability.is_public() { let schedule_next = last_probe_instant + self.config.refresh_interval * 2; delay.reset(schedule_next - Instant::now()); } else { @@ -463,7 +469,7 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - if self.reachability.public_addr().is_none() { + if !self.reachability.is_public() { self.confidence = 0; if let Some(delay) = self.schedule_probe.as_mut() { delay.reset(Duration::ZERO); @@ -486,7 +492,7 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - if self.reachability.public_addr().is_none() { + if !self.reachability.is_public() { self.confidence = 0; if let Some(delay) = self.schedule_probe.as_mut() { delay.reset(Duration::ZERO); @@ -520,7 +526,7 @@ impl NetworkBehaviour for Behaviour { } loop { if is_probe_due { - let mut addresses = match self.reachability.public_addr() { + let mut addresses = match self.public_address() { Some(a) => vec![a.clone()], // Remote should try our assumed public address first. None => Vec::new(), }; @@ -664,7 +670,7 @@ impl NetworkBehaviour for Behaviour { } } -// Filter demanded dial addresses for validity, to prevent abuse. +// Filter dial addresses and replace demanded ip with the observed one. fn filter_valid_addrs( peer: PeerId, demanded: Vec, @@ -674,10 +680,10 @@ fn filter_valid_addrs( if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) { return Vec::new(); } - let observed_ip = observed_remote_at + let observed_ip = match observed_remote_at .into_iter() - .find(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_))); - let observed_ip = match observed_ip { + .find(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_))) + { Some(ip) => ip, None => return Vec::new(), }; @@ -690,7 +696,7 @@ fn filter_valid_addrs( .iter() .position(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))?; let mut addr = addr.replace(i, |_| Some(observed_ip.clone()))?; - // Filter relay addresses and addresses with invalid peer id. + let is_valid = addr.iter().all(|proto| match proto { Protocol::P2pCircuit => false, Protocol::P2p(hash) => hash == peer.into(), From a3724b70b66e13267db8b0850eb2ae2db230effd Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 6 Dec 2021 03:07:16 +0100 Subject: [PATCH 41/69] protocols/autonat/behaviour: enable simultaneous probes --- protocols/autonat/examples/client.rs | 10 +++- protocols/autonat/src/behaviour.rs | 87 ++++++++++++++-------------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index 3c40f11b357..982551620cf 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -23,7 +23,7 @@ use libp2p::autonat; use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; use libp2p::swarm::{Swarm, SwarmEvent}; use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId}; -use std::error::Error; +use std::{error::Error, time::Duration}; use structopt::StructOpt; #[derive(Debug, StructOpt)] @@ -83,7 +83,13 @@ impl Behaviour { )), auto_nat: autonat::Behaviour::new( local_public_key.to_peer_id(), - autonat::Config::default(), + autonat::Config { + retry_interval: Duration::from_secs(10), + refresh_interval: Duration::from_secs(30), + boot_delay: Duration::ZERO, + throttle_peer_period: Duration::from_secs(5), + ..Default::default() + }, ), } } diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 33705ab1a3f..deaa8f1e3f7 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -148,8 +148,8 @@ pub struct Behaviour { // Confidence in the assumed reachability. confidence: usize, - // Delay until next probe. `None` if there is an ongoing probe. - schedule_probe: Option, + // Delay until next probe. + schedule_probe: Delay, // Ongoing inbound requests, where no response has been sent back to the remote yet. ongoing_inbound: HashMap, ResponseChannel)>, @@ -160,6 +160,8 @@ pub struct Behaviour { // Used servers in recent outbound probes. recent_probes: Vec<(PeerId, Instant)>, + last_probe: Option, + pending_out_event: VecDeque<::OutEvent>, } @@ -172,7 +174,7 @@ impl Behaviour { Self { local_peer_id, inner, - schedule_probe: Some(Delay::new(config.boot_delay)), + schedule_probe: Delay::new(config.boot_delay), config, static_servers: Vec::new(), ongoing_inbound: HashMap::default(), @@ -180,6 +182,7 @@ impl Behaviour { reachability: Reachability::Unknown, confidence: 0, recent_probes: Vec::new(), + last_probe: None, pending_out_event: VecDeque::new(), } } @@ -217,6 +220,7 @@ impl Behaviour { // Return `None` if there are no qualified servers or no addresses. fn do_probe(&mut self, addresses: Vec) -> Option { if addresses.is_empty() { + log::debug!("Outbound dial-back request aborted: No server."); return None; } self.recent_probes @@ -245,6 +249,7 @@ impl Behaviour { servers.retain(|s| !throttled.contains(&s)); if servers.is_empty() { + log::debug!("Outbound dial-back request aborted: No server."); return None; } let server = servers @@ -257,7 +262,9 @@ impl Behaviour { addresses, }, ); - self.schedule_probe = None; + self.recent_probes.push((*server, Instant::now())); + self.last_probe = Some(Instant::now()); + log::debug!("Send dial-back request to peer {}.", server); Some(request_id) } @@ -334,11 +341,10 @@ impl Behaviour { Ok(addrs) } - // Adapt confidence and reachability to the result. - // Return new reachability if it changed or if it is the first resolved probe after boot. + // Adapt confidence and reachability to the result. Return new reachability if it changed. fn resolve_probe(&mut self, result: Result) -> Option { let resolved_reachability = Reachability::from(result); - self.schedule_probe = Some(Delay::new(self.config.retry_interval)); + self.schedule_probe.reset(self.config.retry_interval); if resolved_reachability == self.reachability { if !matches!(self.reachability, Reachability::Unknown) { @@ -347,20 +353,25 @@ impl Behaviour { } // Delay with (usually longer) refresh-interval if we reached max confidence. if self.confidence >= self.config.confidence_max { - self.schedule_probe = Some(Delay::new(self.config.refresh_interval)); + self.schedule_probe = Delay::new(self.config.refresh_interval); } } - if self.recent_probes.is_empty() { + if self.last_probe.is_some() { + None + } else { // This is the first probe after boot therefore the status should be reported. Some(resolved_reachability) - } else { - None } } else if self.confidence > 0 { // Reduce confidence but keep old reachability status. self.confidence -= 1; None } else { + log::debug!( + "Flipped reachability from {:?} to {:?}", + self.reachability, + resolved_reachability + ); self.reachability = resolved_reachability; Some(self.reachability.clone()) } @@ -409,19 +420,15 @@ impl NetworkBehaviour for Behaviour { // Retry status already scheduled. return; } - if let Some(delay) = self.schedule_probe.as_mut() { - let last_probe_instant = self - .recent_probes - .last() - .expect("Confidence > 0 implies that there was already a probe.") - .1; - if self.reachability.is_public() { - let schedule_next = last_probe_instant + self.config.refresh_interval * 2; - delay.reset(schedule_next - Instant::now()); - } else { - let schedule_next = last_probe_instant + self.config.refresh_interval / 5; - delay.reset(schedule_next - Instant::now()); - } + let last_probe_instant = self + .last_probe + .expect("Confidence > 0 implies that there was already a probe."); + if self.reachability.is_public() { + let schedule_next = last_probe_instant + self.config.refresh_interval * 2; + self.schedule_probe.reset(schedule_next - Instant::now()); + } else { + let schedule_next = last_probe_instant + self.config.refresh_interval / 5; + self.schedule_probe.reset(schedule_next - Instant::now()); } } } @@ -471,9 +478,7 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_new_listen_addr(id, addr); if !self.reachability.is_public() { self.confidence = 0; - if let Some(delay) = self.schedule_probe.as_mut() { - delay.reset(Duration::ZERO); - } + self.schedule_probe.reset(Duration::ZERO); } } @@ -483,9 +488,7 @@ impl NetworkBehaviour for Behaviour { if public_address == addr { self.confidence = 0; self.reachability = Reachability::Unknown; - if let Some(delay) = self.schedule_probe.as_mut() { - delay.reset(Duration::ZERO); - } + self.schedule_probe.reset(Duration::ZERO); } } } @@ -494,9 +497,7 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_new_external_addr(addr); if !self.reachability.is_public() { self.confidence = 0; - if let Some(delay) = self.schedule_probe.as_mut() { - delay.reset(Duration::ZERO); - } + self.schedule_probe.reset(Duration::ZERO); } } @@ -506,9 +507,7 @@ impl NetworkBehaviour for Behaviour { if public_address == addr { self.confidence = 0; self.reachability = Reachability::Unknown; - if let Some(delay) = self.schedule_probe.as_mut() { - delay.reset(Duration::ZERO); - } + self.schedule_probe.reset(Duration::ZERO); } } } @@ -519,10 +518,9 @@ impl NetworkBehaviour for Behaviour { params: &mut impl PollParameters, ) -> Poll> { let mut is_probe_due = false; - if let Some(delay) = self.schedule_probe.as_mut() { - if delay.poll_unpin(cx).is_ready() { - is_probe_due = true; - } + if self.schedule_probe.poll_unpin(cx).is_ready() { + is_probe_due = true; + self.schedule_probe.reset(self.config.refresh_interval); } loop { if is_probe_due { @@ -540,6 +538,7 @@ impl NetworkBehaviour for Behaviour { self.pending_out_event.push_back(flipped_reachability); } } + is_probe_due = false; } if let Some(event) = self.pending_out_event.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); @@ -571,6 +570,8 @@ impl NetworkBehaviour for Behaviour { } } RequestResponseMessage::Response { response, .. } => { + log::debug!("Outbound dial-back request returned {:?}.", response); + let mut report_addr = None; if let Ok(ref addr) = response.result { // Update observed address score if it is finite. @@ -585,10 +586,10 @@ impl NetworkBehaviour for Behaviour { }); } } + if let Some(flipped_reachability) = self.resolve_probe(response.result) { self.pending_out_event.push_back(flipped_reachability); } - self.recent_probes.push((peer, Instant::now())); if let Some(action) = report_addr { return Poll::Ready(action); } @@ -598,10 +599,8 @@ impl NetworkBehaviour for Behaviour { RequestResponseEvent::ResponseSent { .. }, )) => {} Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { peer, .. }, + RequestResponseEvent::OutboundFailure { .. }, )) => { - // Retry with a different peer. - self.recent_probes.push((peer, Instant::now())); is_probe_due = true; } Poll::Ready(NetworkBehaviourAction::GenerateEvent( From 2775041fa8e219672c5362eec89fb1397e2dc6b3 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 9 Dec 2021 01:48:46 +0100 Subject: [PATCH 42/69] protocols/autonat: Enable manual trigger of probes Rename Reachability to NatStatus. Provide a method for manually triggering a probe, optionally with a fixed server. Add ProbeId for probes and report it together with the Nat status in the AutoNAT Behaviour Event so that it can be linked to the triggered probe. The status of manually triggered probes is always reported as Behaviour Event (otherwise it is only reported if the status flipped). --- protocols/autonat/examples/client.rs | 10 +- protocols/autonat/src/behaviour.rs | 301 +++++++++++++++------------ protocols/autonat/src/lib.rs | 2 +- protocols/autonat/tests/autonat.rs | 30 +-- 4 files changed, 194 insertions(+), 149 deletions(-) diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index 982551620cf..e6591004999 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -86,8 +86,8 @@ impl Behaviour { autonat::Config { retry_interval: Duration::from_secs(10), refresh_interval: Duration::from_secs(30), - boot_delay: Duration::ZERO, - throttle_peer_period: Duration::from_secs(5), + boot_delay: Duration::from_secs(5), + throttle_peer_period: Duration::ZERO, ..Default::default() }, ), @@ -97,7 +97,7 @@ impl Behaviour { #[derive(Debug)] enum Event { - AutoNat(autonat::Reachability), + AutoNat(autonat::Event), Identify(IdentifyEvent), } @@ -107,8 +107,8 @@ impl From for Event { } } -impl From for Event { - fn from(v: autonat::Reachability) -> Self { +impl From for Event { + fn from(v: autonat::Event) -> Self { Self::AutoNat(v) } } diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index deaa8f1e3f7..855c7ee22b4 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -43,19 +43,6 @@ use std::{ time::Duration, }; -/// Configure whether probes should preferably select statically added servers for the next probe. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SelectServer { - /// Prioritize the usage of static servers. - /// Fallback on connected peers if there is no server, or the servers are throttled through [`Config::throttle_peer_period`]. - PrioritizeStatic, - /// Only use static servers. - /// Resolve to status unknown if there are none. - ExclusivelyStatic, - /// Randomly select a target from the list of static servers and connected peers. - Random, -} - /// Config for the [`Behaviour`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { @@ -67,19 +54,19 @@ pub struct Config { pub boot_delay: Duration, /// Interval in which the NAT should be tested again if reached max confidence. pub refresh_interval: Duration, - /// Interval in which the NAT should be re-tried if the current status is unknown + /// Interval in which the NAT status should be re-tried if it is currently is unknown /// or max confidence was not reached yet. pub retry_interval: Duration, /// Throttle period for re-using a peer as server for a dial-request. pub throttle_peer_period: Duration, - /// Policy for selecting the server for a dial-request - pub select_server: SelectServer, - /// Max confidence that can be reached in a public / private reachability. - /// The confidence is increased each time a probe confirms the assumed reachability, and - /// reduced each time a different reachability is reported. On confidence 0 the reachability - /// is flipped if a different status is reported. - /// Note: for [`Reachability::Unknown`] the confidence is always 0. + /// Wether connected peers may be used as server for a probe, in addition to the predefined ones. + pub may_use_connected: bool, + /// Max confidence that can be reached in a public / private NAT status. + /// The confidence is increased each time a probe confirms the assumed status, and + /// reduced each time a different status is reported. On confidence 0 the status + /// is flipped if a different one is reported. + /// Note: for [`NatStatus::Unknown`] the confidence is always 0. pub confidence_max: usize, //== Server Config @@ -97,7 +84,7 @@ impl Default for Config { retry_interval: Duration::from_secs(90), refresh_interval: Duration::from_secs(15 * 60), throttle_peer_period: Duration::from_secs(90), - select_server: SelectServer::Random, + may_use_connected: true, confidence_max: 3, max_peer_addresses: 16, throttle_global_max: 30, @@ -105,47 +92,64 @@ impl Default for Config { } } -/// Assumed reachability. +/// Assumed NAT status. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Reachability { +pub enum NatStatus { Public(Multiaddr), Private, Unknown, } -impl Reachability { +impl NatStatus { pub fn is_public(&self) -> bool { - matches!(self, Reachability::Public(..)) + matches!(self, NatStatus::Public(..)) } } -impl From> for Reachability { +impl From> for NatStatus { fn from(result: Result) -> Self { match result { - Ok(addr) => Reachability::Public(addr), - Err(ResponseError::DialError) => Reachability::Private, - _ => Reachability::Unknown, + Ok(addr) => NatStatus::Public(addr), + Err(ResponseError::DialError) => NatStatus::Private, + _ => NatStatus::Unknown, } } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ProbeId(usize); + +impl ProbeId { + fn next(&mut self) -> ProbeId { + let current = *self; + self.0 += 1; + current + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Event { + pub probe_id: ProbeId, + pub nat_status: NatStatus, +} + /// Network Behaviour for AutoNAT. pub struct Behaviour { // Local peer id local_peer_id: PeerId, - // Inner protocol for sending requests and receiving the response. + // Inner behaviour for sending requests and receiving the response. inner: RequestResponse, config: Config, - // Peers that may not be connected but still can be used as server in a probe. - static_servers: Vec, + // Additional peers apart from the currently connected ones, that may be used for probes. + servers: Vec, - // Assumed reachability derived from the most recent probe. - reachability: Reachability, + // Assumed NAT status. + nat_status: NatStatus, - // Confidence in the assumed reachability. + // Confidence in the assumed NAT status. confidence: usize, // Delay until next probe. @@ -154,15 +158,26 @@ pub struct Behaviour { // Ongoing inbound requests, where no response has been sent back to the remote yet. ongoing_inbound: HashMap, ResponseChannel)>, + // Ongoing outbound requests, where no response has been received from the remote yet. + // Map the ID of the inner request to the probe's ID and specify whether the probe was manually triggered. + ongoing_outbound: HashMap, + // Connected peers with their observed address. + // These peers may be used as servers for dial-requests. connected: HashMap, - // Used servers in recent outbound probes. + // Used servers in recent outbound probes that are throttled through Config::throttle_peer_period. recent_probes: Vec<(PeerId, Instant)>, last_probe: Option, - pending_out_event: VecDeque<::OutEvent>, + probe_id: ProbeId, + + // Pending probes for which a dial-request should be send to a server. + // In case of a manually triggered probe the server is set. + pending_probes: VecDeque<(ProbeId, Option)>, + + pending_out_events: VecDeque<::OutEvent>, } impl Behaviour { @@ -176,100 +191,113 @@ impl Behaviour { inner, schedule_probe: Delay::new(config.boot_delay), config, - static_servers: Vec::new(), + servers: Vec::new(), ongoing_inbound: HashMap::default(), + ongoing_outbound: HashMap::default(), connected: HashMap::default(), - reachability: Reachability::Unknown, + nat_status: NatStatus::Unknown, confidence: 0, recent_probes: Vec::new(), last_probe: None, - pending_out_event: VecDeque::new(), + pending_out_events: VecDeque::new(), + pending_probes: VecDeque::new(), + probe_id: ProbeId(0), } } /// Address if we are public. pub fn public_address(&self) -> Option<&Multiaddr> { - match &self.reachability { - Reachability::Public(address) => Some(address), + match &self.nat_status { + NatStatus::Public(address) => Some(address), _ => None, } } - /// Currently assumed reachability. - pub fn reachability(&self) -> Reachability { - self.reachability.clone() + /// Currently assumed NAT status. + pub fn nat_status(&self) -> NatStatus { + self.nat_status.clone() } - /// Confidence in out current reachability status. + /// Confidence in the assumed NAT status. pub fn confidence(&self) -> usize { self.confidence } + /// Add a peer to the list over servers that may be used for probes. + /// While probes normally use one of the connected peers as server, this allows to add trusted + /// peers that can be used even if they are currently not connected, in which case a connection will be + /// establish before sending the dial-request. pub fn add_server(&mut self, peer: PeerId, address: Option) { - self.static_servers.push(peer); + self.servers.push(peer); if let Some(addr) = address { self.inner.add_address(&peer, addr); } } + /// Remove a peer from the list of servers. + /// See [`Behaviour::add_server`] for more info. pub fn remove_server(&mut self, peer: &PeerId) { - self.static_servers.retain(|p| p != peer); + self.servers.retain(|p| p != peer); } - // Send a dial request to a randomly selected server. - // Return `None` if there are no qualified servers or no addresses. - fn do_probe(&mut self, addresses: Vec) -> Option { - if addresses.is_empty() { - log::debug!("Outbound dial-back request aborted: No server."); - return None; - } + /// Manually trigger a probe. Optionally a the server can be set that is used, otherwise + /// it will select a server based on [`Config::may_use_connected`]. In case of the latter, + /// return `None` if no qualified server is present. + pub fn trigger_probe(&mut self, server: Option) -> Option { + let server_id = server.or_else(|| self.random_server())?; + let probe_id = self.probe_id.next(); + self.pending_probes.push_back((probe_id, Some(server_id))); + Some(probe_id) + } + + // Select a random server for the probe based on [`Config::may_use_connected`]. + fn random_server(&mut self) -> Option { self.recent_probes .retain(|(_, time)| *time + self.config.throttle_peer_period > Instant::now()); let throttled: Vec<_> = self.recent_probes.iter().map(|(id, _)| id).collect(); - let mut servers = match self.config.select_server { - SelectServer::ExclusivelyStatic => self.static_servers.clone(), - SelectServer::PrioritizeStatic => { - let mut servers = Vec::new(); - for server in &self.static_servers { - if !throttled.contains(&server) { - servers.push(*server) - } - } - if servers.is_empty() { - servers.extend(self.connected.iter().map(|(id, _)| *id)); - } - servers - } - SelectServer::Random => { - let mut connected: Vec<_> = self.connected.iter().map(|(id, _)| *id).collect(); - connected.extend(&self.static_servers); - connected - } - }; + let mut servers: Vec<&PeerId> = self.servers.iter().collect(); + + if self.config.may_use_connected { + servers.extend(self.connected.iter().map(|(id, _)| id)); + } - servers.retain(|s| !throttled.contains(&s)); + servers.retain(|s| !throttled.contains(s)); if servers.is_empty() { - log::debug!("Outbound dial-back request aborted: No server."); return None; } - let server = servers - .get(rand::random::() % servers.len()) - .expect("Element is present."); + let server = servers[rand::random::() % servers.len()]; + Some(*server) + } + + // Send a dial request to the set server or a randomly selected one. + // Return `None` if there are no qualified servers or no dial-back addresses. + fn do_probe(&mut self, server: Option, addresses: Vec) -> Option { + if addresses.is_empty() { + log::debug!("Outbound dial-back request aborted: No dial-back addresses."); + return None; + } + let server = match server.or_else(|| self.random_server()) { + Some(s) => s, + None => { + log::debug!("Outbound dial-back request aborted: No qualified server."); + return None; + } + }; let request_id = self.inner.send_request( - server, + &server, DialRequest { peer_id: self.local_peer_id, addresses, }, ); - self.recent_probes.push((*server, Instant::now())); + self.recent_probes.push((server, Instant::now())); self.last_probe = Some(Instant::now()); log::debug!("Send dial-back request to peer {}.", server); Some(request_id) } - // Handle the inbound request and collect the valid addresses to be dialed. - fn handle_request( + // Validate the inbound request and collect the addresses to be dialed. + fn resolve_inbound_request( &mut self, sender: PeerId, request: DialRequest, @@ -341,46 +369,41 @@ impl Behaviour { Ok(addrs) } - // Adapt confidence and reachability to the result. Return new reachability if it changed. - fn resolve_probe(&mut self, result: Result) -> Option { - let resolved_reachability = Reachability::from(result); + // Adapt current confidence and NAT status to the status reported by the latest probe. + // Return whether the currently assumed status was flipped. + fn handle_reported_status(&mut self, reported: &NatStatus) -> bool { self.schedule_probe.reset(self.config.retry_interval); - if resolved_reachability == self.reachability { - if !matches!(self.reachability, Reachability::Unknown) { + if reported == &self.nat_status { + if !matches!(self.nat_status, NatStatus::Unknown) { if self.confidence < self.config.confidence_max { self.confidence += 1; } // Delay with (usually longer) refresh-interval if we reached max confidence. if self.confidence >= self.config.confidence_max { - self.schedule_probe = Delay::new(self.config.refresh_interval); + self.schedule_probe.reset(self.config.refresh_interval); } } - if self.last_probe.is_some() { - None - } else { - // This is the first probe after boot therefore the status should be reported. - Some(resolved_reachability) - } + false } else if self.confidence > 0 { - // Reduce confidence but keep old reachability status. + // Reduce confidence but keep old status. self.confidence -= 1; - None + false } else { log::debug!( - "Flipped reachability from {:?} to {:?}", - self.reachability, - resolved_reachability + "Flipped assumed NAT status from {:?} to {:?}", + self.nat_status, + reported ); - self.reachability = resolved_reachability; - Some(self.reachability.clone()) + self.nat_status = reported.clone(); + true } } } impl NetworkBehaviour for Behaviour { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; - type OutEvent = Reachability; + type OutEvent = Event; fn inject_connection_established( &mut self, @@ -417,13 +440,13 @@ impl NetworkBehaviour for Behaviour { // An inbound connection can indicate that we are public; adjust the delay to the next probe. ConnectedPoint::Listener { .. } => { if self.confidence < self.config.confidence_max { - // Retry status already scheduled. + // Retry already scheduled. return; } let last_probe_instant = self .last_probe .expect("Confidence > 0 implies that there was already a probe."); - if self.reachability.is_public() { + if self.nat_status.is_public() { let schedule_next = last_probe_instant + self.config.refresh_interval * 2; self.schedule_probe.reset(schedule_next - Instant::now()); } else { @@ -476,7 +499,7 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - if !self.reachability.is_public() { + if !self.nat_status.is_public() { self.confidence = 0; self.schedule_probe.reset(Duration::ZERO); } @@ -487,7 +510,7 @@ impl NetworkBehaviour for Behaviour { if let Some(public_address) = self.public_address() { if public_address == addr { self.confidence = 0; - self.reachability = Reachability::Unknown; + self.nat_status = NatStatus::Unknown; self.schedule_probe.reset(Duration::ZERO); } } @@ -495,7 +518,7 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - if !self.reachability.is_public() { + if !self.nat_status.is_public() { self.confidence = 0; self.schedule_probe.reset(Duration::ZERO); } @@ -506,7 +529,7 @@ impl NetworkBehaviour for Behaviour { if let Some(public_address) = self.public_address() { if public_address == addr { self.confidence = 0; - self.reachability = Reachability::Unknown; + self.nat_status = NatStatus::Unknown; self.schedule_probe.reset(Duration::ZERO); } } @@ -517,13 +540,12 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, params: &mut impl PollParameters, ) -> Poll> { - let mut is_probe_due = false; - if self.schedule_probe.poll_unpin(cx).is_ready() { - is_probe_due = true; + if self.pending_probes.is_empty() && self.schedule_probe.poll_unpin(cx).is_ready() { + self.pending_probes.push_back((self.probe_id.next(), None)); self.schedule_probe.reset(self.config.refresh_interval); } loop { - if is_probe_due { + while let Some((probe_id, server)) = self.pending_probes.pop_front() { let mut addresses = match self.public_address() { Some(a) => vec![a.clone()], // Remote should try our assumed public address first. None => Vec::new(), @@ -531,16 +553,26 @@ impl NetworkBehaviour for Behaviour { addresses.extend(params.external_addresses().map(|r| r.addr)); addresses.extend(params.listened_addresses()); - if self.do_probe(addresses).is_none() { - if let Some(flipped_reachability) = - self.resolve_probe(Err(ResponseError::InternalError)) - { - self.pending_out_event.push_back(flipped_reachability); + let is_manually_triggered = server.is_some(); + let is_first = self.last_probe.is_none(); + match self.do_probe(server, addresses) { + Some(request_id) => { + self.ongoing_outbound + .insert(request_id, (probe_id, is_manually_triggered)); + } + None => { + let nat_status = NatStatus::Unknown; + let has_flipped = self.handle_reported_status(&nat_status); + if has_flipped || is_first || is_manually_triggered { + self.pending_out_events.push_back(Event { + nat_status, + probe_id, + }); + } } } - is_probe_due = false; } - if let Some(event) = self.pending_out_event.pop_front() { + if let Some(event) = self.pending_out_events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } match self.inner.poll(cx, params) { @@ -552,7 +584,7 @@ impl NetworkBehaviour for Behaviour { request, channel, } => { - match self.handle_request(peer, request) { + match self.resolve_inbound_request(peer, request) { Ok(addrs) => { log::debug!("Inbound dial request from Peer {} with dial-back addresses {:?}.", peer, addrs); self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); @@ -569,9 +601,16 @@ impl NetworkBehaviour for Behaviour { } } } - RequestResponseMessage::Response { response, .. } => { + RequestResponseMessage::Response { + response, + request_id, + } => { log::debug!("Outbound dial-back request returned {:?}.", response); + let (probe_id, is_manually_triggered) = self + .ongoing_outbound + .remove(&request_id) + .expect("Request ID exists."); let mut report_addr = None; if let Ok(ref addr) = response.result { // Update observed address score if it is finite. @@ -586,9 +625,13 @@ impl NetworkBehaviour for Behaviour { }); } } - - if let Some(flipped_reachability) = self.resolve_probe(response.result) { - self.pending_out_event.push_back(flipped_reachability); + let new = response.result.into(); + let has_flipped = self.handle_reported_status(&new); + if self.last_probe.is_none() || has_flipped || is_manually_triggered { + self.pending_out_events.push_back(Event { + probe_id, + nat_status: new, + }); } if let Some(action) = report_addr { return Poll::Ready(action); @@ -600,9 +643,7 @@ impl NetworkBehaviour for Behaviour { )) => {} Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::OutboundFailure { .. }, - )) => { - is_probe_due = true; - } + )) => self.pending_probes.push_back((self.probe_id.next(), None)), Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::InboundFailure { peer, .. }, )) => { diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index ddd50758314..f1e38bcf64f 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -23,7 +23,7 @@ mod behaviour; mod protocol; pub use self::{ - behaviour::{Behaviour, Config, Reachability}, + behaviour::{Behaviour, Config, Event, NatStatus, ProbeId}, protocol::ResponseError, }; diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index d65aa280900..85e4ebe8d62 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -27,7 +27,7 @@ use libp2p::{ swarm::{AddressScore, Swarm, SwarmEvent}, Multiaddr, PeerId, }; -use libp2p_autonat::{Behaviour, Config, Reachability}; +use libp2p_autonat::{Behaviour, Config, Event, NatStatus}; const SERVER_COUNT: usize = 5; @@ -42,7 +42,11 @@ async fn init_swarm(config: Config) -> Swarm { async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { let (tx, rx) = oneshot::channel(); async_std::task::spawn(async move { - let mut swarm = init_swarm(Config::default()).await; + let mut swarm = init_swarm(Config { + boot_delay: Duration::from_secs(60), + ..Default::default() + }) + .await; let peer_id = *swarm.local_peer_id(); swarm .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) @@ -87,9 +91,9 @@ async fn test_public() { // Auto-Probe should directly resolve to `Unknown` as the local peer has no listening addresses loop { match client.select_next_some().await { - SwarmEvent::Behaviour(flipped) => { - assert_eq!(flipped, Reachability::Unknown); - assert_eq!(client.behaviour().reachability(), Reachability::Unknown); + SwarmEvent::Behaviour(Event { nat_status, .. }) => { + assert_eq!(nat_status, NatStatus::Unknown); + assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); assert!(client.behaviour().public_address().is_none()); assert_eq!(client.behaviour().confidence(), 0); break; @@ -106,11 +110,11 @@ async fn test_public() { // Confidence should increase with each iteration loop { match client.select_next_some().await { - SwarmEvent::Behaviour(flipped) => { - assert_eq!(flipped, Reachability::Private); + SwarmEvent::Behaviour(Event { nat_status, .. }) => { + assert_eq!(nat_status, NatStatus::Private); assert!(matches!( - client.behaviour().reachability(), - Reachability::Private + client.behaviour().nat_status(), + NatStatus::Private )); assert!(client.behaviour().public_address().is_none()); assert_eq!(client.behaviour().confidence(), 0); @@ -134,11 +138,11 @@ async fn test_public() { // Client should be reachable by the servers loop { match client.select_next_some().await { - SwarmEvent::Behaviour(flipped) => { - assert!(matches!(flipped, Reachability::Public(_))); + SwarmEvent::Behaviour(Event { nat_status, .. }) => { + assert!(matches!(nat_status, NatStatus::Public(_))); assert!(matches!( - client.behaviour().reachability(), - Reachability::Public(..) + client.behaviour().nat_status(), + NatStatus::Public(..) )); assert_eq!(client.behaviour().confidence(), 0); assert!(client.behaviour().public_address().is_some()); From 9901ab9e6dacccbd6afc842922a4a9b2ba6b7781 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Fri, 10 Dec 2021 18:58:47 +0100 Subject: [PATCH 43/69] protocols/autonat/behaviour: report all NAT events --- protocols/autonat/src/behaviour.rs | 41 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 855c7ee22b4..b7ea9844879 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -131,6 +131,8 @@ impl ProbeId { pub struct Event { pub probe_id: ProbeId, pub nat_status: NatStatus, + pub confidence: usize, + pub has_flipped: bool, } /// Network Behaviour for AutoNAT. @@ -159,8 +161,8 @@ pub struct Behaviour { ongoing_inbound: HashMap, ResponseChannel)>, // Ongoing outbound requests, where no response has been received from the remote yet. - // Map the ID of the inner request to the probe's ID and specify whether the probe was manually triggered. - ongoing_outbound: HashMap, + // Map the ID of the inner request to the probe's ID. + ongoing_outbound: HashMap, // Connected peers with their observed address. // These peers may be used as servers for dial-requests. @@ -553,22 +555,19 @@ impl NetworkBehaviour for Behaviour { addresses.extend(params.external_addresses().map(|r| r.addr)); addresses.extend(params.listened_addresses()); - let is_manually_triggered = server.is_some(); - let is_first = self.last_probe.is_none(); match self.do_probe(server, addresses) { Some(request_id) => { - self.ongoing_outbound - .insert(request_id, (probe_id, is_manually_triggered)); + self.ongoing_outbound.insert(request_id, probe_id); } None => { let nat_status = NatStatus::Unknown; let has_flipped = self.handle_reported_status(&nat_status); - if has_flipped || is_first || is_manually_triggered { - self.pending_out_events.push_back(Event { - nat_status, - probe_id, - }); - } + self.pending_out_events.push_back(Event { + nat_status, + confidence: self.confidence, + probe_id, + has_flipped, + }); } } } @@ -607,7 +606,7 @@ impl NetworkBehaviour for Behaviour { } => { log::debug!("Outbound dial-back request returned {:?}.", response); - let (probe_id, is_manually_triggered) = self + let probe_id = self .ongoing_outbound .remove(&request_id) .expect("Request ID exists."); @@ -625,14 +624,14 @@ impl NetworkBehaviour for Behaviour { }); } } - let new = response.result.into(); - let has_flipped = self.handle_reported_status(&new); - if self.last_probe.is_none() || has_flipped || is_manually_triggered { - self.pending_out_events.push_back(Event { - probe_id, - nat_status: new, - }); - } + let reported = response.result.into(); + let has_flipped = self.handle_reported_status(&reported); + self.pending_out_events.push_back(Event { + nat_status: reported, + confidence: self.confidence, + probe_id, + has_flipped, + }); if let Some(action) = report_addr { return Poll::Ready(action); } From b25f6b113844a5261b995e59588389c5e6685d12 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Fri, 10 Dec 2021 20:00:21 +0100 Subject: [PATCH 44/69] protocols/autonat: small fixes, test manual probes --- protocols/autonat/src/behaviour.rs | 26 ++++-- protocols/autonat/tests/autonat.rs | 137 +++++++++++++++++++++++------ 2 files changed, 128 insertions(+), 35 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index b7ea9844879..033023260c6 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -248,6 +248,7 @@ impl Behaviour { pub fn trigger_probe(&mut self, server: Option) -> Option { let server_id = server.or_else(|| self.random_server())?; let probe_id = self.probe_id.next(); + self.recent_probes.push((server_id, Instant::now())); self.pending_probes.push_back((probe_id, Some(server_id))); Some(probe_id) } @@ -268,6 +269,7 @@ impl Behaviour { return None; } let server = servers[rand::random::() % servers.len()]; + self.recent_probes.push((*server, Instant::now())); Some(*server) } @@ -292,7 +294,6 @@ impl Behaviour { addresses, }, ); - self.recent_probes.push((server, Instant::now())); self.last_probe = Some(Instant::now()); log::debug!("Send dial-back request to peer {}.", server); Some(request_id) @@ -501,8 +502,9 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - if !self.nat_status.is_public() { - self.confidence = 0; + + if self.confidence == self.config.confidence_max && !self.nat_status.is_public() { + self.confidence -= 1; self.schedule_probe.reset(Duration::ZERO); } } @@ -520,8 +522,8 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - if !self.nat_status.is_public() { - self.confidence = 0; + if self.confidence == self.config.confidence_max && !self.nat_status.is_public() { + self.confidence -= 1; self.schedule_probe.reset(Duration::ZERO); } } @@ -545,6 +547,7 @@ impl NetworkBehaviour for Behaviour { if self.pending_probes.is_empty() && self.schedule_probe.poll_unpin(cx).is_ready() { self.pending_probes.push_back((self.probe_id.next(), None)); self.schedule_probe.reset(self.config.refresh_interval); + println!("Scheduled probe is ready"); } loop { while let Some((probe_id, server)) = self.pending_probes.pop_front() { @@ -555,12 +558,14 @@ impl NetworkBehaviour for Behaviour { addresses.extend(params.external_addresses().map(|r| r.addr)); addresses.extend(params.listened_addresses()); + println!("server: {:?}, addrs: {:?}", server, addresses); match self.do_probe(server, addresses) { Some(request_id) => { self.ongoing_outbound.insert(request_id, probe_id); } None => { let nat_status = NatStatus::Unknown; + println!("naajhh"); let has_flipped = self.handle_reported_status(&nat_status); self.pending_out_events.push_back(Event { nat_status, @@ -641,8 +646,15 @@ impl NetworkBehaviour for Behaviour { RequestResponseEvent::ResponseSent { .. }, )) => {} Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { .. }, - )) => self.pending_probes.push_back((self.probe_id.next(), None)), + RequestResponseEvent::OutboundFailure { error, peer, .. }, + )) => { + log::debug!( + "Outbound Failure {} when sending dial-back request to server {}.", + error, + peer + ); + self.pending_probes.push_back((self.probe_id.next(), None)); + } Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::InboundFailure { peer, .. }, )) => { diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 85e4ebe8d62..2a1e814a0b8 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -18,8 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::time::Duration; - use futures::{channel::oneshot, FutureExt, StreamExt}; use libp2p::{ development_transport, @@ -28,6 +26,7 @@ use libp2p::{ Multiaddr, PeerId, }; use libp2p_autonat::{Behaviour, Config, Event, NatStatus}; +use std::time::Duration; const SERVER_COUNT: usize = 5; @@ -71,31 +70,34 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { } #[async_std::test] -async fn test_public() { - let mut handles = Vec::new(); - +async fn test_auto_probe() { let mut client = init_swarm(Config { retry_interval: Duration::from_millis(100), + throttle_peer_period: Duration::from_millis(99), boot_delay: Duration::ZERO, ..Default::default() }) .await; - for _ in 0..SERVER_COUNT { - let (tx, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; - client.behaviour_mut().add_server(id, Some(addr)); - handles.push(tx); - } + let (_handle, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); - // Auto-Probe should directly resolve to `Unknown` as the local peer has no listening addresses + // Probe should directly resolve to `Unknown` as the local peer has no listening addresses loop { match client.select_next_some().await { - SwarmEvent::Behaviour(Event { nat_status, .. }) => { + SwarmEvent::Behaviour(Event { + nat_status, + confidence, + has_flipped, + .. + }) => { assert_eq!(nat_status, NatStatus::Unknown); - assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); + assert_eq!(client.behaviour().nat_status(), nat_status); assert!(client.behaviour().public_address().is_none()); - assert_eq!(client.behaviour().confidence(), 0); + assert_eq!(confidence, 0); + assert_eq!(client.behaviour().confidence(), confidence); + assert!(!has_flipped); break; } _ => {} @@ -110,14 +112,18 @@ async fn test_public() { // Confidence should increase with each iteration loop { match client.select_next_some().await { - SwarmEvent::Behaviour(Event { nat_status, .. }) => { + SwarmEvent::Behaviour(Event { + nat_status, + has_flipped, + confidence, + .. + }) => { assert_eq!(nat_status, NatStatus::Private); - assert!(matches!( - client.behaviour().nat_status(), - NatStatus::Private - )); + assert_eq!(client.behaviour().nat_status(), nat_status); assert!(client.behaviour().public_address().is_none()); - assert_eq!(client.behaviour().confidence(), 0); + assert_eq!(confidence, 0); + assert_eq!(client.behaviour().confidence(), confidence); + assert!(has_flipped); break; } _ => {} @@ -135,20 +141,95 @@ async fn test_public() { } } - // Client should be reachable by the servers loop { match client.select_next_some().await { - SwarmEvent::Behaviour(Event { nat_status, .. }) => { + SwarmEvent::Behaviour(Event { + nat_status, + confidence, + has_flipped, + .. + }) => { assert!(matches!(nat_status, NatStatus::Public(_))); - assert!(matches!( - client.behaviour().nat_status(), - NatStatus::Public(..) - )); - assert_eq!(client.behaviour().confidence(), 0); + assert_eq!(client.behaviour().nat_status(), nat_status); + assert_eq!(confidence, 0); + assert_eq!(client.behaviour().confidence(), confidence); assert!(client.behaviour().public_address().is_some()); + assert!(has_flipped); break; } _ => {} } } + + drop(_handle); +} + +#[async_std::test] +async fn test_manual_probe() { + let mut handles = Vec::new(); + + let mut client = init_swarm(Config { + retry_interval: Duration::from_secs(60), + throttle_peer_period: Duration::from_millis(100), + ..Default::default() + }) + .await; + + for _ in 0..SERVER_COUNT { + let (tx, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); + handles.push(tx); + } + + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { .. } => break, + _ => {} + } + } + + let mut probes = Vec::new(); + + for _ in 0..SERVER_COUNT { + let probe_id = client.behaviour_mut().trigger_probe(None).unwrap(); + probes.push(probe_id); + } + + // Expect None because of peer throttling + assert!(client.behaviour_mut().trigger_probe(None).is_none()); + + let mut curr_confidence = 0; + let mut expect_flipped = true; + let mut i = 0; + while i < SERVER_COUNT { + match client.select_next_some().await { + SwarmEvent::Behaviour(Event { + nat_status, + probe_id, + confidence, + has_flipped, + }) => { + println!("status: {:?}", nat_status); + assert_eq!(expect_flipped, has_flipped); + expect_flipped = false; + assert!(matches!(nat_status, NatStatus::Public(_))); + assert_eq!(client.behaviour().nat_status(), nat_status); + assert_eq!(client.behaviour().confidence(), confidence); + assert_eq!(client.behaviour().confidence(), curr_confidence); + if curr_confidence < 3 { + curr_confidence += 1; + } + assert!(client.behaviour().public_address().is_some()); + let index = probes.binary_search(&probe_id).unwrap(); + probes.remove(index); + i += 1; + } + _ => {} + } + } + drop(handles) } From ac6d7c9f935b5c5cac752dedbe7bb293c5e158d1 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Fri, 10 Dec 2021 20:03:10 +0100 Subject: [PATCH 45/69] protocols/autonat: remove debug code --- protocols/autonat/src/behaviour.rs | 3 --- protocols/autonat/tests/autonat.rs | 1 - 2 files changed, 4 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 033023260c6..d03306660fb 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -547,7 +547,6 @@ impl NetworkBehaviour for Behaviour { if self.pending_probes.is_empty() && self.schedule_probe.poll_unpin(cx).is_ready() { self.pending_probes.push_back((self.probe_id.next(), None)); self.schedule_probe.reset(self.config.refresh_interval); - println!("Scheduled probe is ready"); } loop { while let Some((probe_id, server)) = self.pending_probes.pop_front() { @@ -558,14 +557,12 @@ impl NetworkBehaviour for Behaviour { addresses.extend(params.external_addresses().map(|r| r.addr)); addresses.extend(params.listened_addresses()); - println!("server: {:?}, addrs: {:?}", server, addresses); match self.do_probe(server, addresses) { Some(request_id) => { self.ongoing_outbound.insert(request_id, probe_id); } None => { let nat_status = NatStatus::Unknown; - println!("naajhh"); let has_flipped = self.handle_reported_status(&nat_status); self.pending_out_events.push_back(Event { nat_status, diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 2a1e814a0b8..0b327ca522a 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -213,7 +213,6 @@ async fn test_manual_probe() { confidence, has_flipped, }) => { - println!("status: {:?}", nat_status); assert_eq!(expect_flipped, has_flipped); expect_flipped = false; assert!(matches!(nat_status, NatStatus::Public(_))); From 37ab4d90c7dc89fd87faa51fbca161351945a5d3 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Fri, 10 Dec 2021 20:14:39 +0100 Subject: [PATCH 46/69] protocols/autonat/behaviour: fix reported NAT status --- protocols/autonat/src/behaviour.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index d03306660fb..4bb481b32b7 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -374,10 +374,10 @@ impl Behaviour { // Adapt current confidence and NAT status to the status reported by the latest probe. // Return whether the currently assumed status was flipped. - fn handle_reported_status(&mut self, reported: &NatStatus) -> bool { + fn handle_reported_status(&mut self, reported: NatStatus) -> bool { self.schedule_probe.reset(self.config.retry_interval); - if reported == &self.nat_status { + if reported == self.nat_status { if !matches!(self.nat_status, NatStatus::Unknown) { if self.confidence < self.config.confidence_max { self.confidence += 1; @@ -398,7 +398,7 @@ impl Behaviour { self.nat_status, reported ); - self.nat_status = reported.clone(); + self.nat_status = reported; true } } @@ -563,9 +563,9 @@ impl NetworkBehaviour for Behaviour { } None => { let nat_status = NatStatus::Unknown; - let has_flipped = self.handle_reported_status(&nat_status); + let has_flipped = self.handle_reported_status(nat_status); self.pending_out_events.push_back(Event { - nat_status, + nat_status: self.nat_status.clone(), confidence: self.confidence, probe_id, has_flipped, @@ -627,9 +627,9 @@ impl NetworkBehaviour for Behaviour { } } let reported = response.result.into(); - let has_flipped = self.handle_reported_status(&reported); + let has_flipped = self.handle_reported_status(reported); self.pending_out_events.push_back(Event { - nat_status: reported, + nat_status: self.nat_status.clone(), confidence: self.confidence, probe_id, has_flipped, From a2c3ca158f1a0ac20630101973193a2a56a116ce Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 12 Dec 2021 13:49:48 +0100 Subject: [PATCH 47/69] protocols/autonat: only report probes if status flipped Revert that all nat events are reported, instead only report nat status if if flipped. Remove `Event` type as OutEvent, remove ProbeIds, remove manual triggering of probes. --- protocols/autonat/examples/client.rs | 6 +- protocols/autonat/src/behaviour.rs | 164 ++++++++------------ protocols/autonat/src/lib.rs | 2 +- protocols/autonat/tests/autonat.rs | 217 +++++++++++++++------------ 4 files changed, 185 insertions(+), 204 deletions(-) diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index e6591004999..b4b5927e4a4 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -97,7 +97,7 @@ impl Behaviour { #[derive(Debug)] enum Event { - AutoNat(autonat::Event), + AutoNat(autonat::NatStatus), Identify(IdentifyEvent), } @@ -107,8 +107,8 @@ impl From for Event { } } -impl From for Event { - fn from(v: autonat::Event) -> Self { +impl From for Event { + fn from(v: autonat::NatStatus) -> Self { Self::AutoNat(v) } } diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 4bb481b32b7..2aaadce045a 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -116,25 +116,6 @@ impl From> for NatStatus { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ProbeId(usize); - -impl ProbeId { - fn next(&mut self) -> ProbeId { - let current = *self; - self.0 += 1; - current - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Event { - pub probe_id: ProbeId, - pub nat_status: NatStatus, - pub confidence: usize, - pub has_flipped: bool, -} - /// Network Behaviour for AutoNAT. pub struct Behaviour { // Local peer id @@ -154,31 +135,21 @@ pub struct Behaviour { // Confidence in the assumed NAT status. confidence: usize, - // Delay until next probe. + // Timer for the next probe. schedule_probe: Delay, // Ongoing inbound requests, where no response has been sent back to the remote yet. ongoing_inbound: HashMap, ResponseChannel)>, - // Ongoing outbound requests, where no response has been received from the remote yet. - // Map the ID of the inner request to the probe's ID. - ongoing_outbound: HashMap, - // Connected peers with their observed address. // These peers may be used as servers for dial-requests. connected: HashMap, // Used servers in recent outbound probes that are throttled through Config::throttle_peer_period. - recent_probes: Vec<(PeerId, Instant)>, + throttled_servers: Vec<(PeerId, Instant)>, last_probe: Option, - probe_id: ProbeId, - - // Pending probes for which a dial-request should be send to a server. - // In case of a manually triggered probe the server is set. - pending_probes: VecDeque<(ProbeId, Option)>, - pending_out_events: VecDeque<::OutEvent>, } @@ -195,15 +166,12 @@ impl Behaviour { config, servers: Vec::new(), ongoing_inbound: HashMap::default(), - ongoing_outbound: HashMap::default(), connected: HashMap::default(), nat_status: NatStatus::Unknown, confidence: 0, - recent_probes: Vec::new(), + throttled_servers: Vec::new(), last_probe: None, pending_out_events: VecDeque::new(), - pending_probes: VecDeque::new(), - probe_id: ProbeId(0), } } @@ -242,22 +210,11 @@ impl Behaviour { self.servers.retain(|p| p != peer); } - /// Manually trigger a probe. Optionally a the server can be set that is used, otherwise - /// it will select a server based on [`Config::may_use_connected`]. In case of the latter, - /// return `None` if no qualified server is present. - pub fn trigger_probe(&mut self, server: Option) -> Option { - let server_id = server.or_else(|| self.random_server())?; - let probe_id = self.probe_id.next(); - self.recent_probes.push((server_id, Instant::now())); - self.pending_probes.push_back((probe_id, Some(server_id))); - Some(probe_id) - } - - // Select a random server for the probe based on [`Config::may_use_connected`]. + // Select a random server for the probe. fn random_server(&mut self) -> Option { - self.recent_probes + self.throttled_servers .retain(|(_, time)| *time + self.config.throttle_peer_period > Instant::now()); - let throttled: Vec<_> = self.recent_probes.iter().map(|(id, _)| id).collect(); + let throttled: Vec<_> = self.throttled_servers.iter().map(|(id, _)| id).collect(); let mut servers: Vec<&PeerId> = self.servers.iter().collect(); if self.config.may_use_connected { @@ -269,18 +226,18 @@ impl Behaviour { return None; } let server = servers[rand::random::() % servers.len()]; - self.recent_probes.push((*server, Instant::now())); Some(*server) } // Send a dial request to the set server or a randomly selected one. // Return `None` if there are no qualified servers or no dial-back addresses. - fn do_probe(&mut self, server: Option, addresses: Vec) -> Option { + fn do_probe(&mut self, addresses: Vec) -> Option { + self.last_probe = Some(Instant::now()); if addresses.is_empty() { log::debug!("Outbound dial-back request aborted: No dial-back addresses."); return None; } - let server = match server.or_else(|| self.random_server()) { + let server = match self.random_server() { Some(s) => s, None => { log::debug!("Outbound dial-back request aborted: No qualified server."); @@ -294,7 +251,7 @@ impl Behaviour { addresses, }, ); - self.last_probe = Some(Instant::now()); + self.throttled_servers.push((server, Instant::now())); log::debug!("Send dial-back request to peer {}.", server); Some(request_id) } @@ -372,19 +329,31 @@ impl Behaviour { Ok(addrs) } + fn schedule_next_probe(&mut self, delay: Duration) { + let last_probe_instant = match self.last_probe { + Some(instant) => instant, + None => { + return; + } + }; + let schedule_next = last_probe_instant + delay; + let diff = schedule_next.checked_duration_since(Instant::now()); + self.schedule_probe.reset(diff.unwrap_or(Duration::ZERO)); + } + // Adapt current confidence and NAT status to the status reported by the latest probe. // Return whether the currently assumed status was flipped. fn handle_reported_status(&mut self, reported: NatStatus) -> bool { - self.schedule_probe.reset(self.config.retry_interval); + self.schedule_next_probe(self.config.retry_interval); if reported == self.nat_status { if !matches!(self.nat_status, NatStatus::Unknown) { if self.confidence < self.config.confidence_max { self.confidence += 1; } - // Delay with (usually longer) refresh-interval if we reached max confidence. + // Delay with (usually longer) refresh-interval. if self.confidence >= self.config.confidence_max { - self.schedule_probe.reset(self.config.refresh_interval); + self.schedule_next_probe(self.config.refresh_interval); } } false @@ -406,7 +375,7 @@ impl Behaviour { impl NetworkBehaviour for Behaviour { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; - type OutEvent = Event; + type OutEvent = NatStatus; fn inject_connection_established( &mut self, @@ -442,19 +411,12 @@ impl NetworkBehaviour for Behaviour { } // An inbound connection can indicate that we are public; adjust the delay to the next probe. ConnectedPoint::Listener { .. } => { - if self.confidence < self.config.confidence_max { - // Retry already scheduled. - return; - } - let last_probe_instant = self - .last_probe - .expect("Confidence > 0 implies that there was already a probe."); - if self.nat_status.is_public() { - let schedule_next = last_probe_instant + self.config.refresh_interval * 2; - self.schedule_probe.reset(schedule_next - Instant::now()); - } else { - let schedule_next = last_probe_instant + self.config.refresh_interval / 5; - self.schedule_probe.reset(schedule_next - Instant::now()); + if self.confidence == self.config.confidence_max { + if self.nat_status.is_public() { + self.schedule_next_probe(self.config.refresh_interval * 2); + } else { + self.schedule_next_probe(self.config.refresh_interval / 5); + } } } } @@ -503,9 +465,12 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - if self.confidence == self.config.confidence_max && !self.nat_status.is_public() { - self.confidence -= 1; - self.schedule_probe.reset(Duration::ZERO); + // New address could be publicly reachable, trigger retry. + if !self.nat_status.is_public() && self.confidence == self.config.confidence_max { + if self.confidence > 0 { + self.confidence -= 1; + } + self.schedule_next_probe(self.config.refresh_interval); } } @@ -522,9 +487,13 @@ impl NetworkBehaviour for Behaviour { fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - if self.confidence == self.config.confidence_max && !self.nat_status.is_public() { - self.confidence -= 1; - self.schedule_probe.reset(Duration::ZERO); + + // New address could be publicly reachable, trigger retry. + if !self.nat_status.is_public() && self.confidence == self.config.confidence_max { + if self.confidence > 0 { + self.confidence -= 1; + } + self.schedule_next_probe(self.config.refresh_interval); } } @@ -544,12 +513,13 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, params: &mut impl PollParameters, ) -> Poll> { - if self.pending_probes.is_empty() && self.schedule_probe.poll_unpin(cx).is_ready() { - self.pending_probes.push_back((self.probe_id.next(), None)); + let mut is_probe_ready = false; + if self.schedule_probe.poll_unpin(cx).is_ready() { + is_probe_ready = true; self.schedule_probe.reset(self.config.refresh_interval); } loop { - while let Some((probe_id, server)) = self.pending_probes.pop_front() { + if is_probe_ready { let mut addresses = match self.public_address() { Some(a) => vec![a.clone()], // Remote should try our assumed public address first. None => Vec::new(), @@ -557,19 +527,14 @@ impl NetworkBehaviour for Behaviour { addresses.extend(params.external_addresses().map(|r| r.addr)); addresses.extend(params.listened_addresses()); - match self.do_probe(server, addresses) { - Some(request_id) => { - self.ongoing_outbound.insert(request_id, probe_id); - } + match self.do_probe(addresses) { + Some(_) => {} None => { let nat_status = NatStatus::Unknown; let has_flipped = self.handle_reported_status(nat_status); - self.pending_out_events.push_back(Event { - nat_status: self.nat_status.clone(), - confidence: self.confidence, - probe_id, - has_flipped, - }); + if has_flipped { + self.pending_out_events.push_back(self.nat_status.clone()); + } } } } @@ -602,16 +567,8 @@ impl NetworkBehaviour for Behaviour { } } } - RequestResponseMessage::Response { - response, - request_id, - } => { + RequestResponseMessage::Response { response, .. } => { log::debug!("Outbound dial-back request returned {:?}.", response); - - let probe_id = self - .ongoing_outbound - .remove(&request_id) - .expect("Request ID exists."); let mut report_addr = None; if let Ok(ref addr) = response.result { // Update observed address score if it is finite. @@ -628,12 +585,9 @@ impl NetworkBehaviour for Behaviour { } let reported = response.result.into(); let has_flipped = self.handle_reported_status(reported); - self.pending_out_events.push_back(Event { - nat_status: self.nat_status.clone(), - confidence: self.confidence, - probe_id, - has_flipped, - }); + if has_flipped { + self.pending_out_events.push_back(self.nat_status.clone()); + } if let Some(action) = report_addr { return Poll::Ready(action); } @@ -650,7 +604,7 @@ impl NetworkBehaviour for Behaviour { error, peer ); - self.pending_probes.push_back((self.probe_id.next(), None)); + is_probe_ready = true; } Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::InboundFailure { peer, .. }, diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index f1e38bcf64f..aa9387b74ce 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -23,7 +23,7 @@ mod behaviour; mod protocol; pub use self::{ - behaviour::{Behaviour, Config, Event, NatStatus, ProbeId}, + behaviour::{Behaviour, Config, NatStatus}, protocol::ResponseError, }; diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 0b327ca522a..858fd76ad51 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -18,17 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{channel::oneshot, FutureExt, StreamExt}; +use async_std::task::sleep; +use futures::{channel::oneshot, select, FutureExt, StreamExt}; use libp2p::{ development_transport, identity::Keypair, swarm::{AddressScore, Swarm, SwarmEvent}, Multiaddr, PeerId, }; -use libp2p_autonat::{Behaviour, Config, Event, NatStatus}; +use libp2p_autonat::{Behaviour, Config, NatStatus}; use std::time::Duration; const SERVER_COUNT: usize = 5; +const MAX_CONFIDENCE: usize = 3; async fn init_swarm(config: Config) -> Swarm { let keypair = Keypair::generate_ed25519(); @@ -69,11 +71,38 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { rx.await.unwrap() } +async fn next_status(client: &mut Swarm) -> NatStatus { + loop { + match client.select_next_some().await { + SwarmEvent::Behaviour(nat_status) => { + break nat_status; + } + _ => {} + } + } +} + +async fn poll_and_sleep(client: &mut Swarm, duration: Duration) { + let poll = async { + loop { + let _ = client.next().await; + } + }; + select! { + _ = poll.fuse()=> {}, + _ = sleep(duration).fuse() => {}, + } +} + #[async_std::test] +#[ignore] +// TODO: fix test async fn test_auto_probe() { + let test_retry_interval = Duration::from_millis(100); let mut client = init_swarm(Config { - retry_interval: Duration::from_millis(100), - throttle_peer_period: Duration::from_millis(99), + retry_interval: test_retry_interval, + refresh_interval: test_retry_interval, + throttle_peer_period: Duration::ZERO, boot_delay: Duration::ZERO, ..Default::default() }) @@ -83,54 +112,60 @@ async fn test_auto_probe() { let (id, addr) = spawn_server(rx).await; client.behaviour_mut().add_server(id, Some(addr)); - // Probe should directly resolve to `Unknown` as the local peer has no listening addresses - loop { - match client.select_next_some().await { - SwarmEvent::Behaviour(Event { - nat_status, - confidence, - has_flipped, - .. - }) => { - assert_eq!(nat_status, NatStatus::Unknown); - assert_eq!(client.behaviour().nat_status(), nat_status); - assert!(client.behaviour().public_address().is_none()); - assert_eq!(confidence, 0); - assert_eq!(client.behaviour().confidence(), confidence); - assert!(!has_flipped); - break; - } - _ => {} - } - } + // Initial status should be unknown + assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); + assert!(client.behaviour().public_address().is_none()); + assert_eq!(client.behaviour().confidence(), 0); // Artificially add a faulty address. let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); // Auto-Probe should resolve to private since the server can not reach us. - // Confidence should increase with each iteration - loop { - match client.select_next_some().await { - SwarmEvent::Behaviour(Event { - nat_status, - has_flipped, - confidence, - .. - }) => { - assert_eq!(nat_status, NatStatus::Private); - assert_eq!(client.behaviour().nat_status(), nat_status); - assert!(client.behaviour().public_address().is_none()); - assert_eq!(confidence, 0); - assert_eq!(client.behaviour().confidence(), confidence); - assert!(has_flipped); - break; - } - _ => {} + // Confidence should increase with each iteration. + let mut expect_confidence = 0; + let status = next_status(&mut client).await; + assert_eq!(status, NatStatus::Private); + assert_eq!(client.behaviour().confidence(), expect_confidence); + assert_eq!(client.behaviour().nat_status(), NatStatus::Private); + + for _ in 0..MAX_CONFIDENCE + 1 { + poll_and_sleep(&mut client, test_retry_interval).await; + + if expect_confidence < MAX_CONFIDENCE { + expect_confidence += 1; } + + assert_eq!(client.behaviour().nat_status(), NatStatus::Private); + assert!(client.behaviour().public_address().is_none()); + assert_eq!(client.behaviour().confidence(), expect_confidence); } + assert_eq!(client.behaviour().confidence(), MAX_CONFIDENCE); + client.remove_external_address(&unreachable_addr); + // Confidence in private should decrease + while expect_confidence > 0 { + poll_and_sleep(&mut client, test_retry_interval).await; + + expect_confidence -= 1; + + assert_eq!(client.behaviour().nat_status(), NatStatus::Private); + assert_eq!(client.behaviour().confidence(), expect_confidence); + } + + // expect to flip to Unknown + let status = next_status(&mut client).await; + assert_eq!(status, NatStatus::Unknown); + + // Confidence should not increase. + for _ in 0..MAX_CONFIDENCE { + poll_and_sleep(&mut client, test_retry_interval).await; + + assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); + assert_eq!(client.behaviour().confidence(), 0); + } + client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); @@ -141,36 +176,38 @@ async fn test_auto_probe() { } } - loop { - match client.select_next_some().await { - SwarmEvent::Behaviour(Event { - nat_status, - confidence, - has_flipped, - .. - }) => { - assert!(matches!(nat_status, NatStatus::Public(_))); - assert_eq!(client.behaviour().nat_status(), nat_status); - assert_eq!(confidence, 0); - assert_eq!(client.behaviour().confidence(), confidence); - assert!(client.behaviour().public_address().is_some()); - assert!(has_flipped); - break; - } - _ => {} + let status = next_status(&mut client).await; + assert!(status.is_public()); + let public_addr = client.behaviour().public_address().unwrap().clone(); + + let mut expect_confidence = 0; + for _ in 0..MAX_CONFIDENCE + 1 { + poll_and_sleep(&mut client, test_retry_interval).await; + + if expect_confidence < MAX_CONFIDENCE { + expect_confidence += 1; } + + assert!(client.behaviour().nat_status().is_public()); + assert_eq!(client.behaviour().public_address(), Some(&public_addr)); + assert_eq!(client.behaviour().confidence(), expect_confidence); } drop(_handle); } #[async_std::test] -async fn test_manual_probe() { +#[ignore] +// TODO: fix test +async fn test_throttle_peer_period() { let mut handles = Vec::new(); + let test_retry_interval = Duration::from_millis(100); let mut client = init_swarm(Config { - retry_interval: Duration::from_secs(60), - throttle_peer_period: Duration::from_millis(100), + retry_interval: test_retry_interval, + refresh_interval: test_retry_interval, + throttle_peer_period: Duration::from_secs(100), + boot_delay: Duration::from_millis(100), ..Default::default() }) .await; @@ -192,43 +229,33 @@ async fn test_manual_probe() { } } - let mut probes = Vec::new(); + let nat_status = next_status(&mut client).await; + assert!(nat_status.is_public()); + assert_eq!(client.behaviour().confidence(), 0); - for _ in 0..SERVER_COUNT { - let probe_id = client.behaviour_mut().trigger_probe(None).unwrap(); - probes.push(probe_id); + let mut expect_confidence = 0; + for _ in 0..SERVER_COUNT - 1 { + poll_and_sleep(&mut client, test_retry_interval).await; + + if expect_confidence < MAX_CONFIDENCE { + expect_confidence += 1; + } + assert!(client.behaviour().nat_status().is_public()); + assert_eq!(client.behaviour().confidence(), expect_confidence); } - // Expect None because of peer throttling - assert!(client.behaviour_mut().trigger_probe(None).is_none()); + assert!(nat_status.is_public()); - let mut curr_confidence = 0; - let mut expect_flipped = true; - let mut i = 0; - while i < SERVER_COUNT { - match client.select_next_some().await { - SwarmEvent::Behaviour(Event { - nat_status, - probe_id, - confidence, - has_flipped, - }) => { - assert_eq!(expect_flipped, has_flipped); - expect_flipped = false; - assert!(matches!(nat_status, NatStatus::Public(_))); - assert_eq!(client.behaviour().nat_status(), nat_status); - assert_eq!(client.behaviour().confidence(), confidence); - assert_eq!(client.behaviour().confidence(), curr_confidence); - if curr_confidence < 3 { - curr_confidence += 1; - } - assert!(client.behaviour().public_address().is_some()); - let index = probes.binary_search(&probe_id).unwrap(); - probes.remove(index); - i += 1; - } - _ => {} - } + // All servers are throttled, probes will decrease confidence + while expect_confidence > 0 { + poll_and_sleep(&mut client, test_retry_interval).await; + expect_confidence -= 1; + assert_eq!(client.behaviour().confidence(), expect_confidence); + assert!(client.behaviour().nat_status().is_public()); } + let nat_status = next_status(&mut client).await; + assert_eq!(nat_status, NatStatus::Unknown); + assert_eq!(client.behaviour().confidence(), 0); + drop(handles) } From 750040c50cbb11291323db14549e8788ec3bb017 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 13 Dec 2021 03:04:04 +0100 Subject: [PATCH 48/69] protocols/autonat: tests --- protocols/autonat/Cargo.toml | 7 +- protocols/autonat/src/behaviour.rs | 9 +- protocols/autonat/tests/autonat.rs | 223 ++++++++++++++++++++--------- 3 files changed, 167 insertions(+), 72 deletions(-) diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index 37f5a4230b9..f4acfa9e593 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -27,5 +27,10 @@ prost = "0.8" [dev-dependencies] async-std = { version = "1.10", features = ["attributes"] } env_logger = "0.9" -libp2p = { path = "../../", default-features = false, features = ["autonat", "dns-async-std", "identify", "mplex", "noise", "tcp-async-io", "websocket", "yamux"]} structopt = "0.3" + + +[dev-dependencies.libp2p] +path = "../../" +default-features = false +features = ["autonat", "dns-async-std", "identify", "mplex", "noise", "tcp-async-io", "websocket", "yamux"] \ No newline at end of file diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 2aaadce045a..baa961e25b7 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -514,11 +514,12 @@ impl NetworkBehaviour for Behaviour { params: &mut impl PollParameters, ) -> Poll> { let mut is_probe_ready = false; - if self.schedule_probe.poll_unpin(cx).is_ready() { - is_probe_ready = true; - self.schedule_probe.reset(self.config.refresh_interval); - } loop { + if self.schedule_probe.poll_unpin(cx).is_ready() { + is_probe_ready = true; + self.schedule_probe.reset(self.config.refresh_interval); + continue; + } if is_probe_ready { let mut addresses = match self.public_address() { Some(a) => vec![a.clone()], // Remote should try our assumed public address first. diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 858fd76ad51..39e1661701e 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -29,8 +29,10 @@ use libp2p::{ use libp2p_autonat::{Behaviour, Config, NatStatus}; use std::time::Duration; -const SERVER_COUNT: usize = 5; +const SERVER_COUNT: usize = 10; const MAX_CONFIDENCE: usize = 3; +const TEST_RETRY_INTERVAL: Duration = Duration::from_millis(50); +const TEST_REFRESH_INTERVAL: Duration = Duration::from_millis(100); async fn init_swarm(config: Config) -> Swarm { let keypair = Keypair::generate_ed25519(); @@ -82,6 +84,7 @@ async fn next_status(client: &mut Swarm) -> NatStatus { } } +// Await a delay while still driving the swarm. async fn poll_and_sleep(client: &mut Swarm, duration: Duration) { let poll = async { loop { @@ -95,13 +98,9 @@ async fn poll_and_sleep(client: &mut Swarm, duration: Duration) { } #[async_std::test] -#[ignore] -// TODO: fix test async fn test_auto_probe() { - let test_retry_interval = Duration::from_millis(100); let mut client = init_swarm(Config { - retry_interval: test_retry_interval, - refresh_interval: test_retry_interval, + retry_interval: TEST_RETRY_INTERVAL, throttle_peer_period: Duration::ZERO, boot_delay: Duration::ZERO, ..Default::default() @@ -112,7 +111,7 @@ async fn test_auto_probe() { let (id, addr) = spawn_server(rx).await; client.behaviour_mut().add_server(id, Some(addr)); - // Initial status should be unknown + // Initial status should be unknown. assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); assert!(client.behaviour().public_address().is_none()); assert_eq!(client.behaviour().confidence(), 0); @@ -121,51 +120,18 @@ async fn test_auto_probe() { let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); - // Auto-Probe should resolve to private since the server can not reach us. - // Confidence should increase with each iteration. - let mut expect_confidence = 0; let status = next_status(&mut client).await; assert_eq!(status, NatStatus::Private); - assert_eq!(client.behaviour().confidence(), expect_confidence); + assert_eq!(client.behaviour().confidence(), 0); assert_eq!(client.behaviour().nat_status(), NatStatus::Private); - for _ in 0..MAX_CONFIDENCE + 1 { - poll_and_sleep(&mut client, test_retry_interval).await; - - if expect_confidence < MAX_CONFIDENCE { - expect_confidence += 1; - } - - assert_eq!(client.behaviour().nat_status(), NatStatus::Private); - assert!(client.behaviour().public_address().is_none()); - assert_eq!(client.behaviour().confidence(), expect_confidence); - } - assert_eq!(client.behaviour().confidence(), MAX_CONFIDENCE); - + // Test empty addresses. client.remove_external_address(&unreachable_addr); - // Confidence in private should decrease - while expect_confidence > 0 { - poll_and_sleep(&mut client, test_retry_interval).await; - - expect_confidence -= 1; - - assert_eq!(client.behaviour().nat_status(), NatStatus::Private); - assert_eq!(client.behaviour().confidence(), expect_confidence); - } - - // expect to flip to Unknown let status = next_status(&mut client).await; assert_eq!(status, NatStatus::Unknown); - // Confidence should not increase. - for _ in 0..MAX_CONFIDENCE { - poll_and_sleep(&mut client, test_retry_interval).await; - - assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); - assert_eq!(client.behaviour().confidence(), 0); - } - + // Start listening. client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); @@ -176,37 +142,33 @@ async fn test_auto_probe() { } } - let status = next_status(&mut client).await; - assert!(status.is_public()); - let public_addr = client.behaviour().public_address().unwrap().clone(); - - let mut expect_confidence = 0; - for _ in 0..MAX_CONFIDENCE + 1 { - poll_and_sleep(&mut client, test_retry_interval).await; - - if expect_confidence < MAX_CONFIDENCE { - expect_confidence += 1; + // Expect inbound dial from server. + loop { + match client.select_next_some().await { + SwarmEvent::OutgoingConnectionError { .. } => {} + SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => break, + _ => {} } - - assert!(client.behaviour().nat_status().is_public()); - assert_eq!(client.behaviour().public_address(), Some(&public_addr)); - assert_eq!(client.behaviour().confidence(), expect_confidence); } + let status = next_status(&mut client).await; + assert!(status.is_public()); + assert!(client.behaviour().public_address().is_some()); + drop(_handle); } +// FIXME: flaky test #[async_std::test] #[ignore] -// TODO: fix test async fn test_throttle_peer_period() { let mut handles = Vec::new(); - let test_retry_interval = Duration::from_millis(100); let mut client = init_swarm(Config { - retry_interval: test_retry_interval, - refresh_interval: test_retry_interval, - throttle_peer_period: Duration::from_secs(100), + retry_interval: TEST_RETRY_INTERVAL, + refresh_interval: TEST_REFRESH_INTERVAL, + // Throttle servers so they can not be re-used for dial request. + throttle_peer_period: Duration::from_secs(1000), boot_delay: Duration::from_millis(100), ..Default::default() }) @@ -233,9 +195,18 @@ async fn test_throttle_peer_period() { assert!(nat_status.is_public()); assert_eq!(client.behaviour().confidence(), 0); + poll_and_sleep(&mut client, TEST_REFRESH_INTERVAL / 2).await; + let mut expect_confidence = 0; + + // Use each server once (minus 1 because one was already used above). for _ in 0..SERVER_COUNT - 1 { - poll_and_sleep(&mut client, test_retry_interval).await; + // Sleep until probe should be completed in background. + if expect_confidence == MAX_CONFIDENCE { + poll_and_sleep(&mut client, TEST_REFRESH_INTERVAL).await; + } else { + poll_and_sleep(&mut client, TEST_RETRY_INTERVAL).await; + } if expect_confidence < MAX_CONFIDENCE { expect_confidence += 1; @@ -244,18 +215,136 @@ async fn test_throttle_peer_period() { assert_eq!(client.behaviour().confidence(), expect_confidence); } - assert!(nat_status.is_public()); - - // All servers are throttled, probes will decrease confidence + // All servers are throttled. while expect_confidence > 0 { - poll_and_sleep(&mut client, test_retry_interval).await; + // Sleep until probe resolved in background. + if expect_confidence == MAX_CONFIDENCE { + poll_and_sleep(&mut client, TEST_REFRESH_INTERVAL).await; + } else { + poll_and_sleep(&mut client, TEST_RETRY_INTERVAL).await; + } + expect_confidence -= 1; assert_eq!(client.behaviour().confidence(), expect_confidence); assert!(client.behaviour().nat_status().is_public()); } + let nat_status = next_status(&mut client).await; assert_eq!(nat_status, NatStatus::Unknown); assert_eq!(client.behaviour().confidence(), 0); drop(handles) } + +#[async_std::test] +async fn test_use_connected_as_server() { + let mut client = init_swarm(Config { + retry_interval: TEST_RETRY_INTERVAL, + throttle_peer_period: Duration::ZERO, + boot_delay: Duration::ZERO, + ..Default::default() + }) + .await; + + let (_handle, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + + client.dial(addr).unwrap(); + + let nat_status = loop { + match client.select_next_some().await { + SwarmEvent::Behaviour(status) => break status, + SwarmEvent::ConnectionClosed { peer_id, .. } => { + // keep connection alive + client.dial(peer_id).unwrap(); + } + _ => {} + } + }; + + assert!(nat_status.is_public()); + assert_eq!(client.behaviour().confidence(), 0); + + let _ = client.disconnect_peer_id(id); + + loop { + match client.select_next_some().await { + SwarmEvent::ConnectionClosed { peer_id, .. } if peer_id == id => break, + _ => {} + } + } + + let nat_status = next_status(&mut client).await; + assert_eq!(nat_status, NatStatus::Unknown); + assert_eq!(client.behaviour().confidence(), 0); + + drop(_handle); +} + +#[async_std::test] +async fn test_outbound_failure() { + let mut handles = Vec::new(); + + let mut client = init_swarm(Config { + retry_interval: TEST_RETRY_INTERVAL, + refresh_interval: TEST_REFRESH_INTERVAL, + throttle_peer_period: Duration::ZERO, + boot_delay: Duration::from_millis(100), + ..Default::default() + }) + .await; + + for _ in 0..SERVER_COUNT { + let (tx, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); + handles.push((id, tx)); + } + + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { .. } => break, + _ => {} + } + } + + let nat_status = next_status(&mut client).await; + assert!(nat_status.is_public()); + assert_eq!(client.behaviour().confidence(), 0); + + // Kill all servers apart from one. + handles.drain(1..); + // Disconnect server in case that it was used for the first probe. + let _ = client.disconnect_peer_id(handles[0].0); + + // Expect retry on outbound failure until one server can be reached. + loop { + match client.select_next_some().await { + SwarmEvent::OutgoingConnectionError { .. } => {} + SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_dialer() => break, + _ => {} + } + } + + // Expect inbound dial. + loop { + match client.select_next_some().await { + SwarmEvent::OutgoingConnectionError { .. } => {} + SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => break, + _ => {} + } + } + + // Delay so that the server has time to send dial-response + poll_and_sleep(&mut client, Duration::from_millis(100)).await; + + assert!(client.behaviour().confidence() > 0); +} From 698ef8c07ec5a454592c3523660b19d795d90dec Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 13 Dec 2021 16:47:33 +0100 Subject: [PATCH 49/69] protocols/autonat: fix tests --- protocols/autonat/src/behaviour.rs | 26 +- protocols/autonat/tests/autonat.rs | 398 ++++++++++++++--------------- 2 files changed, 212 insertions(+), 212 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index baa961e25b7..70e0c621009 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -212,16 +212,20 @@ impl Behaviour { // Select a random server for the probe. fn random_server(&mut self) -> Option { - self.throttled_servers - .retain(|(_, time)| *time + self.config.throttle_peer_period > Instant::now()); - let throttled: Vec<_> = self.throttled_servers.iter().map(|(id, _)| id).collect(); + // Update list of throttled servers. + let i = self + .throttled_servers + .partition_point(|(_, time)| *time + self.config.throttle_peer_period < Instant::now()); + self.throttled_servers.drain(..i); + let mut servers: Vec<&PeerId> = self.servers.iter().collect(); if self.config.may_use_connected { servers.extend(self.connected.iter().map(|(id, _)| id)); } - servers.retain(|s| !throttled.contains(s)); + servers.retain(|s| !self.throttled_servers.iter().any(|(id, _)| s == &id)); + if servers.is_empty() { return None; } @@ -337,8 +341,8 @@ impl Behaviour { } }; let schedule_next = last_probe_instant + delay; - let diff = schedule_next.checked_duration_since(Instant::now()); - self.schedule_probe.reset(diff.unwrap_or(Duration::ZERO)); + self.schedule_probe + .reset(schedule_next.saturating_duration_since(Instant::now())); } // Adapt current confidence and NAT status to the status reported by the latest probe. @@ -466,11 +470,11 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_new_listen_addr(id, addr); // New address could be publicly reachable, trigger retry. - if !self.nat_status.is_public() && self.confidence == self.config.confidence_max { + if !self.nat_status.is_public() { if self.confidence > 0 { self.confidence -= 1; } - self.schedule_next_probe(self.config.refresh_interval); + self.schedule_next_probe(self.config.retry_interval); } } @@ -489,11 +493,11 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_new_external_addr(addr); // New address could be publicly reachable, trigger retry. - if !self.nat_status.is_public() && self.confidence == self.config.confidence_max { + if !self.nat_status.is_public() { if self.confidence > 0 { self.confidence -= 1; } - self.schedule_next_probe(self.config.refresh_interval); + self.schedule_next_probe(self.config.retry_interval); } } @@ -517,7 +521,7 @@ impl NetworkBehaviour for Behaviour { loop { if self.schedule_probe.poll_unpin(cx).is_ready() { is_probe_ready = true; - self.schedule_probe.reset(self.config.refresh_interval); + self.schedule_probe.reset(self.config.retry_interval); continue; } if is_probe_ready { diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 39e1661701e..227b5ee6939 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -20,6 +20,7 @@ use async_std::task::sleep; use futures::{channel::oneshot, select, FutureExt, StreamExt}; +use futures_timer::Delay; use libp2p::{ development_transport, identity::Keypair, @@ -47,6 +48,7 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { async_std::task::spawn(async move { let mut swarm = init_swarm(Config { boot_delay: Duration::from_secs(60), + throttle_peer_period: Duration::ZERO, ..Default::default() }) .await; @@ -99,252 +101,246 @@ async fn poll_and_sleep(client: &mut Swarm, duration: Duration) { #[async_std::test] async fn test_auto_probe() { - let mut client = init_swarm(Config { - retry_interval: TEST_RETRY_INTERVAL, - throttle_peer_period: Duration::ZERO, - boot_delay: Duration::ZERO, - ..Default::default() - }) - .await; - - let (_handle, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; - client.behaviour_mut().add_server(id, Some(addr)); - - // Initial status should be unknown. - assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); - assert!(client.behaviour().public_address().is_none()); - assert_eq!(client.behaviour().confidence(), 0); - - // Artificially add a faulty address. - let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); - client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); - - let status = next_status(&mut client).await; - assert_eq!(status, NatStatus::Private); - assert_eq!(client.behaviour().confidence(), 0); - assert_eq!(client.behaviour().nat_status(), NatStatus::Private); - - // Test empty addresses. - client.remove_external_address(&unreachable_addr); - - let status = next_status(&mut client).await; - assert_eq!(status, NatStatus::Unknown); - - // Start listening. - client - .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) - .unwrap(); - loop { - match client.select_next_some().await { - SwarmEvent::NewListenAddr { .. } => break, - _ => {} + let run_test = async { + let mut client = init_swarm(Config { + retry_interval: TEST_RETRY_INTERVAL, + refresh_interval: TEST_REFRESH_INTERVAL, + confidence_max: MAX_CONFIDENCE, + throttle_peer_period: Duration::ZERO, + boot_delay: Duration::ZERO, + ..Default::default() + }) + .await; + + let (_handle, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); + + // Initial status should be unknown. + assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); + assert!(client.behaviour().public_address().is_none()); + assert_eq!(client.behaviour().confidence(), 0); + + // Artificially add a faulty address. + let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); + client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); + + let status = next_status(&mut client).await; + assert_eq!(status, NatStatus::Private); + assert_eq!(client.behaviour().confidence(), 0); + assert_eq!(client.behaviour().nat_status(), NatStatus::Private); + + // Test empty addresses. + client.remove_external_address(&unreachable_addr); + + let status = next_status(&mut client).await; + assert_eq!(status, NatStatus::Unknown); + + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { .. } => break, + _ => {} + } } - } - // Expect inbound dial from server. - loop { - match client.select_next_some().await { - SwarmEvent::OutgoingConnectionError { .. } => {} - SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => break, - _ => {} + // Expect inbound dial from server. + loop { + match client.select_next_some().await { + SwarmEvent::OutgoingConnectionError { .. } => {} + SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => { + break + } + _ => {} + } } - } - let status = next_status(&mut client).await; - assert!(status.is_public()); - assert!(client.behaviour().public_address().is_some()); + let status = next_status(&mut client).await; + assert!(status.is_public()); + assert!(client.behaviour().public_address().is_some()); - drop(_handle); + drop(_handle); + }; + + futures::select! { + _ = run_test.fuse() => {}, + _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + } } // FIXME: flaky test #[async_std::test] -#[ignore] +// #[ignore] async fn test_throttle_peer_period() { - let mut handles = Vec::new(); - - let mut client = init_swarm(Config { - retry_interval: TEST_RETRY_INTERVAL, - refresh_interval: TEST_REFRESH_INTERVAL, - // Throttle servers so they can not be re-used for dial request. - throttle_peer_period: Duration::from_secs(1000), - boot_delay: Duration::from_millis(100), - ..Default::default() - }) - .await; - - for _ in 0..SERVER_COUNT { - let (tx, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; - client.behaviour_mut().add_server(id, Some(addr)); - handles.push(tx); - } - - client - .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) - .unwrap(); - loop { - match client.select_next_some().await { - SwarmEvent::NewListenAddr { .. } => break, - _ => {} - } - } - - let nat_status = next_status(&mut client).await; - assert!(nat_status.is_public()); - assert_eq!(client.behaviour().confidence(), 0); - - poll_and_sleep(&mut client, TEST_REFRESH_INTERVAL / 2).await; - - let mut expect_confidence = 0; + let run_test = async { + let mut handles = Vec::new(); + + let mut client = init_swarm(Config { + retry_interval: TEST_RETRY_INTERVAL, + refresh_interval: TEST_REFRESH_INTERVAL, + confidence_max: MAX_CONFIDENCE, + // Throttle servers so they can not be re-used for dial request. + throttle_peer_period: Duration::from_secs(1000), + boot_delay: Duration::ZERO, + ..Default::default() + }) + .await; - // Use each server once (minus 1 because one was already used above). - for _ in 0..SERVER_COUNT - 1 { - // Sleep until probe should be completed in background. - if expect_confidence == MAX_CONFIDENCE { - poll_and_sleep(&mut client, TEST_REFRESH_INTERVAL).await; - } else { - poll_and_sleep(&mut client, TEST_RETRY_INTERVAL).await; + for _ in 0..SERVER_COUNT { + let (tx, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); + handles.push(tx); } - if expect_confidence < MAX_CONFIDENCE { - expect_confidence += 1; + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { .. } => break, + _ => {} + } } - assert!(client.behaviour().nat_status().is_public()); - assert_eq!(client.behaviour().confidence(), expect_confidence); - } - // All servers are throttled. - while expect_confidence > 0 { - // Sleep until probe resolved in background. - if expect_confidence == MAX_CONFIDENCE { - poll_and_sleep(&mut client, TEST_REFRESH_INTERVAL).await; - } else { - poll_and_sleep(&mut client, TEST_RETRY_INTERVAL).await; - } + let nat_status = next_status(&mut client).await; + assert!(nat_status.is_public()); + assert_eq!(client.behaviour().confidence(), 0); - expect_confidence -= 1; - assert_eq!(client.behaviour().confidence(), expect_confidence); - assert!(client.behaviour().nat_status().is_public()); - } + // After are servers are used once, no qualified server is present and + // probes resolve to status unknown. + let nat_status = next_status(&mut client).await; + assert_eq!(nat_status, NatStatus::Unknown); + assert_eq!(client.behaviour().confidence(), 0); - let nat_status = next_status(&mut client).await; - assert_eq!(nat_status, NatStatus::Unknown); - assert_eq!(client.behaviour().confidence(), 0); + drop(handles) + }; - drop(handles) + futures::select! { + _ = run_test.fuse() => {}, + _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + } } #[async_std::test] async fn test_use_connected_as_server() { - let mut client = init_swarm(Config { - retry_interval: TEST_RETRY_INTERVAL, - throttle_peer_period: Duration::ZERO, - boot_delay: Duration::ZERO, - ..Default::default() - }) - .await; + let run_test = async { + let mut client = init_swarm(Config { + retry_interval: TEST_RETRY_INTERVAL, + refresh_interval: TEST_REFRESH_INTERVAL, + confidence_max: MAX_CONFIDENCE, + throttle_peer_period: Duration::ZERO, + boot_delay: Duration::ZERO, + ..Default::default() + }) + .await; - let (_handle, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; + let (_handle, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; - client - .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) - .unwrap(); + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); - client.dial(addr).unwrap(); + client.dial(addr).unwrap(); - let nat_status = loop { - match client.select_next_some().await { - SwarmEvent::Behaviour(status) => break status, - SwarmEvent::ConnectionClosed { peer_id, .. } => { - // keep connection alive - client.dial(peer_id).unwrap(); + let nat_status = loop { + match client.select_next_some().await { + SwarmEvent::Behaviour(status) => break status, + SwarmEvent::ConnectionClosed { peer_id, .. } => { + // keep connection alive + client.dial(peer_id).unwrap(); + } + _ => {} } - _ => {} - } - }; + }; - assert!(nat_status.is_public()); - assert_eq!(client.behaviour().confidence(), 0); + assert!(nat_status.is_public()); + assert_eq!(client.behaviour().confidence(), 0); - let _ = client.disconnect_peer_id(id); + let _ = client.disconnect_peer_id(id); - loop { - match client.select_next_some().await { - SwarmEvent::ConnectionClosed { peer_id, .. } if peer_id == id => break, - _ => {} + loop { + match client.select_next_some().await { + SwarmEvent::ConnectionClosed { peer_id, .. } if peer_id == id => break, + _ => {} + } } - } - let nat_status = next_status(&mut client).await; - assert_eq!(nat_status, NatStatus::Unknown); - assert_eq!(client.behaviour().confidence(), 0); + let nat_status = next_status(&mut client).await; + assert_eq!(nat_status, NatStatus::Unknown); + assert_eq!(client.behaviour().confidence(), 0); - drop(_handle); + drop(_handle); + }; + + futures::select! { + _ = run_test.fuse() => {}, + _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + } } #[async_std::test] async fn test_outbound_failure() { - let mut handles = Vec::new(); - - let mut client = init_swarm(Config { - retry_interval: TEST_RETRY_INTERVAL, - refresh_interval: TEST_REFRESH_INTERVAL, - throttle_peer_period: Duration::ZERO, - boot_delay: Duration::from_millis(100), - ..Default::default() - }) - .await; - - for _ in 0..SERVER_COUNT { - let (tx, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; - client.behaviour_mut().add_server(id, Some(addr)); - handles.push((id, tx)); - } + let run_test = async { + let mut handles = Vec::new(); + + let mut client = init_swarm(Config { + retry_interval: TEST_RETRY_INTERVAL, + refresh_interval: TEST_REFRESH_INTERVAL, + confidence_max: MAX_CONFIDENCE, + throttle_peer_period: Duration::ZERO, + boot_delay: Duration::from_millis(100), + ..Default::default() + }) + .await; - client - .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) - .unwrap(); + for _ in 0..SERVER_COUNT { + let (tx, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); + handles.push(tx); + } - loop { - match client.select_next_some().await { - SwarmEvent::NewListenAddr { .. } => break, - _ => {} + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { .. } => break, + _ => {} + } } - } - let nat_status = next_status(&mut client).await; - assert!(nat_status.is_public()); - assert_eq!(client.behaviour().confidence(), 0); + let nat_status = next_status(&mut client).await; + assert!(nat_status.is_public()); + assert_eq!(client.behaviour().confidence(), 0); - // Kill all servers apart from one. - handles.drain(1..); - // Disconnect server in case that it was used for the first probe. - let _ = client.disconnect_peer_id(handles[0].0); + // Kill all servers apart from one. + handles.drain(1..); - // Expect retry on outbound failure until one server can be reached. - loop { - match client.select_next_some().await { - SwarmEvent::OutgoingConnectionError { .. } => {} - SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_dialer() => break, - _ => {} + // Expect inbound dial indicating that we sent a dial-request to the remote. + loop { + match client.select_next_some().await { + SwarmEvent::OutgoingConnectionError { .. } => {} + SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => { + break + } + _ => {} + } } - } - // Expect inbound dial. - loop { - match client.select_next_some().await { - SwarmEvent::OutgoingConnectionError { .. } => {} - SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => break, - _ => {} - } - } + // Delay so that the server has time to send dial-response + poll_and_sleep(&mut client, Duration::from_millis(100)).await; - // Delay so that the server has time to send dial-response - poll_and_sleep(&mut client, Duration::from_millis(100)).await; + assert!(client.behaviour().confidence() > 0); + }; - assert!(client.behaviour().confidence() > 0); + futures::select! { + _ = run_test.fuse() => {}, + _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + } } From 544895258717b256e198aa33727c40f324b2ce40 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Mon, 13 Dec 2021 17:44:15 +0100 Subject: [PATCH 50/69] protocols/autonat: improve example & docs --- protocols/autonat/Cargo.toml | 2 +- protocols/autonat/examples/client.rs | 25 +++++- protocols/autonat/examples/server.rs | 114 +++++++++++++++++++++++++++ protocols/autonat/src/behaviour.rs | 38 +++++---- protocols/autonat/tests/autonat.rs | 1 - 5 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 protocols/autonat/examples/server.rs diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index f4acfa9e593..e2748cad7b7 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -33,4 +33,4 @@ structopt = "0.3" [dev-dependencies.libp2p] path = "../../" default-features = false -features = ["autonat", "dns-async-std", "identify", "mplex", "noise", "tcp-async-io", "websocket", "yamux"] \ No newline at end of file +features = ["autonat", "dns-async-std", "identify", "mplex", "noise", "tcp-async-io", "websocket", "yamux"] diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index b4b5927e4a4..282f0cd7530 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -18,17 +18,34 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +//! Basic example that combines the AutoNAT and identify protocols. +//! +//! The identify protocol informs the local peer of its external addresses, that are then send in AutoNAT dial-back +//! requests to the server. +//! +//! To run this example, follow the instructions in `examples/server` to start a server, then run in a new terminal: +//! ```sh +//! cargo run --example client -- --server-address --server-peer-id --listen_port +//! ``` +//! The `listen_port` parameter is optional and allows to set a fixed port at which the local client should listen. + use futures::prelude::*; use libp2p::autonat; use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; +use libp2p::multiaddr::Protocol; use libp2p::swarm::{Swarm, SwarmEvent}; use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId}; -use std::{error::Error, time::Duration}; +use std::error::Error; +use std::net::Ipv4Addr; +use std::time::Duration; use structopt::StructOpt; #[derive(Debug, StructOpt)] #[structopt(name = "libp2p autonat")] struct Opt { + #[structopt(long)] + listen_port: Option, + #[structopt(long)] server_address: Multiaddr, @@ -51,7 +68,11 @@ async fn main() -> Result<(), Box> { let behaviour = Behaviour::new(local_key.public()); let mut swarm = Swarm::new(transport, behaviour, local_peer_id); - swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + swarm.listen_on( + Multiaddr::empty() + .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) + .with(Protocol::Tcp(opt.listen_port.unwrap_or(0))), + )?; swarm .behaviour_mut() diff --git a/protocols/autonat/examples/server.rs b/protocols/autonat/examples/server.rs new file mode 100644 index 00000000000..0ed74e8b56c --- /dev/null +++ b/protocols/autonat/examples/server.rs @@ -0,0 +1,114 @@ +// Copyright 2021 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Basic example for a AutoNAT server that supports the /libp2p/autonat/1.0.0 and "/ipfs/0.1.0" protocols. +//! +//! To start the server run: +//! ```sh +//! cargo run --example server -- --listen_port +//! ``` +//! The `listen_port` parameter is optional and allows to set a fixed port at which the local peer should listen. + +use futures::prelude::*; +use libp2p::autonat; +use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; +use libp2p::multiaddr::Protocol; +use libp2p::swarm::{Swarm, SwarmEvent}; +use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId}; +use std::error::Error; +use std::net::Ipv4Addr; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "libp2p autonat")] +struct Opt { + #[structopt(long)] + listen_port: Option, +} + +#[async_std::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let opt = Opt::from_args(); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + println!("Local peer id: {:?}", local_peer_id); + + let transport = libp2p::development_transport(local_key.clone()).await?; + + let behaviour = Behaviour::new(local_key.public()); + + let mut swarm = Swarm::new(transport, behaviour, local_peer_id); + swarm.listen_on( + Multiaddr::empty() + .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) + .with(Protocol::Tcp(opt.listen_port.unwrap_or(0))), + )?; + + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {:?}", address), + SwarmEvent::Behaviour(event) => println!("{:?}", event), + e => println!("{:?}", e), + } + } +} + +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "Event")] +struct Behaviour { + identify: Identify, + auto_nat: autonat::Behaviour, +} + +impl Behaviour { + fn new(local_public_key: identity::PublicKey) -> Self { + Self { + identify: Identify::new(IdentifyConfig::new( + "/ipfs/0.1.0".into(), + local_public_key.clone(), + )), + auto_nat: autonat::Behaviour::new( + local_public_key.to_peer_id(), + autonat::Config::default(), + ), + } + } +} + +#[derive(Debug)] +enum Event { + AutoNat(autonat::NatStatus), + Identify(IdentifyEvent), +} + +impl From for Event { + fn from(v: IdentifyEvent) -> Self { + Self::Identify(v) + } +} + +impl From for Event { + fn from(v: autonat::NatStatus) -> Self { + Self::AutoNat(v) + } +} diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 70e0c621009..d9db61dee79 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -50,9 +50,9 @@ pub struct Config { pub timeout: Duration, // == Client Config - /// Delay on init before starting the fist probe + /// Delay on init before starting the fist probe. pub boot_delay: Duration, - /// Interval in which the NAT should be tested again if reached max confidence. + /// Interval in which the NAT should be tested again if max confidence was reached in a status. pub refresh_interval: Duration, /// Interval in which the NAT status should be re-tried if it is currently is unknown /// or max confidence was not reached yet. @@ -63,9 +63,6 @@ pub struct Config { /// Wether connected peers may be used as server for a probe, in addition to the predefined ones. pub may_use_connected: bool, /// Max confidence that can be reached in a public / private NAT status. - /// The confidence is increased each time a probe confirms the assumed status, and - /// reduced each time a different status is reported. On confidence 0 the status - /// is flipped if a different one is reported. /// Note: for [`NatStatus::Unknown`] the confidence is always 0. pub confidence_max: usize, @@ -117,6 +114,19 @@ impl From> for NatStatus { } /// Network Behaviour for AutoNAT. +/// +/// The behaviour frequently runs probes to determine whether the local peer is behind NAT and/ or a firewall, or +/// publicly reachable. +/// In a probe, a dial-back request is sent to a peer that is randomly selected from the list of fixed servers and +/// connected peers. Upon receiving a dial-back request, the remote tries to dial the included addresses. When a +/// first address was successfully dialed, a status Ok will be send back together with the dialed address. If no address +/// can be reached a dial-error is send back. +/// Based on the received response, the sender assumes themselves to be public or private. +/// The status is retried in a frequency of [`Config::retry_interval`] or [`Config::retry_interval`], depending on whether +/// enough confidence in the assumed NAT status was reached or not. +/// The confidence increases each time a probe confirms the assumed status, and decreases if a different status is reported. +/// If the confidence is 0, the status is flipped and the Behaviour will report the +/// new status in an `OutEvent`. pub struct Behaviour { // Local peer id local_peer_id: PeerId, @@ -175,7 +185,8 @@ impl Behaviour { } } - /// Address if we are public. + /// Assumed public address of the local peer. + /// Returns `None` in case of status [`NatStatus::Private`] or [`NatStatus::Unknown`]. pub fn public_address(&self) -> Option<&Multiaddr> { match &self.nat_status { NatStatus::Public(address) => Some(address), @@ -183,7 +194,7 @@ impl Behaviour { } } - /// Currently assumed NAT status. + /// Assumed NAT status. pub fn nat_status(&self) -> NatStatus { self.nat_status.clone() } @@ -194,8 +205,7 @@ impl Behaviour { } /// Add a peer to the list over servers that may be used for probes. - /// While probes normally use one of the connected peers as server, this allows to add trusted - /// peers that can be used even if they are currently not connected, in which case a connection will be + /// These peers are used for dial-request even if they are currently not connection, in which case a connection will be /// establish before sending the dial-request. pub fn add_server(&mut self, peer: PeerId, address: Option) { self.servers.push(peer); @@ -233,7 +243,7 @@ impl Behaviour { Some(*server) } - // Send a dial request to the set server or a randomly selected one. + // Send a dial-request to a randomly selected server. // Return `None` if there are no qualified servers or no dial-back addresses. fn do_probe(&mut self, addresses: Vec) -> Option { self.last_probe = Some(Instant::now()); @@ -333,6 +343,8 @@ impl Behaviour { Ok(addrs) } + // Set the delay to the next probe based on the time of our last prove + // and the specified delay. fn schedule_next_probe(&mut self, delay: Duration) { let last_probe_instant = match self.last_probe { Some(instant) => instant, @@ -525,11 +537,7 @@ impl NetworkBehaviour for Behaviour { continue; } if is_probe_ready { - let mut addresses = match self.public_address() { - Some(a) => vec![a.clone()], // Remote should try our assumed public address first. - None => Vec::new(), - }; - addresses.extend(params.external_addresses().map(|r| r.addr)); + let mut addresses: Vec<_> = params.external_addresses().map(|r| r.addr).collect(); addresses.extend(params.listened_addresses()); match self.do_probe(addresses) { diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 227b5ee6939..bafe256f55b 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -48,7 +48,6 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { async_std::task::spawn(async move { let mut swarm = init_swarm(Config { boot_delay: Duration::from_secs(60), - throttle_peer_period: Duration::ZERO, ..Default::default() }) .await; From aa06f5eb47da100951aebac91af7e5d852b9b4bc Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Tue, 14 Dec 2021 01:06:28 +0100 Subject: [PATCH 51/69] protocols/autonat/behaviour: throttle clients per period --- protocols/autonat/examples/client.rs | 2 +- protocols/autonat/src/behaviour.rs | 66 +++++++++++++++++++++------- protocols/autonat/tests/autonat.rs | 10 ++--- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index 282f0cd7530..26efcf9ca7a 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -108,7 +108,7 @@ impl Behaviour { retry_interval: Duration::from_secs(10), refresh_interval: Duration::from_secs(30), boot_delay: Duration::from_secs(5), - throttle_peer_period: Duration::ZERO, + throttle_server_period: Duration::ZERO, ..Default::default() }, ), diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index d9db61dee79..b46f35a937e 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -59,9 +59,9 @@ pub struct Config { pub retry_interval: Duration, /// Throttle period for re-using a peer as server for a dial-request. - pub throttle_peer_period: Duration, - /// Wether connected peers may be used as server for a probe, in addition to the predefined ones. - pub may_use_connected: bool, + pub throttle_server_period: Duration, + /// Use connected peers as servers for probes. + pub use_connected: bool, /// Max confidence that can be reached in a public / private NAT status. /// Note: for [`NatStatus::Unknown`] the confidence is always 0. pub confidence_max: usize, @@ -69,8 +69,12 @@ pub struct Config { //== Server Config /// Max addresses that are tried per peer. pub max_peer_addresses: usize, - /// Max total simultaneous dial-attempts. - pub throttle_global_max: usize, + /// Max total dial requests done in `[Config::throttle_clients_period`]. + pub throttle_clients_global_max: usize, + /// Max dial requests done in `[Config::throttle_clients_period`] for a peer. + pub throttle_clients_peer_max: usize, + /// Period for throttling clients requests. + pub throttle_clients_period: Duration, } impl Default for Config { @@ -80,11 +84,13 @@ impl Default for Config { boot_delay: Duration::from_secs(15), retry_interval: Duration::from_secs(90), refresh_interval: Duration::from_secs(15 * 60), - throttle_peer_period: Duration::from_secs(90), - may_use_connected: true, + throttle_server_period: Duration::from_secs(90), + use_connected: true, confidence_max: 3, max_peer_addresses: 16, - throttle_global_max: 30, + throttle_clients_global_max: 30, + throttle_clients_peer_max: 3, + throttle_clients_period: Duration::from_secs(1), } } } @@ -155,9 +161,12 @@ pub struct Behaviour { // These peers may be used as servers for dial-requests. connected: HashMap, - // Used servers in recent outbound probes that are throttled through Config::throttle_peer_period. + // Used servers in recent outbound probes that are throttled through Config::throttle_server_period. throttled_servers: Vec<(PeerId, Instant)>, + // Recent probes done for clients + throttled_clients: Vec<(PeerId, Instant)>, + last_probe: Option, pending_out_events: VecDeque<::OutEvent>, @@ -180,6 +189,7 @@ impl Behaviour { nat_status: NatStatus::Unknown, confidence: 0, throttled_servers: Vec::new(), + throttled_clients: Vec::new(), last_probe: None, pending_out_events: VecDeque::new(), } @@ -223,14 +233,14 @@ impl Behaviour { // Select a random server for the probe. fn random_server(&mut self) -> Option { // Update list of throttled servers. - let i = self - .throttled_servers - .partition_point(|(_, time)| *time + self.config.throttle_peer_period < Instant::now()); + let i = self.throttled_servers.partition_point(|(_, time)| { + *time + self.config.throttle_server_period < Instant::now() + }); self.throttled_servers.drain(..i); let mut servers: Vec<&PeerId> = self.servers.iter().collect(); - if self.config.may_use_connected { + if self.config.use_connected { servers.extend(self.connected.iter().map(|(id, _)| id)); } @@ -276,6 +286,12 @@ impl Behaviour { sender: PeerId, request: DialRequest, ) -> Result, DialResponse> { + // Update list of throttled clients. + let i = self.throttled_servers.partition_point(|(_, time)| { + *time + self.config.throttle_clients_period < Instant::now() + }); + self.throttled_clients.drain(..i); + if request.peer_id != sender { let status_text = "peer id mismatch".to_string(); log::debug!( @@ -291,7 +307,7 @@ impl Behaviour { } if self.ongoing_inbound.contains_key(&sender) { - let status_text = "too many dials".to_string(); + let status_text = "dial-back already ongoing".to_string(); log::debug!( "Reject inbound dial request from peer {}: {}.", sender, @@ -304,7 +320,7 @@ impl Behaviour { return Err(response); } - if self.ongoing_inbound.len() >= self.config.throttle_global_max { + if self.throttled_clients.len() >= self.config.throttle_clients_global_max { let status_text = "too many total dials".to_string(); log::debug!( "Reject inbound dial request from peer {}: {}.", @@ -318,6 +334,26 @@ impl Behaviour { return Err(response); } + let ongoing_for_client = self + .throttled_clients + .iter() + .filter(|(p, _)| p == &sender) + .count(); + + if ongoing_for_client >= self.config.throttle_clients_peer_max { + let status_text = "too many dials for peer".to_string(); + log::debug!( + "Reject inbound dial request from peer {}: {}.", + sender, + status_text + ); + let response = DialResponse { + result: Err(ResponseError::DialRefused), + status_text: Some(status_text), + }; + return Err(response); + } + let observed_addr = self .connected .get(&sender) diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index bafe256f55b..29654e821dd 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -105,7 +105,7 @@ async fn test_auto_probe() { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, confidence_max: MAX_CONFIDENCE, - throttle_peer_period: Duration::ZERO, + throttle_server_period: Duration::ZERO, boot_delay: Duration::ZERO, ..Default::default() }) @@ -172,7 +172,7 @@ async fn test_auto_probe() { // FIXME: flaky test #[async_std::test] // #[ignore] -async fn test_throttle_peer_period() { +async fn test_throttle_server_period() { let run_test = async { let mut handles = Vec::new(); @@ -181,7 +181,7 @@ async fn test_throttle_peer_period() { refresh_interval: TEST_REFRESH_INTERVAL, confidence_max: MAX_CONFIDENCE, // Throttle servers so they can not be re-used for dial request. - throttle_peer_period: Duration::from_secs(1000), + throttle_server_period: Duration::from_secs(1000), boot_delay: Duration::ZERO, ..Default::default() }) @@ -230,7 +230,7 @@ async fn test_use_connected_as_server() { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, confidence_max: MAX_CONFIDENCE, - throttle_peer_period: Duration::ZERO, + throttle_server_period: Duration::ZERO, boot_delay: Duration::ZERO, ..Default::default() }) @@ -290,7 +290,7 @@ async fn test_outbound_failure() { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, confidence_max: MAX_CONFIDENCE, - throttle_peer_period: Duration::ZERO, + throttle_server_period: Duration::ZERO, boot_delay: Duration::from_millis(100), ..Default::default() }) From 006771a57283493ff9f5613bdb8bf68f46fdf3b7 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Tue, 14 Dec 2021 01:13:03 +0100 Subject: [PATCH 52/69] protocols/autonat/behaviour: handle different reported public addr If we assumed to be public and a different address than our currently assumed public one is reported in a response `Ok(address)`, update our address but don't reduce or increase confidence. --- protocols/autonat/src/behaviour.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index b46f35a937e..5a1f46a034d 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -409,6 +409,11 @@ impl Behaviour { } } false + } else if reported.is_public() && self.nat_status.is_public() { + // Different address than the currently assumed public address was reported. + // Switch address, but don't report as flipped. + self.nat_status = reported; + false } else if self.confidence > 0 { // Reduce confidence but keep old status. self.confidence -= 1; From de15fc10eeae8a499a49589be34c4b56d61b6285 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Tue, 14 Dec 2021 02:18:38 +0100 Subject: [PATCH 53/69] protocols/autonat: don't reduce confidence on status Unknown --- protocols/autonat/src/behaviour.rs | 21 ++++++++++---------- protocols/autonat/tests/autonat.rs | 32 ++++++++++-------------------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 5a1f46a034d..4300c1124a3 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -131,8 +131,7 @@ impl From> for NatStatus { /// The status is retried in a frequency of [`Config::retry_interval`] or [`Config::retry_interval`], depending on whether /// enough confidence in the assumed NAT status was reached or not. /// The confidence increases each time a probe confirms the assumed status, and decreases if a different status is reported. -/// If the confidence is 0, the status is flipped and the Behaviour will report the -/// new status in an `OutEvent`. +/// If the confidence is 0, the status is flipped and the Behaviour will report the new status in an `OutEvent`. pub struct Behaviour { // Local peer id local_peer_id: PeerId, @@ -398,15 +397,15 @@ impl Behaviour { fn handle_reported_status(&mut self, reported: NatStatus) -> bool { self.schedule_next_probe(self.config.retry_interval); - if reported == self.nat_status { - if !matches!(self.nat_status, NatStatus::Unknown) { - if self.confidence < self.config.confidence_max { - self.confidence += 1; - } - // Delay with (usually longer) refresh-interval. - if self.confidence >= self.config.confidence_max { - self.schedule_next_probe(self.config.refresh_interval); - } + if matches!(reported, NatStatus::Unknown) { + false + } else if reported == self.nat_status { + if self.confidence < self.config.confidence_max { + self.confidence += 1; + } + // Delay with (usually longer) refresh-interval. + if self.confidence >= self.config.confidence_max { + self.schedule_next_probe(self.config.refresh_interval); } false } else if reported.is_public() && self.nat_status.is_public() { diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 29654e821dd..50575e9907f 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -129,12 +129,6 @@ async fn test_auto_probe() { assert_eq!(client.behaviour().confidence(), 0); assert_eq!(client.behaviour().nat_status(), NatStatus::Private); - // Test empty addresses. - client.remove_external_address(&unreachable_addr); - - let status = next_status(&mut client).await; - assert_eq!(status, NatStatus::Unknown); - client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); @@ -169,9 +163,7 @@ async fn test_auto_probe() { } } -// FIXME: flaky test #[async_std::test] -// #[ignore] async fn test_throttle_server_period() { let run_test = async { let mut handles = Vec::new(); @@ -187,7 +179,7 @@ async fn test_throttle_server_period() { }) .await; - for _ in 0..SERVER_COUNT { + for _ in 1..=MAX_CONFIDENCE { let (tx, rx) = oneshot::channel(); let (id, addr) = spawn_server(rx).await; client.behaviour_mut().add_server(id, Some(addr)); @@ -208,11 +200,12 @@ async fn test_throttle_server_period() { assert!(nat_status.is_public()); assert_eq!(client.behaviour().confidence(), 0); - // After are servers are used once, no qualified server is present and - // probes resolve to status unknown. - let nat_status = next_status(&mut client).await; - assert_eq!(nat_status, NatStatus::Unknown); - assert_eq!(client.behaviour().confidence(), 0); + // Sleep double the time that would be needed to reach max confidence + poll_and_sleep(&mut client, TEST_RETRY_INTERVAL * MAX_CONFIDENCE as u32 * 2).await; + + // Can only reach confidence n-1 with n available servers, as one probe + // is required to flip the status the first time. + assert_eq!(client.behaviour().confidence(), MAX_CONFIDENCE - 1); drop(handles) }; @@ -261,15 +254,10 @@ async fn test_use_connected_as_server() { let _ = client.disconnect_peer_id(id); - loop { - match client.select_next_some().await { - SwarmEvent::ConnectionClosed { peer_id, .. } if peer_id == id => break, - _ => {} - } - } + poll_and_sleep(&mut client, TEST_RETRY_INTERVAL * 2).await; - let nat_status = next_status(&mut client).await; - assert_eq!(nat_status, NatStatus::Unknown); + // No connected peers to send probes to; confidence can not increase. + assert!(nat_status.is_public()); assert_eq!(client.behaviour().confidence(), 0); drop(_handle); From 037a330fdd85ddfac626dd8294e85a32dbb90fc2 Mon Sep 17 00:00:00 2001 From: Elena Frank <57632201+elenaf9@users.noreply.github.com> Date: Wed, 22 Dec 2021 00:16:44 +0100 Subject: [PATCH 54/69] Apply suggestions from code review Co-authored-by: Max Inden --- protocols/autonat/src/behaviour.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 4300c1124a3..ff72cb5368c 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -54,7 +54,7 @@ pub struct Config { pub boot_delay: Duration, /// Interval in which the NAT should be tested again if max confidence was reached in a status. pub refresh_interval: Duration, - /// Interval in which the NAT status should be re-tried if it is currently is unknown + /// Interval in which the NAT status should be re-tried if it is currently unknown /// or max confidence was not reached yet. pub retry_interval: Duration, @@ -119,7 +119,7 @@ impl From> for NatStatus { } } -/// Network Behaviour for AutoNAT. +/// [`NetworkBehaviour`] for AutoNAT. /// /// The behaviour frequently runs probes to determine whether the local peer is behind NAT and/ or a firewall, or /// publicly reachable. @@ -286,7 +286,7 @@ impl Behaviour { request: DialRequest, ) -> Result, DialResponse> { // Update list of throttled clients. - let i = self.throttled_servers.partition_point(|(_, time)| { + let i = self.throttled_clients.partition_point(|(_, time)| { *time + self.config.throttle_clients_period < Instant::now() }); self.throttled_clients.drain(..i); @@ -378,7 +378,7 @@ impl Behaviour { Ok(addrs) } - // Set the delay to the next probe based on the time of our last prove + // Set the delay to the next probe based on the time of our last probe // and the specified delay. fn schedule_next_probe(&mut self, delay: Duration) { let last_probe_instant = match self.last_probe { From d194a931edf793c02a65f20ec8d22e8fa36f63c5 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Wed, 29 Dec 2021 19:36:32 +0100 Subject: [PATCH 55/69] protocols/autonat: report event for each probe --- protocols/autonat/examples/client.rs | 6 +- protocols/autonat/examples/server.rs | 6 +- protocols/autonat/src/behaviour.rs | 349 +++++++++++++++++-------- protocols/autonat/src/lib.rs | 6 +- protocols/autonat/tests/autonat.rs | 373 ++++++++++++++++++++------- 5 files changed, 534 insertions(+), 206 deletions(-) diff --git a/protocols/autonat/examples/client.rs b/protocols/autonat/examples/client.rs index 26efcf9ca7a..d99a2b07715 100644 --- a/protocols/autonat/examples/client.rs +++ b/protocols/autonat/examples/client.rs @@ -118,7 +118,7 @@ impl Behaviour { #[derive(Debug)] enum Event { - AutoNat(autonat::NatStatus), + AutoNat(autonat::Event), Identify(IdentifyEvent), } @@ -128,8 +128,8 @@ impl From for Event { } } -impl From for Event { - fn from(v: autonat::NatStatus) -> Self { +impl From for Event { + fn from(v: autonat::Event) -> Self { Self::AutoNat(v) } } diff --git a/protocols/autonat/examples/server.rs b/protocols/autonat/examples/server.rs index 0ed74e8b56c..556367fdc90 100644 --- a/protocols/autonat/examples/server.rs +++ b/protocols/autonat/examples/server.rs @@ -97,7 +97,7 @@ impl Behaviour { #[derive(Debug)] enum Event { - AutoNat(autonat::NatStatus), + AutoNat(autonat::Event), Identify(IdentifyEvent), } @@ -107,8 +107,8 @@ impl From for Event { } } -impl From for Event { - fn from(v: autonat::NatStatus) -> Self { +impl From for Event { + fn from(v: autonat::Event) -> Self { Self::AutoNat(v) } } diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index ff72cb5368c..35020c475e8 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -28,8 +28,9 @@ use libp2p_core::{ ConnectedPoint, Multiaddr, PeerId, }; use libp2p_request_response::{ - handler::RequestResponseHandlerEvent, ProtocolSupport, RequestId, RequestResponse, - RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, + handler::RequestResponseHandlerEvent, InboundFailure, OutboundFailure, ProtocolSupport, + RequestId, RequestResponse, RequestResponseConfig, RequestResponseEvent, + RequestResponseMessage, ResponseChannel, }; use libp2p_swarm::{ dial_opts::{DialOpts, PeerCondition}, @@ -109,16 +110,112 @@ impl NatStatus { } } -impl From> for NatStatus { - fn from(result: Result) -> Self { - match result { - Ok(addr) => NatStatus::Public(addr), - Err(ResponseError::DialError) => NatStatus::Private, - _ => NatStatus::Unknown, - } +/// Unique identifier for a probe. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ProbeId(usize); + +impl ProbeId { + fn next(&mut self) -> ProbeId { + let current = *self; + self.0 += 1; + current } } +/// Outbound probe failed or was aborted. +#[derive(Debug, Clone, PartialEq)] +pub enum OutboundProbeError { + /// Probe was aborted because no server is known, or all servers + /// are throttled through [`Config::throttle_server_period`]. + NoServer, + /// Probe was aborted because the local peer has no listening or + /// external addresses. + NoAddresses, + /// Sending the dial-back request or receiving a response failed. + OutboundRequest(OutboundFailure), + /// The server refused or failed to dial us. + Response(ResponseError), +} + +/// Inbound probe failed. +#[derive(Debug, Clone, PartialEq)] +pub enum InboundProbeError { + /// Receiving the dial-back request or sending a response failed. + InboundRequest(InboundFailure), + /// We refused or failed to dial the client. + Response(ResponseError), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum InboundProbeEvent { + /// A dial-back request was received from a remote peer. + Request { + probe_id: ProbeId, + /// Peer that sent the request. + peer: PeerId, + /// The addresses that will be attempted to dial. + addresses: Vec, + }, + /// A dial request to the remote was successful. + Response { + probe_id: ProbeId, + /// Peer to which the response is sent. + peer: PeerId, + address: Multiaddr, + }, + /// The inbound request failed, was rejected, or none of the remote's + /// addresses could be dialed. + Error { + probe_id: ProbeId, + /// Peer that sent the dial-back request. + peer: PeerId, + error: InboundProbeError, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum OutboundProbeEvent { + /// A dial-back request was sent to a remote peer. + Request { + probe_id: ProbeId, + /// Peer to which the request is sent. + peer: PeerId, + }, + /// The remote successfully dialed one of our addresses. + Response { + probe_id: ProbeId, + /// Id of the peer that sent the response. + peer: PeerId, + /// The address at which the remote succeeded to dial us. + address: Multiaddr, + }, + /// The outbound request failed, was rejected, or the remote could dial + /// none of our addresses. + Error { + probe_id: ProbeId, + /// Id of the peer used for the probe. + /// `None` if the probe was aborted due to no addresses or no qualified server. + peer: Option, + error: OutboundProbeError, + }, +} + +/// Event produced by [`Behaviour`]. +#[derive(Debug, Clone, PartialEq)] +pub enum Event { + /// Event on an inbound probe. + InboundProbe(InboundProbeEvent), + /// Event on an outbound probe. + OutboundProbe(OutboundProbeEvent), + /// The assumed NAT changed. + StatusChanged { + /// Former status. + old: NatStatus, + /// New status. + new: NatStatus, + }, +} + /// [`NetworkBehaviour`] for AutoNAT. /// /// The behaviour frequently runs probes to determine whether the local peer is behind NAT and/ or a firewall, or @@ -154,7 +251,10 @@ pub struct Behaviour { schedule_probe: Delay, // Ongoing inbound requests, where no response has been sent back to the remote yet. - ongoing_inbound: HashMap, ResponseChannel)>, + ongoing_inbound: HashMap, ResponseChannel)>, + + // Ongoing outbound probes and mapped to the inner request id. + ongoing_outbound: HashMap, // Connected peers with their observed address. // These peers may be used as servers for dial-requests. @@ -169,6 +269,8 @@ pub struct Behaviour { last_probe: Option, pending_out_events: VecDeque<::OutEvent>, + + probe_id: ProbeId, } impl Behaviour { @@ -184,6 +286,7 @@ impl Behaviour { config, servers: Vec::new(), ongoing_inbound: HashMap::default(), + ongoing_outbound: HashMap::default(), connected: HashMap::default(), nat_status: NatStatus::Unknown, confidence: 0, @@ -191,6 +294,7 @@ impl Behaviour { throttled_clients: Vec::new(), last_probe: None, pending_out_events: VecDeque::new(), + probe_id: ProbeId(0), } } @@ -253,18 +357,23 @@ impl Behaviour { } // Send a dial-request to a randomly selected server. - // Return `None` if there are no qualified servers or no dial-back addresses. - fn do_probe(&mut self, addresses: Vec) -> Option { + // Returns the server that is used in this probe. + // `Err` if there are no qualified servers or no dial-back addresses. + fn do_probe( + &mut self, + probe_id: ProbeId, + addresses: Vec, + ) -> Result { self.last_probe = Some(Instant::now()); if addresses.is_empty() { log::debug!("Outbound dial-back request aborted: No dial-back addresses."); - return None; + return Err(OutboundProbeError::NoAddresses); } let server = match self.random_server() { Some(s) => s, None => { log::debug!("Outbound dial-back request aborted: No qualified server."); - return None; + return Err(OutboundProbeError::NoServer); } }; let request_id = self.inner.send_request( @@ -276,7 +385,8 @@ impl Behaviour { ); self.throttled_servers.push((server, Instant::now())); log::debug!("Send dial-back request to peer {}.", server); - Some(request_id) + self.ongoing_outbound.insert(request_id, probe_id); + Ok(server) } // Validate the inbound request and collect the addresses to be dialed. @@ -284,7 +394,7 @@ impl Behaviour { &mut self, sender: PeerId, request: DialRequest, - ) -> Result, DialResponse> { + ) -> Result, (String, ResponseError)> { // Update list of throttled clients. let i = self.throttled_clients.partition_point(|(_, time)| { *time + self.config.throttle_clients_period < Instant::now() @@ -293,44 +403,17 @@ impl Behaviour { if request.peer_id != sender { let status_text = "peer id mismatch".to_string(); - log::debug!( - "Reject inbound dial request from peer {}: {}.", - sender, - status_text - ); - let response = DialResponse { - result: Err(ResponseError::BadRequest), - status_text: Some(status_text), - }; - return Err(response); + return Err((status_text, ResponseError::BadRequest)); } if self.ongoing_inbound.contains_key(&sender) { let status_text = "dial-back already ongoing".to_string(); - log::debug!( - "Reject inbound dial request from peer {}: {}.", - sender, - status_text - ); - let response = DialResponse { - result: Err(ResponseError::DialRefused), - status_text: Some(status_text), - }; - return Err(response); + return Err((status_text, ResponseError::DialRefused)); } if self.throttled_clients.len() >= self.config.throttle_clients_global_max { let status_text = "too many total dials".to_string(); - log::debug!( - "Reject inbound dial request from peer {}: {}.", - sender, - status_text - ); - let response = DialResponse { - result: Err(ResponseError::DialRefused), - status_text: Some(status_text), - }; - return Err(response); + return Err((status_text, ResponseError::DialRefused)); } let ongoing_for_client = self @@ -341,16 +424,7 @@ impl Behaviour { if ongoing_for_client >= self.config.throttle_clients_peer_max { let status_text = "too many dials for peer".to_string(); - log::debug!( - "Reject inbound dial request from peer {}: {}.", - sender, - status_text - ); - let response = DialResponse { - result: Err(ResponseError::DialRefused), - status_text: Some(status_text), - }; - return Err(response); + return Err((status_text, ResponseError::DialRefused)); } let observed_addr = self @@ -363,16 +437,7 @@ impl Behaviour { if addrs.is_empty() { let status_text = "no dialable addresses".to_string(); - log::debug!( - "Reject inbound dial request from peer {}: {}.", - sender, - status_text - ); - let response = DialResponse { - result: Err(ResponseError::DialError), - status_text: Some(status_text), - }; - return Err(response); + return Err((status_text, ResponseError::DialError)); } Ok(addrs) @@ -393,13 +458,36 @@ impl Behaviour { } // Adapt current confidence and NAT status to the status reported by the latest probe. - // Return whether the currently assumed status was flipped. - fn handle_reported_status(&mut self, reported: NatStatus) -> bool { + fn handle_reported_status( + &mut self, + probe_id: ProbeId, + peer: Option, + result: Result, + ) { self.schedule_next_probe(self.config.retry_interval); - if matches!(reported, NatStatus::Unknown) { - false - } else if reported == self.nat_status { + let reported_status = match result { + Ok(ref addr) => NatStatus::Public(addr.clone()), + Err(OutboundProbeError::Response(ResponseError::DialError)) => NatStatus::Private, + _ => NatStatus::Unknown, + }; + let event = match result { + Ok(address) => OutboundProbeEvent::Response { + probe_id, + peer: peer.unwrap(), + address, + }, + Err(error) => OutboundProbeEvent::Error { + probe_id, + peer, + error, + }, + }; + self.pending_out_events + .push_back(Event::OutboundProbe(event)); + + if matches!(reported_status, NatStatus::Unknown) { + } else if reported_status == self.nat_status { if self.confidence < self.config.confidence_max { self.confidence += 1; } @@ -407,31 +495,33 @@ impl Behaviour { if self.confidence >= self.config.confidence_max { self.schedule_next_probe(self.config.refresh_interval); } - false - } else if reported.is_public() && self.nat_status.is_public() { + } else if reported_status.is_public() && self.nat_status.is_public() { // Different address than the currently assumed public address was reported. // Switch address, but don't report as flipped. - self.nat_status = reported; - false + self.nat_status = reported_status; } else if self.confidence > 0 { // Reduce confidence but keep old status. self.confidence -= 1; - false } else { log::debug!( "Flipped assumed NAT status from {:?} to {:?}", self.nat_status, - reported + reported_status ); - self.nat_status = reported; - true + let old_status = self.nat_status.clone(); + self.nat_status = reported_status; + let event = Event::StatusChanged { + old: old_status, + new: self.nat_status.clone(), + }; + self.pending_out_events.push_back(event); } } } impl NetworkBehaviour for Behaviour { type ProtocolsHandler = as NetworkBehaviour>::ProtocolsHandler; - type OutEvent = NatStatus; + type OutEvent = Event; fn inject_connection_established( &mut self, @@ -447,7 +537,7 @@ impl NetworkBehaviour for Behaviour { match endpoint { ConnectedPoint::Dialer { address } => { - if let Some((addrs, _)) = self.ongoing_inbound.get(peer) { + if let Some((_, addrs, _)) = self.ongoing_inbound.get(peer) { // Check if the dialed address was among the requested addresses. if addrs.contains(address) { log::debug!( @@ -456,12 +546,19 @@ impl NetworkBehaviour for Behaviour { address ); - let channel = self.ongoing_inbound.remove(peer).unwrap().1; + let (probe_id, _, channel) = self.ongoing_inbound.remove(peer).unwrap(); let response = DialResponse { result: Ok(address.clone()), status_text: None, }; let _ = self.inner.send_response(channel, response); + let event = InboundProbeEvent::Response { + probe_id, + peer: *peer, + address: address.clone(), + }; + self.pending_out_events + .push_back(Event::InboundProbe(event)); } } } @@ -480,23 +577,31 @@ impl NetworkBehaviour for Behaviour { fn inject_dial_failure( &mut self, - peer_id: Option, + peer: Option, handler: Self::ProtocolsHandler, error: &DialError, ) { - self.inner.inject_dial_failure(peer_id, handler, error); - if let Some((_, channel)) = peer_id.and_then(|p| self.ongoing_inbound.remove(&p)) { + self.inner.inject_dial_failure(peer, handler, error); + if let Some((probe_id, _, channel)) = peer.and_then(|p| self.ongoing_inbound.remove(&p)) { log::debug!( "Dial-back to peer {} failed with error {:?}.", - peer_id.unwrap(), + peer.unwrap(), error ); - + let response_error = ResponseError::DialError; let response = DialResponse { - result: Err(ResponseError::DialError), + result: Err(response_error.clone()), status_text: Some("dial failed".to_string()), }; let _ = self.inner.send_response(channel, response); + + let event = InboundProbeEvent::Error { + probe_id, + peer: peer.expect("PeerId is present."), + error: InboundProbeError::Response(response_error), + }; + self.pending_out_events + .push_back(Event::InboundProbe(event)); } } @@ -580,14 +685,15 @@ impl NetworkBehaviour for Behaviour { let mut addresses: Vec<_> = params.external_addresses().map(|r| r.addr).collect(); addresses.extend(params.listened_addresses()); - match self.do_probe(addresses) { - Some(_) => {} - None => { - let nat_status = NatStatus::Unknown; - let has_flipped = self.handle_reported_status(nat_status); - if has_flipped { - self.pending_out_events.push_back(self.nat_status.clone()); - } + let probe_id = self.probe_id.next(); + match self.do_probe(probe_id, addresses) { + Ok(peer) => { + let event = OutboundProbeEvent::Request { probe_id, peer }; + self.pending_out_events + .push_back(Event::OutboundProbe(event)); + } + Err(e) => { + self.handle_reported_status(probe_id, None, Err(e)); } } } @@ -603,10 +709,21 @@ impl NetworkBehaviour for Behaviour { request, channel, } => { + let probe_id = self.probe_id.next(); match self.resolve_inbound_request(peer, request) { Ok(addrs) => { log::debug!("Inbound dial request from Peer {} with dial-back addresses {:?}.", peer, addrs); - self.ongoing_inbound.insert(peer, (addrs.clone(), channel)); + self.ongoing_inbound + .insert(peer, (probe_id, addrs.clone(), channel)); + + let event = InboundProbeEvent::Request { + probe_id, + peer, + addresses: addrs.clone(), + }; + self.pending_out_events + .push_back(Event::InboundProbe(event)); + return Poll::Ready(NetworkBehaviourAction::Dial { opts: DialOpts::peer_id(peer) .condition(PeerCondition::Always) @@ -615,12 +732,33 @@ impl NetworkBehaviour for Behaviour { handler: self.inner.new_handler(), }); } - Err(response) => { - let _ = self.inner.send_response(channel, response); + Err((status_text, error)) => { + log::debug!( + "Reject inbound dial request from peer {}: {}.", + peer, + status_text + ); + + let response = DialResponse { + result: Err(error.clone()), + status_text: Some(status_text), + }; + let _ = self.inner.send_response(channel, response.clone()); + + let event = InboundProbeEvent::Error { + probe_id, + peer, + error: InboundProbeError::Response(error), + }; + self.pending_out_events + .push_back(Event::InboundProbe(event)); } } } - RequestResponseMessage::Response { response, .. } => { + RequestResponseMessage::Response { + response, + request_id, + } => { log::debug!("Outbound dial-back request returned {:?}.", response); let mut report_addr = None; if let Ok(ref addr) = response.result { @@ -636,11 +774,12 @@ impl NetworkBehaviour for Behaviour { }); } } - let reported = response.result.into(); - let has_flipped = self.handle_reported_status(reported); - if has_flipped { - self.pending_out_events.push_back(self.nat_status.clone()); - } + let probe_id = self + .ongoing_outbound + .remove(&request_id) + .expect("RequestId exists."); + let result = response.result.map_err(OutboundProbeError::Response); + self.handle_reported_status(probe_id, Some(peer), result); if let Some(action) = report_addr { return Poll::Ready(action); } diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index aa9387b74ce..3e4fa9ae920 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -23,9 +23,13 @@ mod behaviour; mod protocol; pub use self::{ - behaviour::{Behaviour, Config, NatStatus}, + behaviour::{ + Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, NatStatus, + OutboundProbeError, OutboundProbeEvent, ProbeId, + }, protocol::ResponseError, }; +pub use libp2p_request_response::{InboundFailure, OutboundFailure}; mod structs_proto { include!(concat!(env!("OUT_DIR"), "/structs.rs")); diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index 50575e9907f..ef2da985b27 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -18,8 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use async_std::task::sleep; -use futures::{channel::oneshot, select, FutureExt, StreamExt}; +use futures::{channel::oneshot, FutureExt, StreamExt}; use futures_timer::Delay; use libp2p::{ development_transport, @@ -27,13 +26,14 @@ use libp2p::{ swarm::{AddressScore, Swarm, SwarmEvent}, Multiaddr, PeerId, }; -use libp2p_autonat::{Behaviour, Config, NatStatus}; +use libp2p_autonat::{ + Behaviour, Config, Event, NatStatus, OutboundProbeError, OutboundProbeEvent, ResponseError, +}; use std::time::Duration; -const SERVER_COUNT: usize = 10; const MAX_CONFIDENCE: usize = 3; -const TEST_RETRY_INTERVAL: Duration = Duration::from_millis(50); -const TEST_REFRESH_INTERVAL: Duration = Duration::from_millis(100); +const TEST_RETRY_INTERVAL: Duration = Duration::from_secs(1); +const TEST_REFRESH_INTERVAL: Duration = Duration::from_secs(2); async fn init_swarm(config: Config) -> Swarm { let keypair = Keypair::generate_ed25519(); @@ -48,6 +48,7 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { async_std::task::spawn(async move { let mut swarm = init_swarm(Config { boot_delay: Duration::from_secs(60), + throttle_clients_peer_max: usize::MAX, ..Default::default() }) .await; @@ -74,30 +75,17 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { rx.await.unwrap() } -async fn next_status(client: &mut Swarm) -> NatStatus { +async fn next_event(client: &mut Swarm) -> Event { loop { match client.select_next_some().await { - SwarmEvent::Behaviour(nat_status) => { - break nat_status; + SwarmEvent::Behaviour(event) => { + break event; } _ => {} } } } -// Await a delay while still driving the swarm. -async fn poll_and_sleep(client: &mut Swarm, duration: Duration) { - let poll = async { - loop { - let _ = client.next().await; - } - }; - select! { - _ = poll.fuse()=> {}, - _ = sleep(duration).fuse() => {}, - } -} - #[async_std::test] async fn test_auto_probe() { let run_test = async { @@ -112,22 +100,66 @@ async fn test_auto_probe() { .await; let (_handle, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; - client.behaviour_mut().add_server(id, Some(addr)); + let (server_id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(server_id, Some(addr)); // Initial status should be unknown. assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); assert!(client.behaviour().public_address().is_none()); assert_eq!(client.behaviour().confidence(), 0); + // Test no listening addresses + match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Error { peer, error, .. }) => { + assert!(peer.is_none()); + assert_eq!(error, OutboundProbeError::NoAddresses); + } + other => panic!("Unexpected Event: {:?}", other), + } + + // Test Private NAT Status + // Artificially add a faulty address. let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); - let status = next_status(&mut client).await; - assert_eq!(status, NatStatus::Private); + let id = match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Request { probe_id, peer }) => { + assert_eq!(peer, server_id); + probe_id + } + other => panic!("Unexpected Event: {:?}", other), + }; + + match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Error { + probe_id, + peer, + error, + }) => { + assert_eq!(peer.unwrap(), server_id); + assert_eq!(probe_id, id); + assert_eq!( + error, + OutboundProbeError::Response(ResponseError::DialError) + ); + } + other => panic!("Unexpected Event: {:?}", other), + } + + match next_event(&mut client).await { + Event::StatusChanged { old, new } => { + assert_eq!(old, NatStatus::Unknown); + assert_eq!(new, NatStatus::Private); + } + other => panic!("Unexpected Event: {:?}", other), + } + assert_eq!(client.behaviour().confidence(), 0); assert_eq!(client.behaviour().nat_status(), NatStatus::Private); + assert!(client.behaviour().public_address().is_none()); + + // Test new public listening address client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) @@ -139,19 +171,48 @@ async fn test_auto_probe() { } } + let id = match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Request { probe_id, peer }) => { + assert_eq!(peer, server_id); + probe_id + } + other => panic!("Unexpected Event: {:?}", other), + }; + // Expect inbound dial from server. loop { match client.select_next_some().await { - SwarmEvent::OutgoingConnectionError { .. } => {} - SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => { - break + SwarmEvent::ConnectionEstablished { + endpoint, peer_id, .. + } if endpoint.is_listener() => { + assert_eq!(peer_id, server_id); + break; } - _ => {} + SwarmEvent::IncomingConnection { .. } | SwarmEvent::NewListenAddr { .. } => {} + _ => panic!("Unexpected Swarm Event"), + } + } + + match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Response { probe_id, peer, .. }) => { + assert_eq!(peer, server_id); + assert_eq!(probe_id, id); + } + other => panic!("Unexpected Event: {:?}", other), + } + + // Expect to flip status to public + match next_event(&mut client).await { + Event::StatusChanged { old, new } => { + assert_eq!(old, NatStatus::Private); + assert!(matches!(new, NatStatus::Public(_))); + assert!(new.is_public()); } + other => panic!("Unexpected Event: {:?}", other), } - let status = next_status(&mut client).await; - assert!(status.is_public()); + assert_eq!(client.behaviour().confidence(), 0); + assert!(client.behaviour().nat_status().is_public()); assert!(client.behaviour().public_address().is_some()); drop(_handle); @@ -159,33 +220,122 @@ async fn test_auto_probe() { futures::select! { _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") } } #[async_std::test] -async fn test_throttle_server_period() { +async fn test_confidence() { let run_test = async { - let mut handles = Vec::new(); + let mut client = init_swarm(Config { + retry_interval: TEST_RETRY_INTERVAL, + refresh_interval: TEST_REFRESH_INTERVAL, + confidence_max: MAX_CONFIDENCE, + throttle_server_period: Duration::ZERO, + boot_delay: Duration::from_millis(100), + ..Default::default() + }) + .await; + let (_handle, rx) = oneshot::channel(); + let (server_id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(server_id, Some(addr)); + + // Randomly test either for public or for private status the confidence. + let test_public = rand::random::(); + if test_public { + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { .. } => break, + _ => {} + } + } + } else { + let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); + client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite); + } + + for i in 0..MAX_CONFIDENCE + 1 { + let id = match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Request { probe_id, peer }) => { + assert_eq!(peer, server_id); + probe_id + } + other => panic!("Unexpected Event: {:?}", other), + }; + + match next_event(&mut client).await { + Event::OutboundProbe(event) => { + let (peer, probe_id) = match event { + OutboundProbeEvent::Response { probe_id, peer, .. } if test_public => { + (peer, probe_id) + } + OutboundProbeEvent::Error { + probe_id, + peer, + error, + } if !test_public => { + assert_eq!( + error, + OutboundProbeError::Response(ResponseError::DialError) + ); + (peer.unwrap(), probe_id) + } + other => panic!("Unexpected Outbound Event: {:?}", other), + }; + assert_eq!(peer, server_id); + assert_eq!(probe_id, id); + } + other => panic!("Unexpected Event: {:?}", other), + } + + // Confidence should increase each iteration up to MAX_CONFIDENCE + let expect_confidence = if i <= MAX_CONFIDENCE { + i + } else { + MAX_CONFIDENCE + }; + assert_eq!(client.behaviour().confidence(), expect_confidence); + assert_eq!(client.behaviour().nat_status().is_public(), test_public); + + // Expect status to flip after first probe + if i == 0 { + match next_event(&mut client).await { + Event::StatusChanged { old, new } => { + assert_eq!(old, NatStatus::Unknown); + assert_eq!(new.is_public(), test_public); + } + other => panic!("Unexpected Event: {:?}", other), + } + } + } + + drop(_handle); + }; + + futures::select! { + _ = run_test.fuse() => {}, + _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") + } +} + +#[async_std::test] +async fn test_throttle_server_period() { + let run_test = async { let mut client = init_swarm(Config { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, confidence_max: MAX_CONFIDENCE, // Throttle servers so they can not be re-used for dial request. throttle_server_period: Duration::from_secs(1000), - boot_delay: Duration::ZERO, + boot_delay: Duration::from_millis(100), ..Default::default() }) .await; - for _ in 1..=MAX_CONFIDENCE { - let (tx, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; - client.behaviour_mut().add_server(id, Some(addr)); - handles.push(tx); - } - client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); @@ -196,23 +346,43 @@ async fn test_throttle_server_period() { } } - let nat_status = next_status(&mut client).await; - assert!(nat_status.is_public()); + let (_handle, rx) = oneshot::channel(); + let (id, addr) = spawn_server(rx).await; + client.behaviour_mut().add_server(id, Some(addr)); + + // First probe should be successful and flip status to public. + loop { + match next_event(&mut client).await { + Event::StatusChanged { old, new } => { + assert_eq!(old, NatStatus::Unknown); + assert!(new.is_public()); + break; + } + Event::OutboundProbe(OutboundProbeEvent::Request { .. }) => {} + Event::OutboundProbe(OutboundProbeEvent::Response { .. }) => {} + other => panic!("Unexpected Event: {:?}", other), + } + } + assert_eq!(client.behaviour().confidence(), 0); - // Sleep double the time that would be needed to reach max confidence - poll_and_sleep(&mut client, TEST_RETRY_INTERVAL * MAX_CONFIDENCE as u32 * 2).await; + // Expect following probe to fail because server is throttled - // Can only reach confidence n-1 with n available servers, as one probe - // is required to flip the status the first time. - assert_eq!(client.behaviour().confidence(), MAX_CONFIDENCE - 1); + match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Error { peer, error, .. }) => { + assert!(peer.is_none()); + assert_eq!(error, OutboundProbeError::NoServer); + } + other => panic!("Unexpected Event: {:?}", other), + } + assert_eq!(client.behaviour().confidence(), 0); - drop(handles) + drop(_handle) }; futures::select! { _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") } } @@ -224,13 +394,13 @@ async fn test_use_connected_as_server() { refresh_interval: TEST_REFRESH_INTERVAL, confidence_max: MAX_CONFIDENCE, throttle_server_period: Duration::ZERO, - boot_delay: Duration::ZERO, + boot_delay: Duration::from_millis(100), ..Default::default() }) .await; let (_handle, rx) = oneshot::channel(); - let (id, addr) = spawn_server(rx).await; + let (server_id, addr) = spawn_server(rx).await; client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) @@ -238,41 +408,43 @@ async fn test_use_connected_as_server() { client.dial(addr).unwrap(); - let nat_status = loop { - match client.select_next_some().await { - SwarmEvent::Behaviour(status) => break status, - SwarmEvent::ConnectionClosed { peer_id, .. } => { - // keep connection alive - client.dial(peer_id).unwrap(); - } - _ => {} + // await connection + loop { + if let SwarmEvent::ConnectionEstablished { peer_id, .. } = + client.select_next_some().await + { + assert_eq!(peer_id, server_id); + break; } - }; - - assert!(nat_status.is_public()); - assert_eq!(client.behaviour().confidence(), 0); - - let _ = client.disconnect_peer_id(id); + } - poll_and_sleep(&mut client, TEST_RETRY_INTERVAL * 2).await; + match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Request { peer, .. }) => { + assert_eq!(peer, server_id); + } + other => panic!("Unexpected Event: {:?}", other), + } - // No connected peers to send probes to; confidence can not increase. - assert!(nat_status.is_public()); - assert_eq!(client.behaviour().confidence(), 0); + match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Response { peer, .. }) => { + assert_eq!(peer, server_id); + } + other => panic!("Unexpected Event: {:?}", other), + } drop(_handle); }; futures::select! { _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") } } #[async_std::test] async fn test_outbound_failure() { let run_test = async { - let mut handles = Vec::new(); + let mut servers = Vec::new(); let mut client = init_swarm(Config { retry_interval: TEST_RETRY_INTERVAL, @@ -284,11 +456,11 @@ async fn test_outbound_failure() { }) .await; - for _ in 0..SERVER_COUNT { + for _ in 0..5 { let (tx, rx) = oneshot::channel(); let (id, addr) = spawn_server(rx).await; client.behaviour_mut().add_server(id, Some(addr)); - handles.push(tx); + servers.push((id, tx)); } client @@ -301,33 +473,46 @@ async fn test_outbound_failure() { _ => {} } } - - let nat_status = next_status(&mut client).await; - assert!(nat_status.is_public()); - assert_eq!(client.behaviour().confidence(), 0); - - // Kill all servers apart from one. - handles.drain(1..); - - // Expect inbound dial indicating that we sent a dial-request to the remote. + // First probe should be successful and flip status to public. loop { - match client.select_next_some().await { - SwarmEvent::OutgoingConnectionError { .. } => {} - SwarmEvent::ConnectionEstablished { endpoint, .. } if endpoint.is_listener() => { - break + match next_event(&mut client).await { + Event::StatusChanged { old, new } => { + assert_eq!(old, NatStatus::Unknown); + assert!(new.is_public()); + break; } - _ => {} + Event::OutboundProbe(OutboundProbeEvent::Request { .. }) => {} + Event::OutboundProbe(OutboundProbeEvent::Response { .. }) => {} + other => panic!("Unexpected Event: {:?}", other), } } - // Delay so that the server has time to send dial-response - poll_and_sleep(&mut client, Duration::from_millis(100)).await; + let inactive = servers.split_off(1); + // Drop the handles of the inactive servers to kill them. + let inactive_ids: Vec<_> = inactive.into_iter().map(|(id, _handle)| id).collect(); - assert!(client.behaviour().confidence() > 0); + // Expect to retry on outbound failure + loop { + match next_event(&mut client).await { + Event::OutboundProbe(OutboundProbeEvent::Request { .. }) => {} + Event::OutboundProbe(OutboundProbeEvent::Response { peer, .. }) => { + assert_eq!(peer, servers[0].0); + break; + } + Event::OutboundProbe(OutboundProbeEvent::Error { + peer: Some(peer), + error: OutboundProbeError::OutboundRequest(_), + .. + }) => { + assert!(inactive_ids.contains(&peer)); + } + other => panic!("Unexpected Event: {:?}", other), + } + } }; futures::select! { _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(30)).fuse() => panic!("test timed out") + _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") } } From 7c8cb0b265e340efc350046eb103dc94b8d8c416 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Wed, 29 Dec 2021 19:53:17 +0100 Subject: [PATCH 56/69] protocols/autonat: apply review comments --- protocols/autonat/src/behaviour.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 35020c475e8..f4232f7a915 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -37,6 +37,7 @@ use libp2p_swarm::{ AddressScore, DialError, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; +use rand::{seq::SliceRandom, thread_rng}; use std::{ collections::{HashMap, HashSet, VecDeque}, iter, @@ -50,7 +51,7 @@ pub struct Config { /// Timeout for requests. pub timeout: Duration, - // == Client Config + // Client Config /// Delay on init before starting the fist probe. pub boot_delay: Duration, /// Interval in which the NAT should be tested again if max confidence was reached in a status. @@ -67,7 +68,7 @@ pub struct Config { /// Note: for [`NatStatus::Unknown`] the confidence is always 0. pub confidence_max: usize, - //== Server Config + // Server Config /// Max addresses that are tried per peer. pub max_peer_addresses: usize, /// Max total dial requests done in `[Config::throttle_clients_period`]. @@ -349,11 +350,7 @@ impl Behaviour { servers.retain(|s| !self.throttled_servers.iter().any(|(id, _)| s == &id)); - if servers.is_empty() { - return None; - } - let server = servers[rand::random::() % servers.len()]; - Some(*server) + servers.choose(&mut thread_rng()).map(|&&p| p) } // Send a dial-request to a randomly selected server. @@ -416,13 +413,13 @@ impl Behaviour { return Err((status_text, ResponseError::DialRefused)); } - let ongoing_for_client = self + let throttled_for_client = self .throttled_clients .iter() .filter(|(p, _)| p == &sender) .count(); - if ongoing_for_client >= self.config.throttle_clients_peer_max { + if throttled_for_client >= self.config.throttle_clients_peer_max { let status_text = "too many dials for peer".to_string(); return Err((status_text, ResponseError::DialRefused)); } From dbfc59e1e76076f6215f1ebab30b04d372dc23ed Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Wed, 29 Dec 2021 23:10:01 +0100 Subject: [PATCH 57/69] protocols/autonat/behaviour: clean Behaviour::poll --- protocols/autonat/src/behaviour.rs | 245 ++++++++++++++++------------- protocols/autonat/tests/autonat.rs | 4 + 2 files changed, 138 insertions(+), 111 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index f4232f7a915..7bdfe003541 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -111,6 +111,16 @@ impl NatStatus { } } +impl From> for NatStatus { + fn from(result: Result) -> Self { + match result { + Ok(addr) => NatStatus::Public(addr), + Err(ResponseError::DialError) => NatStatus::Private, + _ => NatStatus::Unknown, + } + } +} + /// Unique identifier for a probe. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ProbeId(usize); @@ -355,7 +365,7 @@ impl Behaviour { // Send a dial-request to a randomly selected server. // Returns the server that is used in this probe. - // `Err` if there are no qualified servers or no dial-back addresses. + // `Err` if there are no qualified servers or no addresses. fn do_probe( &mut self, probe_id: ProbeId, @@ -455,36 +465,15 @@ impl Behaviour { } // Adapt current confidence and NAT status to the status reported by the latest probe. - fn handle_reported_status( - &mut self, - probe_id: ProbeId, - peer: Option, - result: Result, - ) { + // Return the old status if it flipped. + fn handle_reported_status(&mut self, reported_status: NatStatus) -> Option { self.schedule_next_probe(self.config.retry_interval); - let reported_status = match result { - Ok(ref addr) => NatStatus::Public(addr.clone()), - Err(OutboundProbeError::Response(ResponseError::DialError)) => NatStatus::Private, - _ => NatStatus::Unknown, - }; - let event = match result { - Ok(address) => OutboundProbeEvent::Response { - probe_id, - peer: peer.unwrap(), - address, - }, - Err(error) => OutboundProbeEvent::Error { - probe_id, - peer, - error, - }, - }; - self.pending_out_events - .push_back(Event::OutboundProbe(event)); - if matches!(reported_status, NatStatus::Unknown) { - } else if reported_status == self.nat_status { + return None; + } + + if reported_status == self.nat_status { if self.confidence < self.config.confidence_max { self.confidence += 1; } @@ -492,27 +481,31 @@ impl Behaviour { if self.confidence >= self.config.confidence_max { self.schedule_next_probe(self.config.refresh_interval); } - } else if reported_status.is_public() && self.nat_status.is_public() { + return None; + } + + if reported_status.is_public() && self.nat_status.is_public() { // Different address than the currently assumed public address was reported. // Switch address, but don't report as flipped. self.nat_status = reported_status; - } else if self.confidence > 0 { + return None; + } + if self.confidence > 0 { // Reduce confidence but keep old status. self.confidence -= 1; - } else { - log::debug!( - "Flipped assumed NAT status from {:?} to {:?}", - self.nat_status, - reported_status - ); - let old_status = self.nat_status.clone(); - self.nat_status = reported_status; - let event = Event::StatusChanged { - old: old_status, - new: self.nat_status.clone(), - }; - self.pending_out_events.push_back(event); + return None; } + + log::debug!( + "Flipped assumed NAT status from {:?} to {:?}", + self.nat_status, + reported_status + ); + + let old_status = self.nat_status.clone(); + self.nat_status = reported_status; + + Some(old_status) } } @@ -549,13 +542,14 @@ impl NetworkBehaviour for Behaviour { status_text: None, }; let _ = self.inner.send_response(channel, response); - let event = InboundProbeEvent::Response { - probe_id, - peer: *peer, - address: address.clone(), - }; - self.pending_out_events - .push_back(Event::InboundProbe(event)); + + self.pending_out_events.push_back(Event::InboundProbe( + InboundProbeEvent::Response { + probe_id, + peer: *peer, + address: address.clone(), + }, + )); } } } @@ -592,13 +586,12 @@ impl NetworkBehaviour for Behaviour { }; let _ = self.inner.send_response(channel, response); - let event = InboundProbeEvent::Error { - probe_id, - peer: peer.expect("PeerId is present."), - error: InboundProbeError::Response(response_error), - }; self.pending_out_events - .push_back(Event::InboundProbe(event)); + .push_back(Event::InboundProbe(InboundProbeEvent::Error { + probe_id, + peer: peer.expect("PeerId is present."), + error: InboundProbeError::Response(response_error), + })); } } @@ -671,32 +664,8 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, params: &mut impl PollParameters, ) -> Poll> { - let mut is_probe_ready = false; loop { - if self.schedule_probe.poll_unpin(cx).is_ready() { - is_probe_ready = true; - self.schedule_probe.reset(self.config.retry_interval); - continue; - } - if is_probe_ready { - let mut addresses: Vec<_> = params.external_addresses().map(|r| r.addr).collect(); - addresses.extend(params.listened_addresses()); - - let probe_id = self.probe_id.next(); - match self.do_probe(probe_id, addresses) { - Ok(peer) => { - let event = OutboundProbeEvent::Request { probe_id, peer }; - self.pending_out_events - .push_back(Event::OutboundProbe(event)); - } - Err(e) => { - self.handle_reported_status(probe_id, None, Err(e)); - } - } - } - if let Some(event) = self.pending_out_events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); - } + let mut inner_pending = false; match self.inner.poll(cx, params) { Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::Message { peer, message }, @@ -710,16 +679,17 @@ impl NetworkBehaviour for Behaviour { match self.resolve_inbound_request(peer, request) { Ok(addrs) => { log::debug!("Inbound dial request from Peer {} with dial-back addresses {:?}.", peer, addrs); + self.ongoing_inbound .insert(peer, (probe_id, addrs.clone(), channel)); - let event = InboundProbeEvent::Request { - probe_id, - peer, - addresses: addrs.clone(), - }; - self.pending_out_events - .push_back(Event::InboundProbe(event)); + self.pending_out_events.push_back(Event::InboundProbe( + InboundProbeEvent::Request { + probe_id, + peer, + addresses: addrs.clone(), + }, + )); return Poll::Ready(NetworkBehaviourAction::Dial { opts: DialOpts::peer_id(peer) @@ -742,13 +712,13 @@ impl NetworkBehaviour for Behaviour { }; let _ = self.inner.send_response(channel, response.clone()); - let event = InboundProbeEvent::Error { - probe_id, - peer, - error: InboundProbeError::Response(error), - }; - self.pending_out_events - .push_back(Event::InboundProbe(event)); + self.pending_out_events.push_back(Event::InboundProbe( + InboundProbeEvent::Error { + probe_id, + peer, + error: InboundProbeError::Response(error), + }, + )); } } } @@ -757,29 +727,48 @@ impl NetworkBehaviour for Behaviour { request_id, } => { log::debug!("Outbound dial-back request returned {:?}.", response); - let mut report_addr = None; - if let Ok(ref addr) = response.result { + let probe_id = self + .ongoing_outbound + .remove(&request_id) + .expect("RequestId exists."); + + let event = match response.result.clone() { + Ok(address) => OutboundProbeEvent::Response { + probe_id, + peer, + address, + }, + Err(e) => OutboundProbeEvent::Error { + probe_id, + peer: Some(peer), + error: OutboundProbeError::Response(e), + }, + }; + self.pending_out_events + .push_back(Event::OutboundProbe(event)); + + if let Some(old) = + self.handle_reported_status(response.result.clone().into()) + { + self.pending_out_events.push_back(Event::StatusChanged { + old, + new: self.nat_status.clone(), + }); + } + + if let Ok(address) = response.result { // Update observed address score if it is finite. let score = params .external_addresses() - .find_map(|r| (&r.addr == addr).then(|| r.score)) + .find_map(|r| (r.addr == address).then(|| r.score)) .unwrap_or(AddressScore::Finite(0)); if let AddressScore::Finite(finite_score) = score { - report_addr = Some(NetworkBehaviourAction::ReportObservedAddr { - address: addr.clone(), + return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { + address, score: AddressScore::Finite(finite_score + 1), }); } } - let probe_id = self - .ongoing_outbound - .remove(&request_id) - .expect("RequestId exists."); - let result = response.result.map_err(OutboundProbeError::Response); - self.handle_reported_status(probe_id, Some(peer), result); - if let Some(action) = report_addr { - return Poll::Ready(action); - } } }, Poll::Ready(NetworkBehaviourAction::GenerateEvent( @@ -793,7 +782,7 @@ impl NetworkBehaviour for Behaviour { error, peer ); - is_probe_ready = true; + self.schedule_probe.reset(Duration::ZERO); } Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::InboundFailure { peer, .. }, @@ -801,7 +790,41 @@ impl NetworkBehaviour for Behaviour { self.ongoing_inbound.remove(&peer); } Poll::Ready(action) => return Poll::Ready(action.map_out(|_| unreachable!())), - Poll::Pending => return Poll::Pending, + Poll::Pending => inner_pending = true, + } + + if self.schedule_probe.poll_unpin(cx).is_ready() { + self.schedule_probe.reset(self.config.retry_interval); + + let mut addresses: Vec<_> = params.external_addresses().map(|r| r.addr).collect(); + addresses.extend(params.listened_addresses()); + + let probe_id = self.probe_id.next(); + match self.do_probe(probe_id, addresses) { + Ok(peer) => { + self.pending_out_events.push_back(Event::OutboundProbe( + OutboundProbeEvent::Request { probe_id, peer }, + )); + } + Err(error) => { + self.pending_out_events.push_back(Event::OutboundProbe( + OutboundProbeEvent::Error { + probe_id, + peer: None, + error, + }, + )); + self.handle_reported_status(NatStatus::Unknown); + } + } + } + + if let Some(event) = self.pending_out_events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } + + if inner_pending { + return Poll::Pending; } } } diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/autonat.rs index ef2da985b27..773155d1ec5 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/autonat.rs @@ -117,6 +117,10 @@ async fn test_auto_probe() { other => panic!("Unexpected Event: {:?}", other), } + assert_eq!(client.behaviour().nat_status(), NatStatus::Unknown); + assert!(client.behaviour().public_address().is_none()); + assert_eq!(client.behaviour().confidence(), 0); + // Test Private NAT Status // Artificially add a faulty address. From f0cb16e45c5f577a6c392fa7166fcc04505513e8 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 30 Dec 2021 02:00:33 +0100 Subject: [PATCH 58/69] protocols/autonat/behaviour: fix report In-/Outbound Failures --- protocols/autonat/src/behaviour.rs | 69 +++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 7bdfe003541..caad27c1560 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -262,7 +262,15 @@ pub struct Behaviour { schedule_probe: Delay, // Ongoing inbound requests, where no response has been sent back to the remote yet. - ongoing_inbound: HashMap, ResponseChannel)>, + ongoing_inbound: HashMap< + PeerId, + ( + ProbeId, + RequestId, + Vec, + ResponseChannel, + ), + >, // Ongoing outbound probes and mapped to the inner request id. ongoing_outbound: HashMap, @@ -527,7 +535,7 @@ impl NetworkBehaviour for Behaviour { match endpoint { ConnectedPoint::Dialer { address } => { - if let Some((_, addrs, _)) = self.ongoing_inbound.get(peer) { + if let Some((_, _, addrs, _)) = self.ongoing_inbound.get(peer) { // Check if the dialed address was among the requested addresses. if addrs.contains(address) { log::debug!( @@ -536,7 +544,7 @@ impl NetworkBehaviour for Behaviour { address ); - let (probe_id, _, channel) = self.ongoing_inbound.remove(peer).unwrap(); + let (probe_id, _, _, channel) = self.ongoing_inbound.remove(peer).unwrap(); let response = DialResponse { result: Ok(address.clone()), status_text: None, @@ -573,7 +581,8 @@ impl NetworkBehaviour for Behaviour { error: &DialError, ) { self.inner.inject_dial_failure(peer, handler, error); - if let Some((probe_id, _, channel)) = peer.and_then(|p| self.ongoing_inbound.remove(&p)) { + if let Some((probe_id, _, _, channel)) = peer.and_then(|p| self.ongoing_inbound.remove(&p)) + { log::debug!( "Dial-back to peer {} failed with error {:?}.", peer.unwrap(), @@ -671,7 +680,7 @@ impl NetworkBehaviour for Behaviour { RequestResponseEvent::Message { peer, message }, )) => match message { RequestResponseMessage::Request { - request_id: _, + request_id, request, channel, } => { @@ -681,7 +690,7 @@ impl NetworkBehaviour for Behaviour { log::debug!("Inbound dial request from Peer {} with dial-back addresses {:?}.", peer, addrs); self.ongoing_inbound - .insert(peer, (probe_id, addrs.clone(), channel)); + .insert(peer, (probe_id, request_id, addrs.clone(), channel)); self.pending_out_events.push_back(Event::InboundProbe( InboundProbeEvent::Request { @@ -775,19 +784,59 @@ impl NetworkBehaviour for Behaviour { RequestResponseEvent::ResponseSent { .. }, )) => {} Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { error, peer, .. }, + RequestResponseEvent::OutboundFailure { + error, + peer, + request_id, + }, )) => { log::debug!( - "Outbound Failure {} when sending dial-back request to server {}.", + "Outbound Failure {} when on dial-back request to peer {}.", error, peer ); + let probe_id = self + .ongoing_outbound + .remove(&request_id) + .unwrap_or_else(|| self.probe_id.next()); + + self.pending_out_events.push_back(Event::OutboundProbe( + OutboundProbeEvent::Error { + probe_id, + peer: Some(peer), + error: OutboundProbeError::OutboundRequest(error), + }, + )); + self.schedule_probe.reset(Duration::ZERO); } Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::InboundFailure { peer, .. }, + RequestResponseEvent::InboundFailure { + peer, + error, + request_id, + }, )) => { - self.ongoing_inbound.remove(&peer); + log::debug!( + "Inbound Failure {} when on dial-back request from peer {}.", + error, + peer + ); + + let probe_id = match self.ongoing_inbound.get(&peer) { + Some((_, rq_id, _, _)) if *rq_id == request_id => { + self.ongoing_inbound.remove(&peer).unwrap().0 + } + _ => self.probe_id.next(), + }; + + self.pending_out_events.push_back(Event::InboundProbe( + InboundProbeEvent::Error { + probe_id, + peer, + error: InboundProbeError::InboundRequest(error), + }, + )); } Poll::Ready(action) => return Poll::Ready(action.map_out(|_| unreachable!())), Poll::Pending => inner_pending = true, From 27762f0c360269ffb1c1fa331500a7ba5ec19c0e Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Fri, 31 Dec 2021 01:19:16 +0100 Subject: [PATCH 59/69] protocols/autonat/behaviour: clean Behaviour::poll --- protocols/autonat/src/behaviour.rs | 61 +++++++++++++++--------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index caad27c1560..dcf3e87f6ca 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -674,7 +674,11 @@ impl NetworkBehaviour for Behaviour { params: &mut impl PollParameters, ) -> Poll> { loop { - let mut inner_pending = false; + if let Some(event) = self.pending_out_events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); + } + + let mut is_inner_pending = false; match self.inner.poll(cx, params) { Poll::Ready(NetworkBehaviourAction::GenerateEvent( RequestResponseEvent::Message { peer, message }, @@ -839,41 +843,38 @@ impl NetworkBehaviour for Behaviour { )); } Poll::Ready(action) => return Poll::Ready(action.map_out(|_| unreachable!())), - Poll::Pending => inner_pending = true, + Poll::Pending => is_inner_pending = true, } - if self.schedule_probe.poll_unpin(cx).is_ready() { - self.schedule_probe.reset(self.config.retry_interval); + match self.schedule_probe.poll_unpin(cx) { + Poll::Ready(()) => { + self.schedule_probe.reset(self.config.retry_interval); - let mut addresses: Vec<_> = params.external_addresses().map(|r| r.addr).collect(); - addresses.extend(params.listened_addresses()); + let mut addresses: Vec<_> = + params.external_addresses().map(|r| r.addr).collect(); + addresses.extend(params.listened_addresses()); - let probe_id = self.probe_id.next(); - match self.do_probe(probe_id, addresses) { - Ok(peer) => { - self.pending_out_events.push_back(Event::OutboundProbe( - OutboundProbeEvent::Request { probe_id, peer }, - )); - } - Err(error) => { - self.pending_out_events.push_back(Event::OutboundProbe( - OutboundProbeEvent::Error { - probe_id, - peer: None, - error, - }, - )); - self.handle_reported_status(NatStatus::Unknown); + let probe_id = self.probe_id.next(); + match self.do_probe(probe_id, addresses) { + Ok(peer) => { + self.pending_out_events.push_back(Event::OutboundProbe( + OutboundProbeEvent::Request { probe_id, peer }, + )); + } + Err(error) => { + self.pending_out_events.push_back(Event::OutboundProbe( + OutboundProbeEvent::Error { + probe_id, + peer: None, + error, + }, + )); + self.handle_reported_status(NatStatus::Unknown); + } } } - } - - if let Some(event) = self.pending_out_events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); - } - - if inner_pending { - return Poll::Pending; + Poll::Pending if is_inner_pending => return Poll::Pending, + Poll::Pending => {} } } } From 3ed7b91d5500fe5a02a21cf7ab5da1380d88ee65 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 1 Jan 2022 22:33:48 +0100 Subject: [PATCH 60/69] protocols/autonat/behaviour: fix throttle clients --- protocols/autonat/src/behaviour.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index dcf3e87f6ca..9c6c5303e22 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -59,7 +59,6 @@ pub struct Config { /// Interval in which the NAT status should be re-tried if it is currently unknown /// or max confidence was not reached yet. pub retry_interval: Duration, - /// Throttle period for re-using a peer as server for a dial-request. pub throttle_server_period: Duration, /// Use connected peers as servers for probes. @@ -695,6 +694,7 @@ impl NetworkBehaviour for Behaviour { self.ongoing_inbound .insert(peer, (probe_id, request_id, addrs.clone(), channel)); + self.throttled_clients.push((peer, Instant::now())); self.pending_out_events.push_back(Event::InboundProbe( InboundProbeEvent::Request { From 17b23d1c0aba2ed3f3c8fcdc529dcedca3e1f5b9 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 1 Jan 2022 22:38:15 +0100 Subject: [PATCH 61/69] protocols/autonat: clean client tests --- .../tests/{autonat.rs => test_client.rs} | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) rename protocols/autonat/tests/{autonat.rs => test_client.rs} (93%) diff --git a/protocols/autonat/tests/autonat.rs b/protocols/autonat/tests/test_client.rs similarity index 93% rename from protocols/autonat/tests/autonat.rs rename to protocols/autonat/tests/test_client.rs index 773155d1ec5..17234c3dae1 100644 --- a/protocols/autonat/tests/autonat.rs +++ b/protocols/autonat/tests/test_client.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{channel::oneshot, FutureExt, StreamExt}; +use futures::{channel::oneshot, Future, FutureExt, StreamExt}; use futures_timer::Delay; use libp2p::{ development_transport, @@ -46,18 +46,18 @@ async fn init_swarm(config: Config) -> Swarm { async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { let (tx, rx) = oneshot::channel(); async_std::task::spawn(async move { - let mut swarm = init_swarm(Config { + let mut server = init_swarm(Config { boot_delay: Duration::from_secs(60), throttle_clients_peer_max: usize::MAX, ..Default::default() }) .await; - let peer_id = *swarm.local_peer_id(); - swarm + let peer_id = *server.local_peer_id(); + server .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); let addr = loop { - match swarm.select_next_some().await { + match server.select_next_some().await { SwarmEvent::NewListenAddr { address, .. } => break address, _ => {} }; @@ -66,7 +66,7 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { let mut kill = kill.fuse(); loop { futures::select! { - _ = swarm.select_next_some() => {}, + _ = server.select_next_some() => {}, _ = kill => return, } @@ -75,9 +75,9 @@ async fn spawn_server(kill: oneshot::Receiver<()>) -> (PeerId, Multiaddr) { rx.await.unwrap() } -async fn next_event(client: &mut Swarm) -> Event { +async fn next_event(swarm: &mut Swarm) -> Event { loop { - match client.select_next_some().await { + match swarm.select_next_some().await { SwarmEvent::Behaviour(event) => { break event; } @@ -86,9 +86,16 @@ async fn next_event(client: &mut Swarm) -> Event { } } +async fn run_test_with_timeout(test: impl Future) { + futures::select! { + _ = test.fuse() => {}, + _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") + } +} + #[async_std::test] async fn test_auto_probe() { - let run_test = async { + let test = async { let mut client = init_swarm(Config { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, @@ -164,7 +171,6 @@ async fn test_auto_probe() { assert!(client.behaviour().public_address().is_none()); // Test new public listening address - client .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); @@ -222,15 +228,12 @@ async fn test_auto_probe() { drop(_handle); }; - futures::select! { - _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") - } + run_test_with_timeout(test).await; } #[async_std::test] async fn test_confidence() { - let run_test = async { + let test = async { let mut client = init_swarm(Config { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, @@ -320,15 +323,12 @@ async fn test_confidence() { drop(_handle); }; - futures::select! { - _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") - } + run_test_with_timeout(test).await; } #[async_std::test] async fn test_throttle_server_period() { - let run_test = async { + let test = async { let mut client = init_swarm(Config { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, @@ -384,15 +384,12 @@ async fn test_throttle_server_period() { drop(_handle) }; - futures::select! { - _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") - } + run_test_with_timeout(test).await; } #[async_std::test] async fn test_use_connected_as_server() { - let run_test = async { + let test = async { let mut client = init_swarm(Config { retry_interval: TEST_RETRY_INTERVAL, refresh_interval: TEST_REFRESH_INTERVAL, @@ -439,15 +436,12 @@ async fn test_use_connected_as_server() { drop(_handle); }; - futures::select! { - _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") - } + run_test_with_timeout(test).await; } #[async_std::test] async fn test_outbound_failure() { - let run_test = async { + let test = async { let mut servers = Vec::new(); let mut client = init_swarm(Config { @@ -515,8 +509,5 @@ async fn test_outbound_failure() { } }; - futures::select! { - _ = run_test.fuse() => {}, - _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") - } + run_test_with_timeout(test).await; } From 8ea01129dd61c6b4b1fb4b2c3aa2ca7b4b6201e0 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sat, 1 Jan 2022 22:38:35 +0100 Subject: [PATCH 62/69] protocols/autonat: add server tests --- protocols/autonat/tests/test_server.rs | 426 +++++++++++++++++++++++++ 1 file changed, 426 insertions(+) create mode 100644 protocols/autonat/tests/test_server.rs diff --git a/protocols/autonat/tests/test_server.rs b/protocols/autonat/tests/test_server.rs new file mode 100644 index 00000000000..0dea69d872c --- /dev/null +++ b/protocols/autonat/tests/test_server.rs @@ -0,0 +1,426 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::{channel::oneshot, Future, FutureExt, StreamExt}; +use futures_timer::Delay; +use libp2p::{ + development_transport, + identity::Keypair, + multiaddr::Protocol, + swarm::{AddressScore, Swarm, SwarmEvent}, + Multiaddr, PeerId, +}; +use libp2p_autonat::{ + Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, ResponseError, +}; +use libp2p_core::ConnectedPoint; +use libp2p_swarm::{DialError, SwarmBuilder}; +use std::{ + num::{NonZeroU32, NonZeroU8}, + time::Duration, +}; + +async fn init_swarm(config: Config) -> Swarm { + let keypair = Keypair::generate_ed25519(); + let local_id = PeerId::from_public_key(&keypair.public()); + let transport = development_transport(keypair).await.unwrap(); + let behaviour = Behaviour::new(local_id, config); + SwarmBuilder::new(transport, behaviour, local_id) + .dial_concurrency_factor(NonZeroU8::new(1).unwrap()) + .build() +} + +async fn init_server(config: Option) -> (Swarm, PeerId, Multiaddr) { + let mut config = config.unwrap_or_default(); + // Don't do any outbound probes. + config.boot_delay = Duration::from_secs(60); + + let mut server = init_swarm(config).await; + let peer_id = *server.local_peer_id(); + server + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + let addr = loop { + match server.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => break address, + _ => {} + }; + }; + (server, peer_id, addr) +} + +async fn spawn_client( + listen: bool, + add_dummy_external_addr: bool, + server_id: PeerId, + server_addr: Multiaddr, + kill: oneshot::Receiver<()>, +) -> (PeerId, Option) { + let (tx, rx) = oneshot::channel(); + async_std::task::spawn(async move { + let mut client = init_swarm(Config { + boot_delay: Duration::from_millis(100), + refresh_interval: Duration::from_millis(100), + retry_interval: Duration::from_millis(200), + throttle_server_period: Duration::ZERO, + ..Default::default() + }) + .await; + client + .behaviour_mut() + .add_server(server_id, Some(server_addr)); + let peer_id = *client.local_peer_id(); + let mut addr = None; + if listen { + client + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); + loop { + match client.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => { + addr = Some(address); + break; + } + _ => {} + }; + } + } + if add_dummy_external_addr { + let dummy_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap(); + client.add_external_address(dummy_addr, AddressScore::Infinite); + } + tx.send((peer_id, addr)).unwrap(); + let mut kill = kill.fuse(); + loop { + futures::select! { + _ = client.select_next_some() => {}, + _ = kill => return, + + } + } + }); + rx.await.unwrap() +} + +async fn next_event(swarm: &mut Swarm) -> Event { + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(event) => { + break event; + } + _ => {} + } + } +} + +async fn run_test_with_timeout(test: impl Future) { + futures::select! { + _ = test.fuse() => {}, + _ = Delay::new(Duration::from_secs(60)).fuse() => panic!("test timed out") + } +} + +#[async_std::test] +async fn test_dial_back() { + let test = async { + let (mut server, server_id, server_addr) = init_server(None).await; + let (_handle, rx) = oneshot::channel(); + let (client_id, client_addr) = spawn_client(true, false, server_id, server_addr, rx).await; + let client_port = client_addr + .unwrap() + .into_iter() + .find_map(|p| match p { + Protocol::Tcp(port) => Some(port), + _ => None, + }) + .unwrap(); + let observed_client_ip = loop { + match server.select_next_some().await { + SwarmEvent::ConnectionEstablished { + peer_id, + endpoint: + ConnectedPoint::Listener { + mut send_back_addr, .. + }, + .. + } => { + assert_eq!(peer_id, client_id); + let observed_client_ip = loop { + match send_back_addr.pop().unwrap() { + Protocol::Ip4(ip4_addr) => break ip4_addr, + _ => {} + } + }; + break observed_client_ip; + } + SwarmEvent::IncomingConnection { .. } | SwarmEvent::NewListenAddr { .. } => {} + _ => panic!("Unexpected Swarm Event"), + } + }; + let expect_addr = Multiaddr::empty() + .with(Protocol::Ip4(observed_client_ip)) + .with(Protocol::Tcp(client_port)) + .with(Protocol::P2p(client_id.into())); + let request_probe_id = match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Request { + peer, + addresses, + probe_id, + }) => { + assert_eq!(peer, client_id); + assert_eq!(addresses.len(), 1); + assert_eq!(addresses[0], expect_addr); + probe_id + } + other => panic!("Unexpected Event: {:?}", other), + }; + + loop { + match server.select_next_some().await { + SwarmEvent::ConnectionEstablished { + peer_id, + endpoint: ConnectedPoint::Dialer { address }, + num_established, + concurrent_dial_errors, + } => { + assert_eq!(peer_id, client_id); + assert_eq!(num_established, NonZeroU32::new(2).unwrap()); + assert!(concurrent_dial_errors.unwrap().is_empty()); + assert_eq!(address, expect_addr); + break; + } + SwarmEvent::Dialing(peer) => assert_eq!(peer, client_id), + SwarmEvent::NewListenAddr { .. } => {} + _ => panic!("Unexpected Swarm Event"), + } + } + + match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Response { + probe_id, + peer, + address, + }) => { + assert_eq!(probe_id, request_probe_id); + assert_eq!(peer, client_id); + assert_eq!(address, expect_addr); + } + other => panic!("Unexpected Event: {:?}", other), + } + + drop(_handle); + }; + + run_test_with_timeout(test).await; +} + +#[async_std::test] +async fn test_dial_error() { + let test = async { + let (mut server, server_id, server_addr) = init_server(None).await; + let (_handle, rx) = oneshot::channel(); + let (client_id, _) = spawn_client(false, true, server_id, server_addr, rx).await; + let request_probe_id = match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Request { peer, probe_id, .. }) => { + assert_eq!(peer, client_id); + probe_id + } + other => panic!("Unexpected Event: {:?}", other), + }; + + loop { + match server.select_next_some().await { + SwarmEvent::OutgoingConnectionError { peer_id, error } => { + assert_eq!(peer_id.unwrap(), client_id); + assert!(matches!(error, DialError::Transport(_))); + break; + } + SwarmEvent::Dialing(peer) => assert_eq!(peer, client_id), + SwarmEvent::NewListenAddr { .. } => {} + _ => panic!("Unexpected Swarm Event"), + } + } + + match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Error { + probe_id, + peer, + error, + }) => { + assert_eq!(probe_id, request_probe_id); + assert_eq!(peer, client_id); + assert_eq!(error, InboundProbeError::Response(ResponseError::DialError)); + } + other => panic!("Unexpected Event: {:?}", other), + } + + drop(_handle); + }; + + run_test_with_timeout(test).await; +} + +#[async_std::test] +async fn test_throttle_global_max() { + let test = async { + let (mut server, server_id, server_addr) = init_server(Some(Config { + throttle_clients_global_max: 1, + throttle_clients_period: Duration::from_secs(60), + ..Default::default() + })) + .await; + let mut _handles = Vec::new(); + for _ in 0..2 { + let (_handle, rx) = oneshot::channel(); + spawn_client(true, false, server_id, server_addr.clone(), rx).await; + _handles.push(_handle); + } + + let (first_probe_id, first_peer_id) = match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Request { peer, probe_id, .. }) => { + (probe_id, peer) + } + other => panic!("Unexpected Event: {:?}", other), + }; + + loop { + match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Error { + peer, + probe_id, + error: InboundProbeError::Response(ResponseError::DialRefused), + }) => { + assert_ne!(first_peer_id, peer); + assert_ne!(first_probe_id, probe_id); + break; + } + Event::InboundProbe(InboundProbeEvent::Response { peer, probe_id, .. }) => { + assert_eq!(first_peer_id, peer); + assert_eq!(first_probe_id, probe_id); + } + other => panic!("Unexpected Event: {:?}", other), + }; + } + + drop(_handles); + }; + + run_test_with_timeout(test).await; +} + +#[async_std::test] +async fn test_throttle_peer_max() { + let test = async { + let (mut server, server_id, server_addr) = init_server(Some(Config { + throttle_clients_peer_max: 1, + throttle_clients_period: Duration::from_secs(60), + ..Default::default() + })) + .await; + + let (_handle, rx) = oneshot::channel(); + let (client_id, _) = spawn_client(true, false, server_id, server_addr.clone(), rx).await; + + let first_probe_id = match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Request { peer, probe_id, .. }) => { + assert_eq!(client_id, peer); + probe_id + } + other => panic!("Unexpected Event: {:?}", other), + }; + + match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Response { peer, probe_id, .. }) => { + assert_eq!(peer, client_id); + assert_eq!(probe_id, first_probe_id); + } + other => panic!("Unexpected Event: {:?}", other), + } + + match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Error { + peer, + probe_id, + error, + }) => { + assert_eq!(client_id, peer); + assert_ne!(first_probe_id, probe_id); + assert_eq!( + error, + InboundProbeError::Response(ResponseError::DialRefused) + ) + } + other => panic!("Unexpected Event: {:?}", other), + }; + + drop(_handle); + }; + + run_test_with_timeout(test).await; +} + +#[async_std::test] +async fn test_dial_multiple_addr() { + let test = async { + let (mut server, server_id, server_addr) = init_server(Some(Config { + throttle_clients_peer_max: 1, + throttle_clients_period: Duration::from_secs(60), + ..Default::default() + })) + .await; + + let (_handle, rx) = oneshot::channel(); + let (client_id, _) = spawn_client(true, true, server_id, server_addr.clone(), rx).await; + + let dial_addresses = match next_event(&mut server).await { + Event::InboundProbe(InboundProbeEvent::Request { + peer, addresses, .. + }) => { + assert_eq!(addresses.len(), 2); + assert_eq!(client_id, peer); + addresses + } + other => panic!("Unexpected Event: {:?}", other), + }; + + loop { + match server.select_next_some().await { + SwarmEvent::ConnectionEstablished { + peer_id, + endpoint: ConnectedPoint::Dialer { address }, + concurrent_dial_errors, + .. + } => { + assert_eq!(peer_id, client_id); + let dial_errors = concurrent_dial_errors.unwrap(); + assert_eq!(dial_errors.len(), 1); + assert_eq!(dial_errors[0].0, dial_addresses[0]); + assert_eq!(address, dial_addresses[1]); + break; + } + SwarmEvent::Dialing(peer) => assert_eq!(peer, client_id), + SwarmEvent::NewListenAddr { .. } => {} + _ => panic!("Unexpected Swarm Event"), + } + } + }; + + run_test_with_timeout(test).await; +} From 5b15dcb5c93e10c4b047b052d3ec5fc10f53ba07 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 2 Jan 2022 01:31:16 +0100 Subject: [PATCH 63/69] protocols/autonat: Separate Client and Server logic Add `AsClient` and `AsServer` views for the Behaviour to separate the logic of the two roles. Instead of adding permanent (sub-)structs to the Behaviour, the two new structures only hold temporary references to the parent fields. This allows shared access to fields that are used by both structures. --- protocols/autonat/src/behaviour.rs | 728 ++----------------- protocols/autonat/src/behaviour/as_client.rs | 373 ++++++++++ protocols/autonat/src/behaviour/as_server.rs | 428 +++++++++++ 3 files changed, 877 insertions(+), 652 deletions(-) create mode 100644 protocols/autonat/src/behaviour/as_client.rs create mode 100644 protocols/autonat/src/behaviour/as_server.rs diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index 9c6c5303e22..e354fa49582 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -18,28 +18,29 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +mod as_client; +mod as_server; + use crate::protocol::{AutoNatCodec, AutoNatProtocol, DialRequest, DialResponse, ResponseError}; -use futures::FutureExt; +use as_client::AsClient; +pub use as_client::{OutboundProbeError, OutboundProbeEvent}; +use as_server::AsServer; +pub use as_server::{InboundProbeError, InboundProbeEvent}; use futures_timer::Delay; use instant::Instant; use libp2p_core::{ connection::{ConnectionId, ListenerId}, - multiaddr::Protocol, ConnectedPoint, Multiaddr, PeerId, }; use libp2p_request_response::{ - handler::RequestResponseHandlerEvent, InboundFailure, OutboundFailure, ProtocolSupport, - RequestId, RequestResponse, RequestResponseConfig, RequestResponseEvent, - RequestResponseMessage, ResponseChannel, + handler::RequestResponseHandlerEvent, ProtocolSupport, RequestId, RequestResponse, + RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, }; use libp2p_swarm::{ - dial_opts::{DialOpts, PeerCondition}, - AddressScore, DialError, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, + DialError, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; -use rand::{seq::SliceRandom, thread_rng}; use std::{ - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, VecDeque}, iter, task::{Context, Poll}, time::Duration, @@ -110,16 +111,6 @@ impl NatStatus { } } -impl From> for NatStatus { - fn from(result: Result) -> Self { - match result { - Ok(addr) => NatStatus::Public(addr), - Err(ResponseError::DialError) => NatStatus::Private, - _ => NatStatus::Unknown, - } - } -} - /// Unique identifier for a probe. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ProbeId(usize); @@ -132,84 +123,6 @@ impl ProbeId { } } -/// Outbound probe failed or was aborted. -#[derive(Debug, Clone, PartialEq)] -pub enum OutboundProbeError { - /// Probe was aborted because no server is known, or all servers - /// are throttled through [`Config::throttle_server_period`]. - NoServer, - /// Probe was aborted because the local peer has no listening or - /// external addresses. - NoAddresses, - /// Sending the dial-back request or receiving a response failed. - OutboundRequest(OutboundFailure), - /// The server refused or failed to dial us. - Response(ResponseError), -} - -/// Inbound probe failed. -#[derive(Debug, Clone, PartialEq)] -pub enum InboundProbeError { - /// Receiving the dial-back request or sending a response failed. - InboundRequest(InboundFailure), - /// We refused or failed to dial the client. - Response(ResponseError), -} - -#[derive(Debug, Clone, PartialEq)] -pub enum InboundProbeEvent { - /// A dial-back request was received from a remote peer. - Request { - probe_id: ProbeId, - /// Peer that sent the request. - peer: PeerId, - /// The addresses that will be attempted to dial. - addresses: Vec, - }, - /// A dial request to the remote was successful. - Response { - probe_id: ProbeId, - /// Peer to which the response is sent. - peer: PeerId, - address: Multiaddr, - }, - /// The inbound request failed, was rejected, or none of the remote's - /// addresses could be dialed. - Error { - probe_id: ProbeId, - /// Peer that sent the dial-back request. - peer: PeerId, - error: InboundProbeError, - }, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum OutboundProbeEvent { - /// A dial-back request was sent to a remote peer. - Request { - probe_id: ProbeId, - /// Peer to which the request is sent. - peer: PeerId, - }, - /// The remote successfully dialed one of our addresses. - Response { - probe_id: ProbeId, - /// Id of the peer that sent the response. - peer: PeerId, - /// The address at which the remote succeeded to dial us. - address: Multiaddr, - }, - /// The outbound request failed, was rejected, or the remote could dial - /// none of our addresses. - Error { - probe_id: ProbeId, - /// Id of the peer used for the probe. - /// `None` if the probe was aborted due to no addresses or no qualified server. - peer: Option, - error: OutboundProbeError, - }, -} - /// Event produced by [`Behaviour`]. #[derive(Debug, Clone, PartialEq)] pub enum Event { @@ -351,168 +264,32 @@ impl Behaviour { self.servers.retain(|p| p != peer); } - // Select a random server for the probe. - fn random_server(&mut self) -> Option { - // Update list of throttled servers. - let i = self.throttled_servers.partition_point(|(_, time)| { - *time + self.config.throttle_server_period < Instant::now() - }); - self.throttled_servers.drain(..i); - - let mut servers: Vec<&PeerId> = self.servers.iter().collect(); - - if self.config.use_connected { - servers.extend(self.connected.iter().map(|(id, _)| id)); + fn as_client(&mut self) -> AsClient { + AsClient { + inner: &mut self.inner, + local_peer_id: self.local_peer_id, + config: &self.config, + connected: &self.connected, + probe_id: &mut self.probe_id, + servers: &self.servers, + throttled_servers: &mut self.throttled_servers, + nat_status: &mut self.nat_status, + confidence: &mut self.confidence, + ongoing_outbound: &mut self.ongoing_outbound, + last_probe: &mut self.last_probe, + schedule_probe: &mut self.schedule_probe, } - - servers.retain(|s| !self.throttled_servers.iter().any(|(id, _)| s == &id)); - - servers.choose(&mut thread_rng()).map(|&&p| p) } - // Send a dial-request to a randomly selected server. - // Returns the server that is used in this probe. - // `Err` if there are no qualified servers or no addresses. - fn do_probe( - &mut self, - probe_id: ProbeId, - addresses: Vec, - ) -> Result { - self.last_probe = Some(Instant::now()); - if addresses.is_empty() { - log::debug!("Outbound dial-back request aborted: No dial-back addresses."); - return Err(OutboundProbeError::NoAddresses); + fn as_server(&mut self) -> AsServer { + AsServer { + inner: &mut self.inner, + config: &self.config, + connected: &self.connected, + probe_id: &mut self.probe_id, + throttled_clients: &mut self.throttled_clients, + ongoing_inbound: &mut self.ongoing_inbound, } - let server = match self.random_server() { - Some(s) => s, - None => { - log::debug!("Outbound dial-back request aborted: No qualified server."); - return Err(OutboundProbeError::NoServer); - } - }; - let request_id = self.inner.send_request( - &server, - DialRequest { - peer_id: self.local_peer_id, - addresses, - }, - ); - self.throttled_servers.push((server, Instant::now())); - log::debug!("Send dial-back request to peer {}.", server); - self.ongoing_outbound.insert(request_id, probe_id); - Ok(server) - } - - // Validate the inbound request and collect the addresses to be dialed. - fn resolve_inbound_request( - &mut self, - sender: PeerId, - request: DialRequest, - ) -> Result, (String, ResponseError)> { - // Update list of throttled clients. - let i = self.throttled_clients.partition_point(|(_, time)| { - *time + self.config.throttle_clients_period < Instant::now() - }); - self.throttled_clients.drain(..i); - - if request.peer_id != sender { - let status_text = "peer id mismatch".to_string(); - return Err((status_text, ResponseError::BadRequest)); - } - - if self.ongoing_inbound.contains_key(&sender) { - let status_text = "dial-back already ongoing".to_string(); - return Err((status_text, ResponseError::DialRefused)); - } - - if self.throttled_clients.len() >= self.config.throttle_clients_global_max { - let status_text = "too many total dials".to_string(); - return Err((status_text, ResponseError::DialRefused)); - } - - let throttled_for_client = self - .throttled_clients - .iter() - .filter(|(p, _)| p == &sender) - .count(); - - if throttled_for_client >= self.config.throttle_clients_peer_max { - let status_text = "too many dials for peer".to_string(); - return Err((status_text, ResponseError::DialRefused)); - } - - let observed_addr = self - .connected - .get(&sender) - .expect("We are connected to the peer."); - - let mut addrs = filter_valid_addrs(sender, request.addresses, observed_addr); - addrs.truncate(self.config.max_peer_addresses); - - if addrs.is_empty() { - let status_text = "no dialable addresses".to_string(); - return Err((status_text, ResponseError::DialError)); - } - - Ok(addrs) - } - - // Set the delay to the next probe based on the time of our last probe - // and the specified delay. - fn schedule_next_probe(&mut self, delay: Duration) { - let last_probe_instant = match self.last_probe { - Some(instant) => instant, - None => { - return; - } - }; - let schedule_next = last_probe_instant + delay; - self.schedule_probe - .reset(schedule_next.saturating_duration_since(Instant::now())); - } - - // Adapt current confidence and NAT status to the status reported by the latest probe. - // Return the old status if it flipped. - fn handle_reported_status(&mut self, reported_status: NatStatus) -> Option { - self.schedule_next_probe(self.config.retry_interval); - - if matches!(reported_status, NatStatus::Unknown) { - return None; - } - - if reported_status == self.nat_status { - if self.confidence < self.config.confidence_max { - self.confidence += 1; - } - // Delay with (usually longer) refresh-interval. - if self.confidence >= self.config.confidence_max { - self.schedule_next_probe(self.config.refresh_interval); - } - return None; - } - - if reported_status.is_public() && self.nat_status.is_public() { - // Different address than the currently assumed public address was reported. - // Switch address, but don't report as flipped. - self.nat_status = reported_status; - return None; - } - if self.confidence > 0 { - // Reduce confidence but keep old status. - self.confidence -= 1; - return None; - } - - log::debug!( - "Flipped assumed NAT status from {:?} to {:?}", - self.nat_status, - reported_status - ); - - let old_status = self.nat_status.clone(); - self.nat_status = reported_status; - - Some(old_status) } } @@ -534,42 +311,12 @@ impl NetworkBehaviour for Behaviour { match endpoint { ConnectedPoint::Dialer { address } => { - if let Some((_, _, addrs, _)) = self.ongoing_inbound.get(peer) { - // Check if the dialed address was among the requested addresses. - if addrs.contains(address) { - log::debug!( - "Dial-back to peer {} succeeded at addr {:?}.", - peer, - address - ); - - let (probe_id, _, _, channel) = self.ongoing_inbound.remove(peer).unwrap(); - let response = DialResponse { - result: Ok(address.clone()), - status_text: None, - }; - let _ = self.inner.send_response(channel, response); - - self.pending_out_events.push_back(Event::InboundProbe( - InboundProbeEvent::Response { - probe_id, - peer: *peer, - address: address.clone(), - }, - )); - } - } - } - // An inbound connection can indicate that we are public; adjust the delay to the next probe. - ConnectedPoint::Listener { .. } => { - if self.confidence == self.config.confidence_max { - if self.nat_status.is_public() { - self.schedule_next_probe(self.config.refresh_interval * 2); - } else { - self.schedule_next_probe(self.config.refresh_interval / 5); - } + if let Some(event) = self.as_server().on_outbound_connection(peer, address) { + self.pending_out_events + .push_back(Event::InboundProbe(event)); } } + ConnectedPoint::Listener { .. } => self.as_client().on_inbound_connection(), } } @@ -580,26 +327,9 @@ impl NetworkBehaviour for Behaviour { error: &DialError, ) { self.inner.inject_dial_failure(peer, handler, error); - if let Some((probe_id, _, _, channel)) = peer.and_then(|p| self.ongoing_inbound.remove(&p)) - { - log::debug!( - "Dial-back to peer {} failed with error {:?}.", - peer.unwrap(), - error - ); - let response_error = ResponseError::DialError; - let response = DialResponse { - result: Err(response_error.clone()), - status_text: Some("dial failed".to_string()), - }; - let _ = self.inner.send_response(channel, response); - + if let Some(event) = self.as_server().on_outbound_dial_error(peer, error) { self.pending_out_events - .push_back(Event::InboundProbe(InboundProbeEvent::Error { - probe_id, - peer: peer.expect("PeerId is present."), - error: InboundProbeError::Response(response_error), - })); + .push_back(Event::InboundProbe(event)); } } @@ -623,55 +353,25 @@ impl NetworkBehaviour for Behaviour { fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_new_listen_addr(id, addr); - - // New address could be publicly reachable, trigger retry. - if !self.nat_status.is_public() { - if self.confidence > 0 { - self.confidence -= 1; - } - self.schedule_next_probe(self.config.retry_interval); - } + self.as_client().on_new_address(); } fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { self.inner.inject_expired_listen_addr(id, addr); - if let Some(public_address) = self.public_address() { - if public_address == addr { - self.confidence = 0; - self.nat_status = NatStatus::Unknown; - self.schedule_probe.reset(Duration::ZERO); - } - } + self.as_client().on_expired_address(addr); } fn inject_new_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_new_external_addr(addr); - - // New address could be publicly reachable, trigger retry. - if !self.nat_status.is_public() { - if self.confidence > 0 { - self.confidence -= 1; - } - self.schedule_next_probe(self.config.retry_interval); - } + self.as_client().on_new_address(); } fn inject_expired_external_addr(&mut self, addr: &Multiaddr) { self.inner.inject_expired_external_addr(addr); - if let Some(public_address) = self.public_address() { - if public_address == addr { - self.confidence = 0; - self.nat_status = NatStatus::Unknown; - self.schedule_probe.reset(Duration::ZERO); - } - } + self.as_client().on_expired_address(addr); } - fn poll( - &mut self, - cx: &mut Context<'_>, - params: &mut impl PollParameters, - ) -> Poll> { + fn poll(&mut self, cx: &mut Context<'_>, params: &mut impl PollParameters) -> Poll { loop { if let Some(event) = self.pending_out_events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); @@ -679,200 +379,37 @@ impl NetworkBehaviour for Behaviour { let mut is_inner_pending = false; match self.inner.poll(cx, params) { - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::Message { peer, message }, - )) => match message { - RequestResponseMessage::Request { - request_id, - request, - channel, - } => { - let probe_id = self.probe_id.next(); - match self.resolve_inbound_request(peer, request) { - Ok(addrs) => { - log::debug!("Inbound dial request from Peer {} with dial-back addresses {:?}.", peer, addrs); - - self.ongoing_inbound - .insert(peer, (probe_id, request_id, addrs.clone(), channel)); - self.throttled_clients.push((peer, Instant::now())); - - self.pending_out_events.push_back(Event::InboundProbe( - InboundProbeEvent::Request { - probe_id, - peer, - addresses: addrs.clone(), - }, - )); - - return Poll::Ready(NetworkBehaviourAction::Dial { - opts: DialOpts::peer_id(peer) - .condition(PeerCondition::Always) - .addresses(addrs) - .build(), - handler: self.inner.new_handler(), - }); - } - Err((status_text, error)) => { - log::debug!( - "Reject inbound dial request from peer {}: {}.", - peer, - status_text - ); - - let response = DialResponse { - result: Err(error.clone()), - status_text: Some(status_text), - }; - let _ = self.inner.send_response(channel, response.clone()); - - self.pending_out_events.push_back(Event::InboundProbe( - InboundProbeEvent::Error { - probe_id, - peer, - error: InboundProbeError::Response(error), - }, - )); - } + Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) => { + let (mut events, action) = match event { + RequestResponseEvent::Message { + message: RequestResponseMessage::Response { .. }, + .. } - } - RequestResponseMessage::Response { - response, - request_id, - } => { - log::debug!("Outbound dial-back request returned {:?}.", response); - let probe_id = self - .ongoing_outbound - .remove(&request_id) - .expect("RequestId exists."); - - let event = match response.result.clone() { - Ok(address) => OutboundProbeEvent::Response { - probe_id, - peer, - address, - }, - Err(e) => OutboundProbeEvent::Error { - probe_id, - peer: Some(peer), - error: OutboundProbeError::Response(e), - }, - }; - self.pending_out_events - .push_back(Event::OutboundProbe(event)); - - if let Some(old) = - self.handle_reported_status(response.result.clone().into()) - { - self.pending_out_events.push_back(Event::StatusChanged { - old, - new: self.nat_status.clone(), - }); + | RequestResponseEvent::OutboundFailure { .. } => { + self.as_client().handle_event(params, event) } - - if let Ok(address) = response.result { - // Update observed address score if it is finite. - let score = params - .external_addresses() - .find_map(|r| (r.addr == address).then(|| r.score)) - .unwrap_or(AddressScore::Finite(0)); - if let AddressScore::Finite(finite_score) = score { - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score: AddressScore::Finite(finite_score + 1), - }); - } + RequestResponseEvent::Message { + message: RequestResponseMessage::Request { .. }, + .. } - } - }, - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::ResponseSent { .. }, - )) => {} - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::OutboundFailure { - error, - peer, - request_id, - }, - )) => { - log::debug!( - "Outbound Failure {} when on dial-back request to peer {}.", - error, - peer - ); - let probe_id = self - .ongoing_outbound - .remove(&request_id) - .unwrap_or_else(|| self.probe_id.next()); - - self.pending_out_events.push_back(Event::OutboundProbe( - OutboundProbeEvent::Error { - probe_id, - peer: Some(peer), - error: OutboundProbeError::OutboundRequest(error), - }, - )); - - self.schedule_probe.reset(Duration::ZERO); - } - Poll::Ready(NetworkBehaviourAction::GenerateEvent( - RequestResponseEvent::InboundFailure { - peer, - error, - request_id, - }, - )) => { - log::debug!( - "Inbound Failure {} when on dial-back request from peer {}.", - error, - peer - ); - - let probe_id = match self.ongoing_inbound.get(&peer) { - Some((_, rq_id, _, _)) if *rq_id == request_id => { - self.ongoing_inbound.remove(&peer).unwrap().0 + | RequestResponseEvent::InboundFailure { .. } => { + self.as_server().handle_event(params, event) } - _ => self.probe_id.next(), + RequestResponseEvent::ResponseSent { .. } => (VecDeque::new(), None), }; - - self.pending_out_events.push_back(Event::InboundProbe( - InboundProbeEvent::Error { - probe_id, - peer, - error: InboundProbeError::InboundRequest(error), - }, - )); + self.pending_out_events.append(&mut events); + if let Some(action) = action { + return Poll::Ready(action); + } } Poll::Ready(action) => return Poll::Ready(action.map_out(|_| unreachable!())), Poll::Pending => is_inner_pending = true, } - match self.schedule_probe.poll_unpin(cx) { - Poll::Ready(()) => { - self.schedule_probe.reset(self.config.retry_interval); - - let mut addresses: Vec<_> = - params.external_addresses().map(|r| r.addr).collect(); - addresses.extend(params.listened_addresses()); - - let probe_id = self.probe_id.next(); - match self.do_probe(probe_id, addresses) { - Ok(peer) => { - self.pending_out_events.push_back(Event::OutboundProbe( - OutboundProbeEvent::Request { probe_id, peer }, - )); - } - Err(error) => { - self.pending_out_events.push_back(Event::OutboundProbe( - OutboundProbeEvent::Error { - probe_id, - peer: None, - error, - }, - )); - self.handle_reported_status(NatStatus::Unknown); - } - } - } + match self.as_client().poll_auto_probe(params, cx) { + Poll::Ready(event) => self + .pending_out_events + .push_back(Event::OutboundProbe(event)), Poll::Pending if is_inner_pending => return Poll::Pending, Poll::Pending => {} } @@ -934,129 +471,16 @@ impl NetworkBehaviour for Behaviour { } } -// Filter dial addresses and replace demanded ip with the observed one. -fn filter_valid_addrs( - peer: PeerId, - demanded: Vec, - observed_remote_at: &Multiaddr, -) -> Vec { - // Skip if the observed address is a relay address. - if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) { - return Vec::new(); - } - let observed_ip = match observed_remote_at - .into_iter() - .find(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_))) - { - Some(ip) => ip, - None => return Vec::new(), - }; - let mut distinct = HashSet::new(); - demanded - .into_iter() - .filter_map(|addr| { - // Replace the demanded ip with the observed one. - let i = addr - .iter() - .position(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))?; - let mut addr = addr.replace(i, |_| Some(observed_ip.clone()))?; - - let is_valid = addr.iter().all(|proto| match proto { - Protocol::P2pCircuit => false, - Protocol::P2p(hash) => hash == peer.into(), - _ => true, - }); - - if !is_valid { - return None; - } - if !addr.iter().any(|p| matches!(p, Protocol::P2p(_))) { - addr.push(Protocol::P2p(peer.into())) - } - // Only collect distinct addresses. - distinct.insert(addr.clone()).then(|| addr) - }) - .collect() -} - -#[cfg(test)] -mod test { - use super::*; +type Action = NetworkBehaviourAction< + ::OutEvent, + ::ProtocolsHandler, +>; - use std::net::Ipv4Addr; - - fn random_ip<'a>() -> Protocol<'a> { - Protocol::Ip4(Ipv4Addr::new( - rand::random(), - rand::random(), - rand::random(), - rand::random(), - )) - } - fn random_port<'a>() -> Protocol<'a> { - Protocol::Tcp(rand::random()) - } - - #[test] - fn filter_addresses() { - let peer_id = PeerId::random(); - let observed_ip = random_ip(); - let observed_addr = Multiaddr::empty() - .with(observed_ip.clone()) - .with(random_port()) - .with(Protocol::P2p(peer_id.into())); - // Valid address with matching peer-id - let demanded_1 = Multiaddr::empty() - .with(random_ip()) - .with(random_port()) - .with(Protocol::P2p(peer_id.into())); - // Invalid because peer_id does not match - let demanded_2 = Multiaddr::empty() - .with(random_ip()) - .with(random_port()) - .with(Protocol::P2p(PeerId::random().into())); - // Valid address without peer-id - let demanded_3 = Multiaddr::empty().with(random_ip()).with(random_port()); - // Invalid because relayed - let demanded_4 = Multiaddr::empty() - .with(random_ip()) - .with(random_port()) - .with(Protocol::P2p(PeerId::random().into())) - .with(Protocol::P2pCircuit) - .with(Protocol::P2p(peer_id.into())); - let demanded = vec![ - demanded_1.clone(), - demanded_2, - demanded_3.clone(), - demanded_4, - ]; - let filtered = filter_valid_addrs(peer_id, demanded, &observed_addr); - let expected_1 = demanded_1 - .replace(0, |_| Some(observed_ip.clone())) - .unwrap(); - let expected_2 = demanded_3 - .replace(0, |_| Some(observed_ip)) - .unwrap() - .with(Protocol::P2p(peer_id.into())); - assert_eq!(filtered, vec![expected_1, expected_2]); - } - - #[test] - fn skip_relayed_addr() { - let peer_id = PeerId::random(); - let observed_ip = random_ip(); - // Observed address is relayed. - let observed_addr = Multiaddr::empty() - .with(observed_ip.clone()) - .with(random_port()) - .with(Protocol::P2p(PeerId::random().into())) - .with(Protocol::P2pCircuit) - .with(Protocol::P2p(peer_id.into())); - let demanded = Multiaddr::empty() - .with(random_ip()) - .with(random_port()) - .with(Protocol::P2p(peer_id.into())); - let filtered = filter_valid_addrs(peer_id, vec![demanded], &observed_addr); - assert!(filtered.is_empty()); - } +// Trait implemented for `AsClient` as `AsServer` to handle events from the inner [`RequestResponse`] Protocol. +trait HandleInnerEvent { + fn handle_event( + &mut self, + params: &mut impl PollParameters, + event: RequestResponseEvent, + ) -> (VecDeque, Option); } diff --git a/protocols/autonat/src/behaviour/as_client.rs b/protocols/autonat/src/behaviour/as_client.rs new file mode 100644 index 00000000000..dba34da8f86 --- /dev/null +++ b/protocols/autonat/src/behaviour/as_client.rs @@ -0,0 +1,373 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use crate::ResponseError; + +use super::{ + Action, AutoNatCodec, Config, DialRequest, DialResponse, Event, HandleInnerEvent, NatStatus, + ProbeId, +}; +use futures::FutureExt; +use futures_timer::Delay; +use instant::Instant; +use libp2p_core::{Multiaddr, PeerId}; +use libp2p_request_response::{ + OutboundFailure, RequestId, RequestResponse, RequestResponseEvent, RequestResponseMessage, +}; +use libp2p_swarm::{AddressScore, NetworkBehaviourAction, PollParameters}; +use rand::{seq::SliceRandom, thread_rng}; +use std::{ + collections::{HashMap, VecDeque}, + task::{Context, Poll}, + time::Duration, +}; + +/// Outbound probe failed or was aborted. +#[derive(Debug, Clone, PartialEq)] +pub enum OutboundProbeError { + /// Probe was aborted because no server is known, or all servers + /// are throttled through [`Config::throttle_server_period`]. + NoServer, + /// Probe was aborted because the local peer has no listening or + /// external addresses. + NoAddresses, + /// Sending the dial-back request or receiving a response failed. + OutboundRequest(OutboundFailure), + /// The server refused or failed to dial us. + Response(ResponseError), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum OutboundProbeEvent { + /// A dial-back request was sent to a remote peer. + Request { + probe_id: ProbeId, + /// Peer to which the request is sent. + peer: PeerId, + }, + /// The remote successfully dialed one of our addresses. + Response { + probe_id: ProbeId, + /// Id of the peer that sent the response. + peer: PeerId, + /// The address at which the remote succeeded to dial us. + address: Multiaddr, + }, + /// The outbound request failed, was rejected, or the remote could dial + /// none of our addresses. + Error { + probe_id: ProbeId, + /// Id of the peer used for the probe. + /// `None` if the probe was aborted due to no addresses or no qualified server. + peer: Option, + error: OutboundProbeError, + }, +} + +/// View over [`Behaviour`] in a client role. +pub struct AsClient<'a> { + pub inner: &'a mut RequestResponse, + pub local_peer_id: PeerId, + pub config: &'a Config, + pub connected: &'a HashMap, + pub probe_id: &'a mut ProbeId, + + pub servers: &'a Vec, + pub throttled_servers: &'a mut Vec<(PeerId, Instant)>, + + pub nat_status: &'a mut NatStatus, + pub confidence: &'a mut usize, + + pub ongoing_outbound: &'a mut HashMap, + + pub last_probe: &'a mut Option, + pub schedule_probe: &'a mut Delay, +} + +impl<'a> HandleInnerEvent for AsClient<'a> { + fn handle_event( + &mut self, + params: &mut impl PollParameters, + event: RequestResponseEvent, + ) -> (VecDeque, Option) { + let mut events = VecDeque::new(); + let mut action = None; + match event { + RequestResponseEvent::Message { + peer, + message: + RequestResponseMessage::Response { + request_id, + response, + }, + } => { + log::debug!("Outbound dial-back request returned {:?}.", response); + + let probe_id = self + .ongoing_outbound + .remove(&request_id) + .expect("RequestId exists."); + + let event = match response.result.clone() { + Ok(address) => OutboundProbeEvent::Response { + probe_id, + peer, + address, + }, + Err(e) => OutboundProbeEvent::Error { + probe_id, + peer: Some(peer), + error: OutboundProbeError::Response(e), + }, + }; + events.push_back(Event::OutboundProbe(event)); + + if let Some(old) = self.handle_reported_status(response.result.clone().into()) { + events.push_back(Event::StatusChanged { + old, + new: self.nat_status.clone(), + }); + } + + if let Ok(address) = response.result { + // Update observed address score if it is finite. + let score = params + .external_addresses() + .find_map(|r| (r.addr == address).then(|| r.score)) + .unwrap_or(AddressScore::Finite(0)); + if let AddressScore::Finite(finite_score) = score { + action = Some(NetworkBehaviourAction::ReportObservedAddr { + address, + score: AddressScore::Finite(finite_score + 1), + }); + } + } + } + RequestResponseEvent::OutboundFailure { + peer, + error, + request_id, + } => { + log::debug!( + "Outbound Failure {} when on dial-back request to peer {}.", + error, + peer + ); + let probe_id = self + .ongoing_outbound + .remove(&request_id) + .unwrap_or_else(|| self.probe_id.next()); + + events.push_back(Event::OutboundProbe(OutboundProbeEvent::Error { + probe_id, + peer: Some(peer), + error: OutboundProbeError::OutboundRequest(error), + })); + + self.schedule_probe.reset(Duration::ZERO); + } + _ => {} + } + (events, action) + } +} + +impl<'a> AsClient<'a> { + pub fn poll_auto_probe( + &mut self, + params: &mut impl PollParameters, + cx: &mut Context<'_>, + ) -> Poll { + match self.schedule_probe.poll_unpin(cx) { + Poll::Ready(()) => { + self.schedule_probe.reset(self.config.retry_interval); + + let mut addresses: Vec<_> = params.external_addresses().map(|r| r.addr).collect(); + addresses.extend(params.listened_addresses()); + + let probe_id = self.probe_id.next(); + let event = match self.do_probe(probe_id, addresses) { + Ok(peer) => OutboundProbeEvent::Request { probe_id, peer }, + Err(error) => { + self.handle_reported_status(NatStatus::Unknown); + OutboundProbeEvent::Error { + probe_id, + peer: None, + error, + } + } + }; + Poll::Ready(event) + } + Poll::Pending => Poll::Pending, + } + } + + // An inbound connection can indicate that we are public; adjust the delay to the next probe. + pub fn on_inbound_connection(&mut self) { + if *self.confidence == self.config.confidence_max { + if self.nat_status.is_public() { + self.schedule_next_probe(self.config.refresh_interval * 2); + } else { + self.schedule_next_probe(self.config.refresh_interval / 5); + } + } + } + + pub fn on_new_address(&mut self) { + if !self.nat_status.is_public() { + // New address could be publicly reachable, trigger retry. + if *self.confidence > 0 { + *self.confidence -= 1; + } + self.schedule_next_probe(self.config.retry_interval); + } + } + + pub fn on_expired_address(&mut self, addr: &Multiaddr) { + if let NatStatus::Public(public_address) = self.nat_status { + if public_address == addr { + *self.confidence = 0; + *self.nat_status = NatStatus::Unknown; + self.schedule_next_probe(Duration::ZERO); + } + } + } + + // Select a random server for the probe. + fn random_server(&mut self) -> Option { + // Update list of throttled servers. + let i = self.throttled_servers.partition_point(|(_, time)| { + *time + self.config.throttle_server_period < Instant::now() + }); + self.throttled_servers.drain(..i); + + let mut servers: Vec<&PeerId> = self.servers.iter().collect(); + + if self.config.use_connected { + servers.extend(self.connected.iter().map(|(id, _)| id)); + } + + servers.retain(|s| !self.throttled_servers.iter().any(|(id, _)| s == &id)); + + servers.choose(&mut thread_rng()).map(|&&p| p) + } + + // Send a dial-request to a randomly selected server. + // Returns the server that is used in this probe. + // `Err` if there are no qualified servers or no addresses. + fn do_probe( + &mut self, + probe_id: ProbeId, + addresses: Vec, + ) -> Result { + let _ = self.last_probe.insert(Instant::now()); + if addresses.is_empty() { + log::debug!("Outbound dial-back request aborted: No dial-back addresses."); + return Err(OutboundProbeError::NoAddresses); + } + let server = match self.random_server() { + Some(s) => s, + None => { + log::debug!("Outbound dial-back request aborted: No qualified server."); + return Err(OutboundProbeError::NoServer); + } + }; + let request_id = self.inner.send_request( + &server, + DialRequest { + peer_id: self.local_peer_id, + addresses, + }, + ); + self.throttled_servers.push((server, Instant::now())); + log::debug!("Send dial-back request to peer {}.", server); + self.ongoing_outbound.insert(request_id, probe_id); + Ok(server) + } + + // Set the delay to the next probe based on the time of our last probe + // and the specified delay. + fn schedule_next_probe(&mut self, delay: Duration) { + let last_probe_instant = match self.last_probe { + Some(instant) => instant, + None => { + return; + } + }; + let schedule_next = *last_probe_instant + delay; + self.schedule_probe + .reset(schedule_next.saturating_duration_since(Instant::now())); + } + + // Adapt current confidence and NAT status to the status reported by the latest probe. + // Return the old status if it flipped. + fn handle_reported_status(&mut self, reported_status: NatStatus) -> Option { + self.schedule_next_probe(self.config.retry_interval); + + if matches!(reported_status, NatStatus::Unknown) { + return None; + } + + if reported_status == *self.nat_status { + if *self.confidence < self.config.confidence_max { + *self.confidence += 1; + } + // Delay with (usually longer) refresh-interval. + if *self.confidence >= self.config.confidence_max { + self.schedule_next_probe(self.config.refresh_interval); + } + return None; + } + + if reported_status.is_public() && self.nat_status.is_public() { + // Different address than the currently assumed public address was reported. + // Switch address, but don't report as flipped. + *self.nat_status = reported_status; + return None; + } + if *self.confidence > 0 { + // Reduce confidence but keep old status. + *self.confidence -= 1; + return None; + } + + log::debug!( + "Flipped assumed NAT status from {:?} to {:?}", + self.nat_status, + reported_status + ); + + let old_status = self.nat_status.clone(); + *self.nat_status = reported_status; + + Some(old_status) + } +} + +impl From> for NatStatus { + fn from(result: Result) -> Self { + match result { + Ok(addr) => NatStatus::Public(addr), + Err(ResponseError::DialError) => NatStatus::Private, + _ => NatStatus::Unknown, + } + } +} diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/behaviour/as_server.rs new file mode 100644 index 00000000000..af1c874da5c --- /dev/null +++ b/protocols/autonat/src/behaviour/as_server.rs @@ -0,0 +1,428 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::{ + Action, AutoNatCodec, Config, DialRequest, DialResponse, Event, HandleInnerEvent, ProbeId, + ResponseError, +}; +use instant::Instant; +use libp2p_core::{multiaddr::Protocol, Multiaddr, PeerId}; +use libp2p_request_response::{ + InboundFailure, RequestId, RequestResponse, RequestResponseEvent, RequestResponseMessage, + ResponseChannel, +}; +use libp2p_swarm::{ + dial_opts::{DialOpts, PeerCondition}, + DialError, NetworkBehaviour, NetworkBehaviourAction, PollParameters, +}; +use std::collections::{HashMap, HashSet, VecDeque}; + +/// Inbound probe failed. +#[derive(Debug, Clone, PartialEq)] +pub enum InboundProbeError { + /// Receiving the dial-back request or sending a response failed. + InboundRequest(InboundFailure), + /// We refused or failed to dial the client. + Response(ResponseError), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum InboundProbeEvent { + /// A dial-back request was received from a remote peer. + Request { + probe_id: ProbeId, + /// Peer that sent the request. + peer: PeerId, + /// The addresses that will be attempted to dial. + addresses: Vec, + }, + /// A dial request to the remote was successful. + Response { + probe_id: ProbeId, + /// Peer to which the response is sent. + peer: PeerId, + address: Multiaddr, + }, + /// The inbound request failed, was rejected, or none of the remote's + /// addresses could be dialed. + Error { + probe_id: ProbeId, + /// Peer that sent the dial-back request. + peer: PeerId, + error: InboundProbeError, + }, +} + +/// View over [`Behaviour`] in a server role. +pub struct AsServer<'a> { + pub inner: &'a mut RequestResponse, + pub config: &'a Config, + pub connected: &'a HashMap, + pub probe_id: &'a mut ProbeId, + + pub throttled_clients: &'a mut Vec<(PeerId, Instant)>, + + #[allow(clippy::type_complexity)] + pub ongoing_inbound: &'a mut HashMap< + PeerId, + ( + ProbeId, + RequestId, + Vec, + ResponseChannel, + ), + >, +} + +impl<'a> HandleInnerEvent for AsServer<'a> { + fn handle_event( + &mut self, + _params: &mut impl PollParameters, + event: RequestResponseEvent, + ) -> (VecDeque, Option) { + let mut events = VecDeque::new(); + let mut action = None; + match event { + RequestResponseEvent::Message { + peer, + message: + RequestResponseMessage::Request { + request_id, + request, + channel, + }, + } => { + let probe_id = self.probe_id.next(); + match self.resolve_inbound_request(peer, request) { + Ok(addrs) => { + log::debug!( + "Inbound dial request from Peer {} with dial-back addresses {:?}.", + peer, + addrs + ); + + self.ongoing_inbound + .insert(peer, (probe_id, request_id, addrs.clone(), channel)); + self.throttled_clients.push((peer, Instant::now())); + + events.push_back(Event::InboundProbe(InboundProbeEvent::Request { + probe_id, + peer, + addresses: addrs.clone(), + })); + + action = Some(NetworkBehaviourAction::Dial { + opts: DialOpts::peer_id(peer) + .condition(PeerCondition::Always) + .addresses(addrs) + .build(), + handler: self.inner.new_handler(), + }); + } + Err((status_text, error)) => { + log::debug!( + "Reject inbound dial request from peer {}: {}.", + peer, + status_text + ); + + let response = DialResponse { + result: Err(error.clone()), + status_text: Some(status_text), + }; + let _ = self.inner.send_response(channel, response); + + events.push_back(Event::InboundProbe(InboundProbeEvent::Error { + probe_id, + peer, + error: InboundProbeError::Response(error), + })); + } + } + } + RequestResponseEvent::InboundFailure { + peer, + error, + request_id, + } => { + log::debug!( + "Inbound Failure {} when on dial-back request from peer {}.", + error, + peer + ); + + let probe_id = match self.ongoing_inbound.get(&peer) { + Some((_, rq_id, _, _)) if *rq_id == request_id => { + self.ongoing_inbound.remove(&peer).unwrap().0 + } + _ => self.probe_id.next(), + }; + + events.push_back(Event::InboundProbe(InboundProbeEvent::Error { + probe_id, + peer, + error: InboundProbeError::InboundRequest(error), + })); + } + _ => {} + } + (events, action) + } +} + +impl<'a> AsServer<'a> { + pub fn on_outbound_connection( + &mut self, + peer: &PeerId, + address: &Multiaddr, + ) -> Option { + let (_, _, addrs, _) = self.ongoing_inbound.get(peer)?; + + // Check if the dialed address was among the requested addresses. + if !addrs.contains(address) { + return None; + } + + log::debug!( + "Dial-back to peer {} succeeded at addr {:?}.", + peer, + address + ); + + let (probe_id, _, _, channel) = self.ongoing_inbound.remove(peer).unwrap(); + let response = DialResponse { + result: Ok(address.clone()), + status_text: None, + }; + let _ = self.inner.send_response(channel, response); + + Some(InboundProbeEvent::Response { + probe_id, + peer: *peer, + address: address.clone(), + }) + } + + pub fn on_outbound_dial_error( + &mut self, + peer: Option, + error: &DialError, + ) -> Option { + let (probe_id, _, _, channel) = peer.and_then(|p| self.ongoing_inbound.remove(&p))?; + log::debug!( + "Dial-back to peer {} failed with error {:?}.", + peer.unwrap(), + error + ); + let response_error = ResponseError::DialError; + let response = DialResponse { + result: Err(response_error.clone()), + status_text: Some("dial failed".to_string()), + }; + let _ = self.inner.send_response(channel, response); + + Some(InboundProbeEvent::Error { + probe_id, + peer: peer.expect("PeerId is present."), + error: InboundProbeError::Response(response_error), + }) + } + + // Validate the inbound request and collect the addresses to be dialed. + fn resolve_inbound_request( + &mut self, + sender: PeerId, + request: DialRequest, + ) -> Result, (String, ResponseError)> { + // Update list of throttled clients. + let i = self.throttled_clients.partition_point(|(_, time)| { + *time + self.config.throttle_clients_period < Instant::now() + }); + self.throttled_clients.drain(..i); + + if request.peer_id != sender { + let status_text = "peer id mismatch".to_string(); + return Err((status_text, ResponseError::BadRequest)); + } + + if self.ongoing_inbound.contains_key(&sender) { + let status_text = "dial-back already ongoing".to_string(); + return Err((status_text, ResponseError::DialRefused)); + } + + if self.throttled_clients.len() >= self.config.throttle_clients_global_max { + let status_text = "too many total dials".to_string(); + return Err((status_text, ResponseError::DialRefused)); + } + + let throttled_for_client = self + .throttled_clients + .iter() + .filter(|(p, _)| p == &sender) + .count(); + + if throttled_for_client >= self.config.throttle_clients_peer_max { + let status_text = "too many dials for peer".to_string(); + return Err((status_text, ResponseError::DialRefused)); + } + + let observed_addr = self + .connected + .get(&sender) + .expect("We are connected to the peer."); + + let mut addrs = Self::filter_valid_addrs(sender, request.addresses, observed_addr); + addrs.truncate(self.config.max_peer_addresses); + + if addrs.is_empty() { + let status_text = "no dialable addresses".to_string(); + return Err((status_text, ResponseError::DialError)); + } + + Ok(addrs) + } + + // Filter dial addresses and replace demanded ip with the observed one. + fn filter_valid_addrs( + peer: PeerId, + demanded: Vec, + observed_remote_at: &Multiaddr, + ) -> Vec { + // Skip if the observed address is a relay address. + if observed_remote_at.iter().any(|p| p == Protocol::P2pCircuit) { + return Vec::new(); + } + let observed_ip = match observed_remote_at + .into_iter() + .find(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_))) + { + Some(ip) => ip, + None => return Vec::new(), + }; + let mut distinct = HashSet::new(); + demanded + .into_iter() + .filter_map(|addr| { + // Replace the demanded ip with the observed one. + let i = addr + .iter() + .position(|p| matches!(p, Protocol::Ip4(_) | Protocol::Ip6(_)))?; + let mut addr = addr.replace(i, |_| Some(observed_ip.clone()))?; + + let is_valid = addr.iter().all(|proto| match proto { + Protocol::P2pCircuit => false, + Protocol::P2p(hash) => hash == peer.into(), + _ => true, + }); + + if !is_valid { + return None; + } + if !addr.iter().any(|p| matches!(p, Protocol::P2p(_))) { + addr.push(Protocol::P2p(peer.into())) + } + // Only collect distinct addresses. + distinct.insert(addr.clone()).then(|| addr) + }) + .collect() + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::net::Ipv4Addr; + + fn random_ip<'a>() -> Protocol<'a> { + Protocol::Ip4(Ipv4Addr::new( + rand::random(), + rand::random(), + rand::random(), + rand::random(), + )) + } + fn random_port<'a>() -> Protocol<'a> { + Protocol::Tcp(rand::random()) + } + + #[test] + fn filter_addresses() { + let peer_id = PeerId::random(); + let observed_ip = random_ip(); + let observed_addr = Multiaddr::empty() + .with(observed_ip.clone()) + .with(random_port()) + .with(Protocol::P2p(peer_id.into())); + // Valid address with matching peer-id + let demanded_1 = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(peer_id.into())); + // Invalid because peer_id does not match + let demanded_2 = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(PeerId::random().into())); + // Valid address without peer-id + let demanded_3 = Multiaddr::empty().with(random_ip()).with(random_port()); + // Invalid because relayed + let demanded_4 = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(PeerId::random().into())) + .with(Protocol::P2pCircuit) + .with(Protocol::P2p(peer_id.into())); + let demanded = vec![ + demanded_1.clone(), + demanded_2, + demanded_3.clone(), + demanded_4, + ]; + let filtered = AsServer::filter_valid_addrs(peer_id, demanded, &observed_addr); + let expected_1 = demanded_1 + .replace(0, |_| Some(observed_ip.clone())) + .unwrap(); + let expected_2 = demanded_3 + .replace(0, |_| Some(observed_ip)) + .unwrap() + .with(Protocol::P2p(peer_id.into())); + assert_eq!(filtered, vec![expected_1, expected_2]); + } + + #[test] + fn skip_relayed_addr() { + let peer_id = PeerId::random(); + let observed_ip = random_ip(); + // Observed address is relayed. + let observed_addr = Multiaddr::empty() + .with(observed_ip.clone()) + .with(random_port()) + .with(Protocol::P2p(PeerId::random().into())) + .with(Protocol::P2pCircuit) + .with(Protocol::P2p(peer_id.into())); + let demanded = Multiaddr::empty() + .with(random_ip()) + .with(random_port()) + .with(Protocol::P2p(peer_id.into())); + let filtered = AsServer::filter_valid_addrs(peer_id, vec![demanded], &observed_addr); + assert!(filtered.is_empty()); + } +} From 4ee4eb08d62dda026a15637314bc8e410b007c84 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 2 Jan 2022 01:48:48 +0100 Subject: [PATCH 64/69] protocols/autonat: Use current Repo license --- protocols/autonat/build.rs | 2 +- protocols/autonat/src/behaviour.rs | 2 +- protocols/autonat/src/behaviour/as_client.rs | 2 +- protocols/autonat/src/behaviour/as_server.rs | 2 +- protocols/autonat/src/lib.rs | 2 +- protocols/autonat/src/protocol.rs | 2 +- protocols/autonat/tests/test_client.rs | 2 +- protocols/autonat/tests/test_server.rs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/protocols/autonat/build.rs b/protocols/autonat/build.rs index 56c7b20121a..d3714fdec14 100644 --- a/protocols/autonat/build.rs +++ b/protocols/autonat/build.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index e354fa49582..a25815c73ae 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/protocols/autonat/src/behaviour/as_client.rs b/protocols/autonat/src/behaviour/as_client.rs index dba34da8f86..f4b018d9b69 100644 --- a/protocols/autonat/src/behaviour/as_client.rs +++ b/protocols/autonat/src/behaviour/as_client.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/behaviour/as_server.rs index af1c874da5c..8a403dcb633 100644 --- a/protocols/autonat/src/behaviour/as_server.rs +++ b/protocols/autonat/src/behaviour/as_server.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 3e4fa9ae920..d55ab3acc14 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index 4ee10ebf083..802fc40bac6 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/protocols/autonat/tests/test_client.rs b/protocols/autonat/tests/test_client.rs index 17234c3dae1..fcfb8922f2a 100644 --- a/protocols/autonat/tests/test_client.rs +++ b/protocols/autonat/tests/test_client.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/protocols/autonat/tests/test_server.rs b/protocols/autonat/tests/test_server.rs index 0dea69d872c..519bc475d20 100644 --- a/protocols/autonat/tests/test_server.rs +++ b/protocols/autonat/tests/test_server.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), From f8e8805e4c7a9dd983b73e552a1c60ab6358f0f7 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Sun, 2 Jan 2022 01:56:24 +0100 Subject: [PATCH 65/69] protocols/autonat/behaviour: fix broken_intra_doc_links --- protocols/autonat/src/behaviour/as_client.rs | 2 +- protocols/autonat/src/behaviour/as_server.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/autonat/src/behaviour/as_client.rs b/protocols/autonat/src/behaviour/as_client.rs index f4b018d9b69..b4d58680b97 100644 --- a/protocols/autonat/src/behaviour/as_client.rs +++ b/protocols/autonat/src/behaviour/as_client.rs @@ -81,7 +81,7 @@ pub enum OutboundProbeEvent { }, } -/// View over [`Behaviour`] in a client role. +/// View over [`super::Behaviour`] in a client role. pub struct AsClient<'a> { pub inner: &'a mut RequestResponse, pub local_peer_id: PeerId, diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/behaviour/as_server.rs index 8a403dcb633..e9335cbfbef 100644 --- a/protocols/autonat/src/behaviour/as_server.rs +++ b/protocols/autonat/src/behaviour/as_server.rs @@ -70,7 +70,7 @@ pub enum InboundProbeEvent { }, } -/// View over [`Behaviour`] in a server role. +/// View over [`super::Behaviour`] in a server role. pub struct AsServer<'a> { pub inner: &'a mut RequestResponse, pub config: &'a Config, From 2a73082edbd32370381a0b69ed9d77fc9dfe5b53 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Wed, 12 Jan 2022 22:36:15 +0100 Subject: [PATCH 66/69] protocols/autonat/behaviour: fix observed addrs Track all non-relayed addresses of the remote. On inbound dial-back request select one of these addresses as observed one, reject dial-back if there are none (i.g. all connections are relayed). --- protocols/autonat/src/behaviour.rs | 51 +++++++++++++------- protocols/autonat/src/behaviour/as_client.rs | 4 +- protocols/autonat/src/behaviour/as_server.rs | 13 +++-- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/behaviour.rs index a25815c73ae..8a8e1d1eabe 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/behaviour.rs @@ -187,9 +187,9 @@ pub struct Behaviour { // Ongoing outbound probes and mapped to the inner request id. ongoing_outbound: HashMap, - // Connected peers with their observed address. - // These peers may be used as servers for dial-requests. - connected: HashMap, + // Connected peers with the observed address of each connection. + // If the endpoint of a connection is relayed, the observed address is `None`. + connected: HashMap>>, // Used servers in recent outbound probes that are throttled through Config::throttle_server_period. throttled_servers: Vec<(PeerId, Instant)>, @@ -306,8 +306,13 @@ impl NetworkBehaviour for Behaviour { ) { self.inner .inject_connection_established(peer, conn, endpoint, failed_addresses); - self.connected - .insert(*peer, endpoint.get_remote_address().clone()); + let connections = self.connected.entry(*peer).or_default(); + let addr = if endpoint.is_relayed() { + None + } else { + Some(endpoint.get_remote_address().clone()) + }; + connections.insert(*conn, addr); match endpoint { ConnectedPoint::Dialer { address } => { @@ -320,6 +325,19 @@ impl NetworkBehaviour for Behaviour { } } + fn inject_connection_closed( + &mut self, + peer: &PeerId, + conn: &ConnectionId, + endpoint: &ConnectedPoint, + handler: ::Handler, + ) { + self.inner + .inject_connection_closed(peer, conn, endpoint, handler); + let connections = self.connected.get_mut(peer).expect("Peer is connected."); + connections.remove(conn); + } + fn inject_dial_failure( &mut self, peer: Option, @@ -347,8 +365,16 @@ impl NetworkBehaviour for Behaviour { ) { self.inner.inject_address_change(peer, conn, old, new); - self.connected - .insert(*peer, new.get_remote_address().clone()); + if old.is_relayed() && new.is_relayed() { + return; + } + let connections = self.connected.get_mut(peer).expect("Peer is connected."); + let addr = if new.is_relayed() { + None + } else { + Some(new.get_remote_address().clone()) + }; + connections.insert(*conn, addr); } fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { @@ -428,17 +454,6 @@ impl NetworkBehaviour for Behaviour { self.inner.inject_connected(peer) } - fn inject_connection_closed( - &mut self, - peer: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - handler: ::Handler, - ) { - self.inner - .inject_connection_closed(peer, conn, endpoint, handler); - } - fn inject_event( &mut self, peer_id: PeerId, diff --git a/protocols/autonat/src/behaviour/as_client.rs b/protocols/autonat/src/behaviour/as_client.rs index b4d58680b97..de6a00d5629 100644 --- a/protocols/autonat/src/behaviour/as_client.rs +++ b/protocols/autonat/src/behaviour/as_client.rs @@ -27,7 +27,7 @@ use super::{ use futures::FutureExt; use futures_timer::Delay; use instant::Instant; -use libp2p_core::{Multiaddr, PeerId}; +use libp2p_core::{connection::ConnectionId, Multiaddr, PeerId}; use libp2p_request_response::{ OutboundFailure, RequestId, RequestResponse, RequestResponseEvent, RequestResponseMessage, }; @@ -86,7 +86,7 @@ pub struct AsClient<'a> { pub inner: &'a mut RequestResponse, pub local_peer_id: PeerId, pub config: &'a Config, - pub connected: &'a HashMap, + pub connected: &'a HashMap>>, pub probe_id: &'a mut ProbeId, pub servers: &'a Vec, diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/behaviour/as_server.rs index e9335cbfbef..0dcfab950f5 100644 --- a/protocols/autonat/src/behaviour/as_server.rs +++ b/protocols/autonat/src/behaviour/as_server.rs @@ -23,7 +23,7 @@ use super::{ ResponseError, }; use instant::Instant; -use libp2p_core::{multiaddr::Protocol, Multiaddr, PeerId}; +use libp2p_core::{connection::ConnectionId, multiaddr::Protocol, Multiaddr, PeerId}; use libp2p_request_response::{ InboundFailure, RequestId, RequestResponse, RequestResponseEvent, RequestResponseMessage, ResponseChannel, @@ -74,7 +74,7 @@ pub enum InboundProbeEvent { pub struct AsServer<'a> { pub inner: &'a mut RequestResponse, pub config: &'a Config, - pub connected: &'a HashMap, + pub connected: &'a HashMap>>, pub probe_id: &'a mut ProbeId, pub throttled_clients: &'a mut Vec<(PeerId, Instant)>, @@ -283,10 +283,17 @@ impl<'a> AsServer<'a> { return Err((status_text, ResponseError::DialRefused)); } + // Obtain an observed address from non-relayed connections. let observed_addr = self .connected .get(&sender) - .expect("We are connected to the peer."); + .expect("Peer is connected.") + .values() + .find_map(|a| a.as_ref()) + .ok_or_else(|| { + let status_text = "no dial-request over relayed connections".to_string(); + (status_text, ResponseError::DialError) + })?; let mut addrs = Self::filter_valid_addrs(sender, request.addresses, observed_addr); addrs.truncate(self.config.max_peer_addresses); From f238adfe142db86aec7ae9df69d1d4daf377120b Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 13 Jan 2022 16:58:45 +0100 Subject: [PATCH 67/69] protocols/autonat/src/protocol: Use ResponseStatus::from_i32 --- protocols/autonat/src/protocol.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index 802fc40bac6..3c8a5e1386c 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -191,17 +191,19 @@ impl From for i32 { } } -impl TryFrom for ResponseError { +impl TryFrom for ResponseError { type Error = io::Error; - fn try_from(value: i32) -> Result { + fn try_from(value: structs_proto::message::ResponseStatus) -> Result { match value { - 100 => Ok(ResponseError::DialError), - 101 => Ok(ResponseError::DialRefused), - 200 => Ok(ResponseError::BadRequest), - 300 => Ok(ResponseError::InternalError), - _ => { - log::debug!("Received response with invalid status code."); + structs_proto::message::ResponseStatus::EDialError => Ok(ResponseError::DialError), + structs_proto::message::ResponseStatus::EDialRefused => Ok(ResponseError::DialRefused), + structs_proto::message::ResponseStatus::EBadRequest => Ok(ResponseError::BadRequest), + structs_proto::message::ResponseStatus::EInternalError => { + Ok(ResponseError::InternalError) + } + structs_proto::message::ResponseStatus::Ok => { + log::debug!("Received response with status code OK but expected error."); Err(io::Error::new( io::ErrorKind::InvalidData, "invalid response error type", @@ -227,10 +229,12 @@ impl DialResponse { Ok(match msg.dial_response { Some(structs_proto::message::DialResponse { - status: Some(0), + status: Some(status), status_text, addr: Some(addr), - }) => { + }) if structs_proto::message::ResponseStatus::from_i32(status) + == Some(structs_proto::message::ResponseStatus::Ok) => + { let addr = Multiaddr::try_from(addr) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; Self { @@ -244,7 +248,11 @@ impl DialResponse { addr: None, }) => Self { status_text, - result: Err(ResponseError::try_from(status)?), + result: Err(ResponseError::try_from( + structs_proto::message::ResponseStatus::from_i32(status).ok_or( + io::Error::new(io::ErrorKind::InvalidData, "invalid response status code"), + )?, + )?), }, _ => { log::debug!("Received malformed response message."); From 8f6a1a091e03a6c9342480998f0731eb20d6d248 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 13 Jan 2022 18:49:03 +0100 Subject: [PATCH 68/69] protocols/autonat: override dial concurrency factor --- protocols/autonat/src/behaviour/as_server.rs | 6 +++++- protocols/autonat/tests/test_server.rs | 11 +++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/behaviour/as_server.rs index 0dcfab950f5..24ffb443deb 100644 --- a/protocols/autonat/src/behaviour/as_server.rs +++ b/protocols/autonat/src/behaviour/as_server.rs @@ -32,7 +32,10 @@ use libp2p_swarm::{ dial_opts::{DialOpts, PeerCondition}, DialError, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; -use std::collections::{HashMap, HashSet, VecDeque}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + num::NonZeroU8, +}; /// Inbound probe failed. #[derive(Debug, Clone, PartialEq)] @@ -131,6 +134,7 @@ impl<'a> HandleInnerEvent for AsServer<'a> { action = Some(NetworkBehaviourAction::Dial { opts: DialOpts::peer_id(peer) .condition(PeerCondition::Always) + .override_dial_concurrency_factor(NonZeroU8::new(1).expect("1 > 0")) .addresses(addrs) .build(), handler: self.inner.new_handler(), diff --git a/protocols/autonat/tests/test_server.rs b/protocols/autonat/tests/test_server.rs index 519bc475d20..b96adaf78a4 100644 --- a/protocols/autonat/tests/test_server.rs +++ b/protocols/autonat/tests/test_server.rs @@ -31,20 +31,15 @@ use libp2p_autonat::{ Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, ResponseError, }; use libp2p_core::ConnectedPoint; -use libp2p_swarm::{DialError, SwarmBuilder}; -use std::{ - num::{NonZeroU32, NonZeroU8}, - time::Duration, -}; +use libp2p_swarm::DialError; +use std::{num::NonZeroU32, time::Duration}; async fn init_swarm(config: Config) -> Swarm { let keypair = Keypair::generate_ed25519(); let local_id = PeerId::from_public_key(&keypair.public()); let transport = development_transport(keypair).await.unwrap(); let behaviour = Behaviour::new(local_id, config); - SwarmBuilder::new(transport, behaviour, local_id) - .dial_concurrency_factor(NonZeroU8::new(1).unwrap()) - .build() + Swarm::new(transport, behaviour, local_id) } async fn init_server(config: Option) -> (Swarm, PeerId, Multiaddr) { From 4071597be50797480a82ba74811cd33dce920124 Mon Sep 17 00:00:00 2001 From: elenaf9 Date: Thu, 13 Jan 2022 18:55:55 +0100 Subject: [PATCH 69/69] protocols/autonat/protocol: fix clippy::or_fun_call --- protocols/autonat/src/protocol.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/protocol.rs index 3c8a5e1386c..13647a4f7af 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/protocol.rs @@ -249,9 +249,9 @@ impl DialResponse { }) => Self { status_text, result: Err(ResponseError::try_from( - structs_proto::message::ResponseStatus::from_i32(status).ok_or( - io::Error::new(io::ErrorKind::InvalidData, "invalid response status code"), - )?, + structs_proto::message::ResponseStatus::from_i32(status).ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "invalid response status code") + })?, )?), }, _ => {