From 3bd66022879e24f069e4dadfd8b2ebfbe8ccdd06 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 16 Apr 2021 16:38:47 +0200 Subject: [PATCH 001/108] protocols/relay: Implement circuit relay v2 protocol This commit adds an implementation for the circuit relay v2 protocol to be used as a relay server, i.e. it supports incoming HOP requests and outgoing STOP requests. Future commits will add support for clients, i.e. outgoing HOP requests and incoming STOP requests. The existing circuit relay v1 protocol implementation is moved to protocols/relay/src/v1. --- protocols/relay/Cargo.toml | 1 + protocols/relay/build.rs | 3 +- .../relay/examples/{relay.rs => relay_v1.rs} | 4 +- protocols/relay/examples/relay_v2.rs | 121 ++++ protocols/relay/src/lib.rs | 106 +-- protocols/relay/src/v1.rs | 126 ++++ protocols/relay/src/{ => v1}/behaviour.rs | 44 +- protocols/relay/src/v1/connection.rs | 95 +++ .../relay/src/{protocol => v1}/copy_future.rs | 0 protocols/relay/src/{ => v1}/handler.rs | 16 +- protocols/relay/src/{ => v1}/message.proto | 2 +- protocols/relay/src/{ => v1}/protocol.rs | 96 +-- .../src/{ => v1}/protocol/incoming_dst_req.rs | 19 +- .../{ => v1}/protocol/incoming_relay_req.rs | 6 +- .../relay/src/{ => v1}/protocol/listen.rs | 9 +- .../src/{ => v1}/protocol/outgoing_dst_req.rs | 7 +- .../{ => v1}/protocol/outgoing_relay_req.rs | 9 +- protocols/relay/src/{ => v1}/transport.rs | 30 +- protocols/relay/src/v2.rs | 30 + protocols/relay/src/v2/behaviour.rs | 565 +++++++++++++++ protocols/relay/src/v2/copy_future.rs | 149 ++++ protocols/relay/src/v2/handler.rs | 680 ++++++++++++++++++ protocols/relay/src/v2/message.proto | 63 ++ protocols/relay/src/v2/protocol.rs | 27 + .../relay/src/v2/protocol/inbound_hop.rs | 279 +++++++ .../relay/src/v2/protocol/outbound_stop.rs | 188 +++++ protocols/relay/tests/{lib.rs => v1.rs} | 45 +- 27 files changed, 2446 insertions(+), 274 deletions(-) rename protocols/relay/examples/{relay.rs => relay_v1.rs} (96%) create mode 100644 protocols/relay/examples/relay_v2.rs create mode 100644 protocols/relay/src/v1.rs rename protocols/relay/src/{ => v1}/behaviour.rs (97%) create mode 100644 protocols/relay/src/v1/connection.rs rename protocols/relay/src/{protocol => v1}/copy_future.rs (100%) rename protocols/relay/src/{ => v1}/handler.rs (98%) rename protocols/relay/src/{ => v1}/message.proto (98%) rename protocols/relay/src/{ => v1}/protocol.rs (65%) rename protocols/relay/src/{ => v1}/protocol/incoming_dst_req.rs (92%) rename protocols/relay/src/{ => v1}/protocol/incoming_relay_req.rs (97%) rename protocols/relay/src/{ => v1}/protocol/listen.rs (95%) rename protocols/relay/src/{ => v1}/protocol/outgoing_dst_req.rs (97%) rename protocols/relay/src/{ => v1}/protocol/outgoing_relay_req.rs (96%) rename protocols/relay/src/{ => v1}/transport.rs (96%) create mode 100644 protocols/relay/src/v2.rs create mode 100644 protocols/relay/src/v2/behaviour.rs create mode 100644 protocols/relay/src/v2/copy_future.rs create mode 100644 protocols/relay/src/v2/handler.rs create mode 100644 protocols/relay/src/v2/message.proto create mode 100644 protocols/relay/src/v2/protocol.rs create mode 100644 protocols/relay/src/v2/protocol/inbound_hop.rs create mode 100644 protocols/relay/src/v2/protocol/outbound_stop.rs rename protocols/relay/tests/{lib.rs => v1.rs} (97%) diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 10d957dbe0c..9cd106dcab6 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -21,6 +21,7 @@ pin-project = "1" prost = "0.7" rand = "0.7" smallvec = "1.6.1" +static_assertions = "1" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1" wasm-timer = "0.2" diff --git a/protocols/relay/build.rs b/protocols/relay/build.rs index cd7bd3deef6..a6776c9fab8 100644 --- a/protocols/relay/build.rs +++ b/protocols/relay/build.rs @@ -19,5 +19,6 @@ // DEALINGS IN THE SOFTWARE. fn main() { - prost_build::compile_protos(&["src/message.proto"], &["src"]).unwrap(); + prost_build::compile_protos(&["src/v1/message.proto"], &["src/v1"]).unwrap(); + prost_build::compile_protos(&["src/v2/message.proto"], &["src/v2"]).unwrap(); } diff --git a/protocols/relay/examples/relay.rs b/protocols/relay/examples/relay_v1.rs similarity index 96% rename from protocols/relay/examples/relay.rs rename to protocols/relay/examples/relay_v1.rs index 2e22cdff7f0..4dcfc9a8913 100644 --- a/protocols/relay/examples/relay.rs +++ b/protocols/relay/examples/relay_v1.rs @@ -22,7 +22,7 @@ use futures::executor::block_on; use futures::stream::StreamExt; use libp2p::core::upgrade; use libp2p::plaintext; -use libp2p::relay::RelayConfig; +use libp2p::relay::v1::RelayConfig; use libp2p::tcp::TcpConfig; use libp2p::Transport; use libp2p::{identity, PeerId, Swarm}; @@ -45,7 +45,7 @@ fn main() -> Result<(), Box> { ..Default::default() }; let (relay_wrapped_transport, relay_behaviour) = - libp2p_relay::new_transport_and_behaviour(relay_config, tcp_transport); + libp2p_relay::v1::new_transport_and_behaviour(relay_config, tcp_transport); let plaintext = plaintext::PlainText2Config { local_public_key: local_key.public(), diff --git a/protocols/relay/examples/relay_v2.rs b/protocols/relay/examples/relay_v2.rs new file mode 100644 index 00000000000..87072fdf1f8 --- /dev/null +++ b/protocols/relay/examples/relay_v2.rs @@ -0,0 +1,121 @@ +// 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"), +// 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::executor::block_on; +use futures::stream::StreamExt; +use libp2p::core::upgrade; +use libp2p::identify::{Identify, IdentifyEvent, IdentifyConfig}; +use libp2p::noise; +use libp2p::ping::{Ping, PingEvent, PingConfig}; +use libp2p::relay::v2::{Relay, RelayEvent}; +use libp2p::tcp::TcpConfig; +use libp2p::Transport; +use libp2p::{identity, NetworkBehaviour, PeerId, Swarm}; +use std::error::Error; +use std::task::{Context, Poll}; + +fn main() -> Result<(), Box> { + env_logger::init(); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + println!("Local peer id: {:?}", local_peer_id); + + let tcp_transport = TcpConfig::new(); + + let noise_keys = noise::Keypair::::new() + .into_authentic(&local_key) + .expect("Signing libp2p-noise static DH keypair failed."); + + let transport = tcp_transport + .upgrade(upgrade::Version::V1) + .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) + .multiplex(libp2p_yamux::YamuxConfig::default()) + .boxed(); + + #[derive(NetworkBehaviour)] + #[behaviour(out_event = "Event", event_process = false)] + struct Behaviour { + relay: Relay, + ping: Ping, + identify: Identify, + } + + #[derive(Debug)] + enum Event { + Ping(PingEvent), + Identify(IdentifyEvent), + Relay(RelayEvent), + } + + impl From for Event { + fn from(e: PingEvent) -> Self { + Event::Ping(e) + } + } + + impl From for Event { + fn from(e: IdentifyEvent) -> Self { + Event::Identify(e) + } + } + + impl From for Event { + fn from(e: RelayEvent) -> Self { + Event::Relay(e) + } + } + + let behaviour = Behaviour { + relay: Relay::new(local_peer_id, Default::default()), + ping: Ping::new(PingConfig::new()), + identify: Identify::new(IdentifyConfig::new( + "/TODO/0.0.1".to_string(), + local_key.public(), + )), + }; + + let mut swarm = Swarm::new(transport, behaviour, local_peer_id); + + // Listen on all interfaces and whatever port the OS assigns + swarm.listen_on("/ip4/0.0.0.0/tcp/4001".parse()?)?; + + let mut listening = false; + block_on(futures::future::poll_fn(move |cx: &mut Context<'_>| { + loop { + match swarm.poll_next_unpin(cx) { + Poll::Ready(Some(Event::Relay(event))) => println!("{:?}", event), + Poll::Ready(Some(_)) => {}, + Poll::Ready(None) => return Poll::Ready(Ok(())), + Poll::Pending => { + if !listening { + for addr in Swarm::listeners(&swarm) { + println!("Listening on {:?}", addr); + listening = true; + } + } + break; + } + } + } + Poll::Pending + })) +} diff --git a/protocols/relay/src/lib.rs b/protocols/relay/src/lib.rs index 7f81860534d..391051ed22b 100644 --- a/protocols/relay/src/lib.rs +++ b/protocols/relay/src/lib.rs @@ -18,107 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Implementation of the [libp2p circuit relay -//! specification](https://github.com/libp2p/specs/tree/master/relay). -//! -//! ## Example -//! -//! ```rust -//! # use libp2p_core::transport::memory::MemoryTransport; -//! # use libp2p_relay::{RelayConfig, new_transport_and_behaviour}; -//! # use libp2p_swarm::Swarm; -//! # use libp2p_core::{identity, Multiaddr, multiaddr::Protocol, PeerId, upgrade, Transport}; -//! # use libp2p_yamux::YamuxConfig; -//! # use libp2p_plaintext::PlainText2Config; -//! # use std::convert::TryInto; -//! # use std::str::FromStr; -//! # -//! # let local_key = identity::Keypair::generate_ed25519(); -//! # let local_public_key = local_key.public(); -//! # let local_peer_id = local_public_key.clone().into_peer_id(); -//! # let plaintext = PlainText2Config { -//! # local_public_key: local_public_key.clone(), -//! # }; -//! # -//! let (relay_transport, relay_behaviour) = new_transport_and_behaviour( -//! RelayConfig::default(), -//! MemoryTransport::default(), -//! ); -//! -//! let transport = relay_transport -//! .upgrade(upgrade::Version::V1) -//! .authenticate(plaintext) -//! .multiplex(YamuxConfig::default()) -//! .boxed(); -//! -//! let mut swarm = Swarm::new(transport, relay_behaviour, local_peer_id); -//! -//! let relay_addr = Multiaddr::from_str("/memory/1234").unwrap() -//! .with(Protocol::P2p(PeerId::random().into())) -//! .with(Protocol::P2pCircuit); -//! let dst_addr = relay_addr.clone().with(Protocol::Memory(5678)); -//! -//! // Listen for incoming connections via relay node (1234). -//! swarm.listen_on(relay_addr).unwrap(); -//! -//! // Dial node (5678) via relay node (1234). -//! swarm.dial_addr(dst_addr).unwrap(); -//! ``` -//! -//! ## Terminology -//! -//! ### Entities -//! -//! - **Source**: The node initiating a connection via a *relay* to a *destination*. -//! -//! - **Relay**: The node being asked by a *source* to relay to a *destination*. -//! -//! - **Destination**: The node contacted by the *source* via the *relay*. -//! -//! ### Messages -//! -//! - **Outgoing relay request**: The request sent by a *source* to a *relay*. -//! -//! - **Incoming relay request**: The request received by a *relay* from a *source*. -//! -//! - **Outgoing destination request**: The request sent by a *relay* to a *destination*. -//! -//! - **Incoming destination request**: The request received by a *destination* from a *relay*. - -mod behaviour; - -mod message_proto { - include!(concat!(env!("OUT_DIR"), "/message.pb.rs")); +mod message_proto_v2 { + include!(concat!(env!("OUT_DIR"), "/message_v2.pb.rs")); } -mod handler; -mod protocol; -mod transport; - -pub use behaviour::{Relay, RelayConfig}; -pub use transport::{RelayError, RelayTransport}; - -use libp2p_core::Transport; - -/// Create both a [`RelayTransport`] wrapping the provided [`Transport`] -/// as well as a [`Relay`] [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). -/// -/// Interconnects the returned [`RelayTransport`] and [`Relay`]. -pub fn new_transport_and_behaviour( - config: RelayConfig, - transport: T, -) -> (RelayTransport, Relay) { - let (transport, from_transport) = RelayTransport::new(transport); - let behaviour = Relay::new(config, from_transport); - (transport, behaviour) -} +pub mod v1; +pub mod v2; -/// The ID of an outgoing / incoming, relay / destination request. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct RequestId(u64); -impl RequestId { - fn new() -> RequestId { - RequestId(rand::random()) - } +// Check that we can safely cast a `usize` to a `u64`. +static_assertions::const_assert! { + std::mem::size_of::() <= std::mem::size_of::() } diff --git a/protocols/relay/src/v1.rs b/protocols/relay/src/v1.rs new file mode 100644 index 00000000000..f1bc7e4766b --- /dev/null +++ b/protocols/relay/src/v1.rs @@ -0,0 +1,126 @@ +// Copyright 2019 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 [libp2p circuit relay v1 +//! specification](https://github.com/libp2p/specs/tree/master/relay). +//! +//! ## Example +//! +//! ```rust +//! # use libp2p_core::transport::memory::MemoryTransport; +//! # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +//! # use libp2p_swarm::Swarm; +//! # use libp2p_core::{identity, Multiaddr, multiaddr::Protocol, PeerId, upgrade, Transport}; +//! # use libp2p_yamux::YamuxConfig; +//! # use libp2p_plaintext::PlainText2Config; +//! # use std::convert::TryInto; +//! # use std::str::FromStr; +//! # +//! # let local_key = identity::Keypair::generate_ed25519(); +//! # let local_public_key = local_key.public(); +//! # let local_peer_id = local_public_key.clone().into_peer_id(); +//! # let plaintext = PlainText2Config { +//! # local_public_key: local_public_key.clone(), +//! # }; +//! # +//! let (relay_transport, relay_behaviour) = new_transport_and_behaviour( +//! RelayConfig::default(), +//! MemoryTransport::default(), +//! ); +//! +//! let transport = relay_transport +//! .upgrade(upgrade::Version::V1) +//! .authenticate(plaintext) +//! .multiplex(YamuxConfig::default()) +//! .boxed(); +//! +//! let mut swarm = Swarm::new(transport, relay_behaviour, local_peer_id); +//! +//! let relay_addr = Multiaddr::from_str("/memory/1234").unwrap() +//! .with(Protocol::P2p(PeerId::random().into())) +//! .with(Protocol::P2pCircuit); +//! let dst_addr = relay_addr.clone().with(Protocol::Memory(5678)); +//! +//! // Listen for incoming connections via relay node (1234). +//! swarm.listen_on(relay_addr).unwrap(); +//! +//! // Dial node (5678) via relay node (1234). +//! swarm.dial_addr(dst_addr).unwrap(); +//! ``` +//! +//! ## Terminology +//! +//! ### Entities +//! +//! - **Source**: The node initiating a connection via a *relay* to a *destination*. +//! +//! - **Relay**: The node being asked by a *source* to relay to a *destination*. +//! +//! - **Destination**: The node contacted by the *source* via the *relay*. +//! +//! ### Messages +//! +//! - **Outgoing relay request**: The request sent by a *source* to a *relay*. +//! +//! - **Incoming relay request**: The request received by a *relay* from a *source*. +//! +//! - **Outgoing destination request**: The request sent by a *relay* to a *destination*. +//! +//! - **Incoming destination request**: The request received by a *destination* from a *relay*. + +mod behaviour; +mod connection; +mod copy_future; +mod handler; +mod protocol; +mod transport; + +pub use behaviour::{Relay, RelayConfig}; +pub use connection::Connection; +pub use transport::{RelayError, RelayListener, RelayTransport}; + +use libp2p_core::Transport; + +mod message_proto { + include!(concat!(env!("OUT_DIR"), "/message_v1.pb.rs")); +} + +/// Create both a [`RelayTransport`] wrapping the provided [`Transport`] +/// as well as a [`Relay`] [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). +/// +/// Interconnects the returned [`RelayTransport`] and [`Relay`]. +pub fn new_transport_and_behaviour( + config: RelayConfig, + transport: T, +) -> (RelayTransport, Relay) { + let (transport, from_transport) = RelayTransport::new(transport); + let behaviour = Relay::new(config, from_transport); + (transport, behaviour) +} + +/// The ID of an outgoing / incoming, relay / destination request. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct RequestId(u64); + +impl RequestId { + fn new() -> RequestId { + RequestId(rand::random()) + } +} diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/v1/behaviour.rs similarity index 97% rename from protocols/relay/src/behaviour.rs rename to protocols/relay/src/v1/behaviour.rs index 81fe6da8957..6be0a5da60b 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/v1/behaviour.rs @@ -18,11 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::handler::{RelayHandlerConfig, RelayHandlerEvent, RelayHandlerIn, RelayHandlerProto}; -use crate::message_proto::circuit_relay; -use crate::protocol; -use crate::transport::TransportToBehaviourMsg; -use crate::RequestId; +use crate::v1::handler::{ + RelayHandlerConfig, RelayHandlerEvent, RelayHandlerIn, RelayHandlerProto, +}; +use crate::v1::message_proto::circuit_relay; +use crate::v1::transport::{OutgoingRelayReqError, TransportToBehaviourMsg}; +use crate::v1::{protocol, Connection, RequestId}; use futures::channel::{mpsc, oneshot}; use futures::prelude::*; use libp2p_core::connection::{ConnectedPoint, ConnectionId, ListenerId}; @@ -38,10 +39,10 @@ use std::time::Duration; /// Network behaviour allowing the local node to act as a source, a relay and a destination. pub struct Relay { config: RelayConfig, - /// Channel receiver from [`crate::RelayTransport`]. + /// Channel receiver from [`crate::v1::RelayTransport`]. from_transport: mpsc::Receiver, - /// Events that need to be send to a [`RelayListener`](crate::transport::RelayListener) via + /// Events that need to be send to a [`RelayListener`](crate::v1::RelayListener) via /// [`Self::listeners`] or [`Self::listener_any_relay`]. outbox_to_listeners: VecDeque<(PeerId, BehaviourToListenerMsg)>, /// Events that need to be yielded to the outside when polling. @@ -82,11 +83,11 @@ struct OutgoingDialingRelayReq { relay_addr: Multiaddr, dst_addr: Option, dst_peer_id: PeerId, - send_back: oneshot::Sender>, + send_back: oneshot::Sender>, } struct OutgoingUpgradingRelayReq { - send_back: oneshot::Sender>, + send_back: oneshot::Sender>, } enum IncomingRelayReq { @@ -303,7 +304,7 @@ impl NetworkBehaviour for Relay { fn inject_dial_failure(&mut self, peer_id: &PeerId) { if let Entry::Occupied(o) = self.listeners.entry(*peer_id) { - if matches!(o.get(), RelayListener::Connecting{ .. }) { + if matches!(o.get(), RelayListener::Connecting { .. }) { // By removing the entry, the channel to the listener is dropped and thus the // listener is notified that dialing the relay failed. o.remove_entry(); @@ -755,17 +756,6 @@ impl NetworkBehaviour for Relay { } } -#[derive(Debug)] -pub enum BehaviourToListenerMsg { - ConnectionToRelayEstablished, - IncomingRelayedConnection { - stream: protocol::Connection, - src_peer_id: PeerId, - relay_peer_id: PeerId, - relay_addr: Multiaddr, - }, -} - enum RelayListener { Connecting { relay_addr: Multiaddr, @@ -788,7 +778,13 @@ impl RelayListener { } } -#[derive(Debug, Eq, PartialEq)] -pub enum OutgoingRelayReqError { - DialingRelay, +#[derive(Debug)] +pub enum BehaviourToListenerMsg { + ConnectionToRelayEstablished, + IncomingRelayedConnection { + stream: Connection, + src_peer_id: PeerId, + relay_peer_id: PeerId, + relay_addr: Multiaddr, + }, } diff --git a/protocols/relay/src/v1/connection.rs b/protocols/relay/src/v1/connection.rs new file mode 100644 index 00000000000..99b12d04a42 --- /dev/null +++ b/protocols/relay/src/v1/connection.rs @@ -0,0 +1,95 @@ +// Copyright 2019 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 bytes::Bytes; +use futures::channel::oneshot; +use futures::io::{AsyncRead, AsyncWrite}; +use libp2p_swarm::NegotiatedSubstream; +use std::io::{Error, IoSlice}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +/// A [`NegotiatedSubstream`] acting as a relayed [`Connection`]. +#[derive(Debug)] +pub struct Connection { + /// [`Connection`] might at first return data, that was already read during relay negotiation. + initial_data: Bytes, + stream: NegotiatedSubstream, + /// Notifies the other side of the channel of this [`Connection`] being dropped. + _notifier: oneshot::Sender<()>, +} + +impl Unpin for Connection {} + +impl Connection { + pub fn new( + initial_data: Bytes, + stream: NegotiatedSubstream, + notifier: oneshot::Sender<()>, + ) -> Self { + Connection { + initial_data, + stream, + + _notifier: notifier, + } + } +} + +impl AsyncWrite for Connection { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.stream).poll_write(cx, buf) + } + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + Pin::new(&mut self.stream).poll_flush(cx) + } + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + Pin::new(&mut self.stream).poll_close(cx) + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context, + bufs: &[IoSlice], + ) -> Poll> { + Pin::new(&mut self.stream).poll_write_vectored(cx, bufs) + } +} + +impl AsyncRead for Connection { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if !self.initial_data.is_empty() { + let n = std::cmp::min(self.initial_data.len(), buf.len()); + let data = self.initial_data.split_to(n); + buf[0..n].copy_from_slice(&data[..]); + return Poll::Ready(Ok(n)); + } + + Pin::new(&mut self.stream).poll_read(cx, buf) + } +} diff --git a/protocols/relay/src/protocol/copy_future.rs b/protocols/relay/src/v1/copy_future.rs similarity index 100% rename from protocols/relay/src/protocol/copy_future.rs rename to protocols/relay/src/v1/copy_future.rs diff --git a/protocols/relay/src/handler.rs b/protocols/relay/src/v1/handler.rs similarity index 98% rename from protocols/relay/src/handler.rs rename to protocols/relay/src/v1/handler.rs index 7ea0d8731ac..8c342871654 100644 --- a/protocols/relay/src/handler.rs +++ b/protocols/relay/src/v1/handler.rs @@ -18,9 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::message_proto::circuit_relay; -use crate::protocol; -use crate::RequestId; +use crate::v1::message_proto::circuit_relay; +use crate::v1::{protocol, RequestId, Connection}; use futures::channel::oneshot::{self, Canceled}; use futures::future::BoxFuture; use futures::prelude::*; @@ -93,7 +92,7 @@ pub struct RelayHandler { BoxFuture< 'static, Result< - (PeerId, protocol::Connection, oneshot::Receiver<()>), + (PeerId, Connection, oneshot::Receiver<()>), protocol::IncomingDstReqError, >, >, @@ -107,8 +106,8 @@ pub struct RelayHandler { /// Queue of events to return when polled. queued_events: Vec, /// Tracks substreams lend out to other [`RelayHandler`]s or as - /// [`Connection`](protocol::Connection) to the - /// [`RelayTransport`](crate::RelayTransport). + /// [`Connection`](Connection) to the + /// [`RelayTransport`](crate::v1::RelayTransport). /// /// For each substream to the peer of this handler, there is a future in here that resolves once /// the given substream is dropped. @@ -168,7 +167,7 @@ pub enum RelayHandlerEvent { /// > **Note**: There is no proof that we are actually communicating with the destination. An /// > encryption handshake has to be performed on top of this substream in order to /// > avoid MITM attacks. - OutgoingRelayReqSuccess(PeerId, RequestId, protocol::Connection), + OutgoingRelayReqSuccess(PeerId, RequestId, Connection), /// The local node has accepted an incoming destination request. Contains a substream that /// communicates with the source. @@ -177,7 +176,7 @@ pub enum RelayHandlerEvent { /// > encryption handshake has to be performed on top of this substream in order to /// > avoid MITM attacks. IncomingDstReqSuccess { - stream: protocol::Connection, + stream: Connection, src_peer_id: PeerId, relay_peer_id: PeerId, relay_addr: Multiaddr, @@ -337,6 +336,7 @@ impl ProtocolsHandler for RelayHandler { "Can not successfully dial a destination when actually dialing a relay." ), }; + // TODO: Should this not be driven by the src handler? self.copy_futures .push(incoming_relay_req.fulfill(to_dest_substream, from_dst_read_buffer)); } diff --git a/protocols/relay/src/message.proto b/protocols/relay/src/v1/message.proto similarity index 98% rename from protocols/relay/src/message.proto rename to protocols/relay/src/v1/message.proto index dfaf0411eea..0366c9ef9ca 100644 --- a/protocols/relay/src/message.proto +++ b/protocols/relay/src/v1/message.proto @@ -1,5 +1,5 @@ syntax = "proto2"; -package message.pb; +package message_v1.pb; message CircuitRelay { diff --git a/protocols/relay/src/protocol.rs b/protocols/relay/src/v1/protocol.rs similarity index 65% rename from protocols/relay/src/protocol.rs rename to protocols/relay/src/v1/protocol.rs index 900d16a6d74..9847bd1a0d4 100644 --- a/protocols/relay/src/protocol.rs +++ b/protocols/relay/src/v1/protocol.rs @@ -18,28 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::message_proto::circuit_relay; +use crate::v1::message_proto::circuit_relay; -use bytes::Bytes; -use futures::channel::oneshot; -use futures::io::{AsyncRead, AsyncWrite}; use libp2p_core::{multiaddr::Error as MultiaddrError, Multiaddr, PeerId}; -use libp2p_swarm::NegotiatedSubstream; use smallvec::SmallVec; -use std::io::{Error, IoSlice}; -use std::pin::Pin; -use std::task::{Context, Poll}; use std::{convert::TryFrom, error, fmt}; -/// Any message received on the wire whose length exceeds this value is refused. -// -// The circuit relay specification sets a maximum of 1024 bytes per multiaddr. A single message can -// contain multiple addresses for both the source and destination node. Setting the maximum message -// length to 10 times that limit is an unproven estimate. Feel free to refine this in the future. -const MAX_ACCEPTED_MESSAGE_LEN: usize = 10 * 1024; - -const PROTOCOL_NAME: &[u8; 27] = b"/libp2p/circuit/relay/0.1.0"; - // Source -> Relay mod incoming_relay_req; mod outgoing_relay_req; @@ -55,7 +39,15 @@ pub use self::outgoing_dst_req::{OutgoingDstReq, OutgoingDstReqError}; mod listen; pub use self::listen::{RelayListen, RelayListenError, RelayRemoteReq}; -pub mod copy_future; +/// Any message received on the wire whose length exceeds this value is refused. +// +// The circuit relay specification sets a maximum of 1024 bytes per multiaddr. A single message can +// contain multiple addresses for both the source and destination node. Setting the maximum message +// length to 10 times that limit is an unproven estimate. Feel free to refine this in the future. +const MAX_ACCEPTED_MESSAGE_LEN: usize = 10 * 1024; + +const PROTOCOL_NAME: &[u8; 27] = b"/libp2p/circuit/relay/0.1.0"; + /// Representation of a `CircuitRelay_Peer` protobuf message with refined field types. /// @@ -112,71 +104,3 @@ impl error::Error for PeerParseError { } } } - -/// A [`NegotiatedSubstream`] acting as a relayed [`Connection`]. -#[derive(Debug)] -pub struct Connection { - /// [`Connection`] might at first return data, that was already read during relay negotiation. - initial_data: Bytes, - stream: NegotiatedSubstream, - /// Notifies the other side of the channel of this [`Connection`] being dropped. - _notifier: oneshot::Sender<()>, -} - -impl Unpin for Connection {} - -impl Connection { - fn new( - initial_data: Bytes, - stream: NegotiatedSubstream, - notifier: oneshot::Sender<()>, - ) -> Self { - Connection { - initial_data, - stream, - - _notifier: notifier, - } - } -} - -impl AsyncWrite for Connection { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.stream).poll_write(cx, buf) - } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut self.stream).poll_flush(cx) - } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut self.stream).poll_close(cx) - } - - fn poll_write_vectored( - mut self: Pin<&mut Self>, - cx: &mut Context, - bufs: &[IoSlice], - ) -> Poll> { - Pin::new(&mut self.stream).poll_write_vectored(cx, bufs) - } -} - -impl AsyncRead for Connection { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - if !self.initial_data.is_empty() { - let n = std::cmp::min(self.initial_data.len(), buf.len()); - let data = self.initial_data.split_to(n); - buf[0..n].copy_from_slice(&data[..]); - return Poll::Ready(Ok(n)); - } - - Pin::new(&mut self.stream).poll_read(cx, buf) - } -} diff --git a/protocols/relay/src/protocol/incoming_dst_req.rs b/protocols/relay/src/v1/protocol/incoming_dst_req.rs similarity index 92% rename from protocols/relay/src/protocol/incoming_dst_req.rs rename to protocols/relay/src/v1/protocol/incoming_dst_req.rs index b3b0ded9de6..74c3c710a2c 100644 --- a/protocols/relay/src/protocol/incoming_dst_req.rs +++ b/protocols/relay/src/v1/protocol/incoming_dst_req.rs @@ -18,13 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::message_proto::{circuit_relay, CircuitRelay}; -use crate::protocol::Peer; +use crate::v1::message_proto::{circuit_relay, CircuitRelay}; +use crate::v1::protocol::Peer; +use crate::v1::Connection; use asynchronous_codec::{Framed, FramedParts}; use bytes::BytesMut; -use futures::{future::BoxFuture, prelude::*}; use futures::channel::oneshot; +use futures::{future::BoxFuture, prelude::*}; use libp2p_core::{Multiaddr, PeerId}; use libp2p_swarm::NegotiatedSubstream; use prost::Message; @@ -47,8 +48,7 @@ pub struct IncomingDstReq { src: Peer, } -impl IncomingDstReq -{ +impl IncomingDstReq { /// Creates a `IncomingDstReq`. pub(crate) fn new(stream: Framed, src: Peer) -> Self { IncomingDstReq { @@ -73,7 +73,8 @@ impl IncomingDstReq /// stream then points to the source (as retreived with `src_id()` and `src_addrs()`). pub fn accept( self, - ) -> BoxFuture<'static, Result<(PeerId, super::Connection, oneshot::Receiver<()>), IncomingDstReqError>> { + ) -> BoxFuture<'static, Result<(PeerId, Connection, oneshot::Receiver<()>), IncomingDstReqError>> + { let IncomingDstReq { mut stream, src } = self; let msg = CircuitRelay { r#type: Some(circuit_relay::Type::Status.into()), @@ -101,7 +102,11 @@ impl IncomingDstReq let (tx, rx) = oneshot::channel(); - Ok((src.peer_id, super::Connection::new(read_buffer.freeze(), io, tx), rx)) + Ok(( + src.peer_id, + Connection::new(read_buffer.freeze(), io, tx), + rx, + )) } .boxed() } diff --git a/protocols/relay/src/protocol/incoming_relay_req.rs b/protocols/relay/src/v1/protocol/incoming_relay_req.rs similarity index 97% rename from protocols/relay/src/protocol/incoming_relay_req.rs rename to protocols/relay/src/v1/protocol/incoming_relay_req.rs index 6f585db2854..5fde863316c 100644 --- a/protocols/relay/src/protocol/incoming_relay_req.rs +++ b/protocols/relay/src/v1/protocol/incoming_relay_req.rs @@ -18,9 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::copy_future::CopyFuture; -use crate::message_proto::{circuit_relay, circuit_relay::Status, CircuitRelay}; -use crate::protocol::Peer; +use crate::v1::copy_future::CopyFuture; +use crate::v1::message_proto::{circuit_relay, circuit_relay::Status, CircuitRelay}; +use crate::v1::protocol::Peer; use asynchronous_codec::{Framed, FramedParts}; use bytes::{BytesMut, Bytes}; diff --git a/protocols/relay/src/protocol/listen.rs b/protocols/relay/src/v1/protocol/listen.rs similarity index 95% rename from protocols/relay/src/protocol/listen.rs rename to protocols/relay/src/v1/protocol/listen.rs index 70a084640f8..137840bfa2d 100644 --- a/protocols/relay/src/protocol/listen.rs +++ b/protocols/relay/src/v1/protocol/listen.rs @@ -18,10 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::message_proto::{circuit_relay, CircuitRelay}; -use crate::protocol::incoming_dst_req::IncomingDstReq; -use crate::protocol::incoming_relay_req::IncomingRelayReq; -use crate::protocol::{Peer, PeerParseError, MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; +use crate::v1::message_proto::{circuit_relay, CircuitRelay}; +use crate::v1::protocol::incoming_dst_req::IncomingDstReq; +use crate::v1::protocol::incoming_relay_req::IncomingRelayReq; +use crate::v1::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; +use crate::v1::protocol::{Peer, PeerParseError}; use asynchronous_codec::Framed; use futures::channel::oneshot; use futures::{future::BoxFuture, prelude::*}; diff --git a/protocols/relay/src/protocol/outgoing_dst_req.rs b/protocols/relay/src/v1/protocol/outgoing_dst_req.rs similarity index 97% rename from protocols/relay/src/protocol/outgoing_dst_req.rs rename to protocols/relay/src/v1/protocol/outgoing_dst_req.rs index 0a257da3bab..32526f9e29c 100644 --- a/protocols/relay/src/protocol/outgoing_dst_req.rs +++ b/protocols/relay/src/v1/protocol/outgoing_dst_req.rs @@ -18,8 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::message_proto::{circuit_relay, CircuitRelay}; -use crate::protocol::{Peer, MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; +use crate::v1::message_proto::{circuit_relay, CircuitRelay}; +use crate::v1::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; +use crate::v1::protocol::Peer; use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::future::BoxFuture; @@ -27,7 +28,7 @@ use futures::prelude::*; use libp2p_core::{upgrade, Multiaddr, PeerId}; use libp2p_swarm::NegotiatedSubstream; use prost::Message; -use std::{fmt, error, iter}; +use std::{error, fmt, iter}; use unsigned_varint::codec::UviBytes; /// Ask the remote to become a destination. The upgrade succeeds if the remote accepts, and fails diff --git a/protocols/relay/src/protocol/outgoing_relay_req.rs b/protocols/relay/src/v1/protocol/outgoing_relay_req.rs similarity index 96% rename from protocols/relay/src/protocol/outgoing_relay_req.rs rename to protocols/relay/src/v1/protocol/outgoing_relay_req.rs index 8f6ed8b3aa3..d9fa2fe90fa 100644 --- a/protocols/relay/src/protocol/outgoing_relay_req.rs +++ b/protocols/relay/src/v1/protocol/outgoing_relay_req.rs @@ -18,8 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::message_proto::{circuit_relay, CircuitRelay}; -use crate::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; +use crate::v1::message_proto::{circuit_relay, CircuitRelay}; +use crate::v1::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; +use crate::v1::Connection; use asynchronous_codec::{Framed, FramedParts}; use futures::channel::oneshot; use futures::future::BoxFuture; @@ -68,7 +69,7 @@ impl upgrade::UpgradeInfo for OutgoingRelayReq { } impl upgrade::OutboundUpgrade for OutgoingRelayReq { - type Output = (super::Connection, oneshot::Receiver<()>); + type Output = (Connection, oneshot::Receiver<()>); type Error = OutgoingRelayReqError; type Future = BoxFuture<'static, Result>; @@ -158,7 +159,7 @@ impl upgrade::OutboundUpgrade for OutgoingRelayReq { let (tx, rx) = oneshot::channel(); - Ok((super::Connection::new(read_buffer.freeze(), io, tx), rx)) + Ok((Connection::new(read_buffer.freeze(), io, tx), rx)) } .boxed() } diff --git a/protocols/relay/src/transport.rs b/protocols/relay/src/v1/transport.rs similarity index 96% rename from protocols/relay/src/transport.rs rename to protocols/relay/src/v1/transport.rs index 4aa96f69933..65497cb6978 100644 --- a/protocols/relay/src/transport.rs +++ b/protocols/relay/src/v1/transport.rs @@ -18,9 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::behaviour::{BehaviourToListenerMsg, OutgoingRelayReqError}; -use crate::protocol; -use crate::RequestId; +use crate::v1::{RequestId, Connection}; +use crate::v1::behaviour::BehaviourToListenerMsg; use futures::channel::mpsc; use futures::channel::oneshot; use futures::future::{BoxFuture, Future, FutureExt}; @@ -43,7 +42,7 @@ use std::task::{Context, Poll}; /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport}; /// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::{RelayConfig, new_transport_and_behaviour}; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; /// # let inner_transport = MemoryTransport::default(); /// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( /// # RelayConfig::default(), @@ -57,7 +56,7 @@ use std::task::{Context, Poll}; /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; /// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::{RelayConfig, new_transport_and_behaviour}; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; /// # let inner_transport = MemoryTransport::default(); /// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( /// # RelayConfig::default(), @@ -77,7 +76,7 @@ use std::task::{Context, Poll}; /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; /// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::{RelayConfig, new_transport_and_behaviour}; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; /// # let inner_transport = MemoryTransport::default(); /// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( /// # RelayConfig::default(), @@ -98,7 +97,7 @@ use std::task::{Context, Poll}; /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; /// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::{RelayConfig, new_transport_and_behaviour}; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; /// # let inner_transport = MemoryTransport::default(); /// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( /// # RelayConfig::default(), @@ -121,7 +120,7 @@ impl RelayTransport { /// ///``` /// # use libp2p_core::transport::dummy::DummyTransport; - /// # use libp2p_relay::{RelayConfig, new_transport_and_behaviour}; + /// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; /// /// let inner_transport = DummyTransport::<()>::new(); /// let (relay_transport, relay_behaviour) = new_transport_and_behaviour( @@ -143,7 +142,7 @@ impl RelayTransport { } impl Transport for RelayTransport { - type Output = EitherOutput<::Output, protocol::Connection>; + type Output = EitherOutput<::Output, Connection>; type Error = EitherError<::Error, RelayError>; type Listener = RelayListener; type ListenerUpgrade = RelayedListenerUpgrade; @@ -427,17 +426,17 @@ impl Stream for RelayListener { } } -pub type RelayedDial = BoxFuture<'static, Result>; +pub type RelayedDial = BoxFuture<'static, Result>; #[pin_project(project = RelayedListenerUpgradeProj)] pub enum RelayedListenerUpgrade { Inner(#[pin] ::ListenerUpgrade), - Relayed(Option), + Relayed(Option), } impl Future for RelayedListenerUpgrade { type Output = Result< - EitherOutput<::Output, protocol::Connection>, + EitherOutput<::Output, Connection>, EitherError<::Error, RelayError>, >; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -544,7 +543,7 @@ pub enum TransportToBehaviourMsg { relay_peer_id: PeerId, dst_addr: Option, dst_peer_id: PeerId, - send_back: oneshot::Sender>, + send_back: oneshot::Sender>, }, /// Listen for incoming relayed connections via relay node. ListenReq { @@ -555,3 +554,8 @@ pub enum TransportToBehaviourMsg { to_listener: mpsc::Sender, }, } + +#[derive(Debug, Eq, PartialEq)] +pub enum OutgoingRelayReqError { + DialingRelay, +} diff --git a/protocols/relay/src/v2.rs b/protocols/relay/src/v2.rs new file mode 100644 index 00000000000..37aa2995775 --- /dev/null +++ b/protocols/relay/src/v2.rs @@ -0,0 +1,30 @@ +// 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. + +mod message_proto { + include!(concat!(env!("OUT_DIR"), "/message_v2.pb.rs")); +} + +mod behaviour; +mod copy_future; +mod handler; +mod protocol; + +pub use behaviour::{Relay, RelayEvent}; \ No newline at end of file diff --git a/protocols/relay/src/v2/behaviour.rs b/protocols/relay/src/v2/behaviour.rs new file mode 100644 index 00000000000..0b30d5459a4 --- /dev/null +++ b/protocols/relay/src/v2/behaviour.rs @@ -0,0 +1,565 @@ +// 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 crate::v2::handler::{self, RelayHandlerEvent, RelayHandlerIn, RelayHandlerProto}; +use crate::v2::message_proto; +use libp2p_core::connection::{ConnectedPoint, ConnectionId}; +use libp2p_core::multiaddr::Protocol; +use libp2p_core::{Multiaddr, PeerId}; +use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::ops::Add; +use std::task::{Context, Poll}; +use std::time::Duration; + +pub struct Config { + // TODO: Should we use u32? + max_reservations: usize, + _max_reservations_per_ip: u32, + // TODO: Good idea? + _max_reservations_per_asn: u32, + reservation_duration: Duration, + + // TODO: Should we use u32? + max_circuits: usize, + max_circuit_duration: Duration, + max_circuit_bytes: u64, +} + +impl Default for Config { + fn default() -> Self { + Config { + max_reservations: 128, + _max_reservations_per_ip: 4, + // TODO: Good idea? + _max_reservations_per_asn: 32, + reservation_duration: Duration::from_secs(60 * 60), + + // TODO: Shouldn't we have a limit per IP and ASN as well? + max_circuits: 16, + max_circuit_duration: Duration::from_secs(2 * 60), + max_circuit_bytes: 1 << 17, // 128 kibibyte + } + } +} + +#[derive(Debug)] +pub enum RelayEvent { + // TODO: Note that this can both be a new one or a renewal. + ReservationReqAccepted { + src_peer_id: PeerId, + renewed: bool, + }, + ReservationReqAcceptFailed { + src_peer_id: PeerId, + error: std::io::Error, + }, + ReservationReqDenied { + src_peer_id: PeerId, + }, + ReservationReqDenyFailed { + src_peer_id: PeerId, + error: std::io::Error, + }, + ReservationTimedOut { + src_peer_id: PeerId, + }, + CircuitReqDenied { + src_peer_id: PeerId, + dst_peer_id: PeerId, + }, + CircuitReqDenyFailed { + src_peer_id: PeerId, + dst_peer_id: PeerId, + error: std::io::Error, + }, + CircuitReqAccepted { + src_peer_id: PeerId, + dst_peer_id: PeerId, + }, + CircuitReqAcceptFailed { + src_peer_id: PeerId, + dst_peer_id: PeerId, + error: std::io::Error, + }, + CircuitClosed { + src_peer_id: PeerId, + dst_peer_id: PeerId, + error: Option, + }, +} + +// TODO: Consider having one Behaviour for relay and one for both source and destination. +pub struct Relay { + config: Config, + + local_peer_id: PeerId, + + reservations: HashMap>, + circuits: CircuitsTracker, + + /// Queue of actions to return when polled. + queued_actions: VecDeque>, +} + +impl Relay { + pub fn new(local_peer_id: PeerId, config: Config) -> Self { + Self { + config, + local_peer_id, + reservations: Default::default(), + circuits: Default::default(), + queued_actions: Default::default(), + } + } +} + +impl NetworkBehaviour for Relay { + type ProtocolsHandler = RelayHandlerProto; + type OutEvent = RelayEvent; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + RelayHandlerProto { + config: handler::Config { + reservation_duration: self.config.reservation_duration, + max_circuit_duration: self.config.max_circuit_duration, + max_circuit_bytes: self.config.max_circuit_bytes, + }, + } + } + + fn addresses_of_peer(&mut self, _remote_peer_id: &PeerId) -> Vec { + vec![] + } + + fn inject_connected(&mut self, _peer_id: &PeerId) {} + + fn inject_disconnected(&mut self, _peer: &PeerId) {} + + fn inject_connection_closed( + &mut self, + peer: &PeerId, + connection: &ConnectionId, + _: &ConnectedPoint, + ) { + self.reservations + .get_mut(peer) + .map(|cs| cs.remove(&connection)) + .unwrap_or(false); + + for circuit in self + .circuits + .remove_by_connection(*peer, *connection) + .iter() + // Only emit [`CircuitClosed`] for accepted requests. + .filter(|c| matches!(c.status, CircuitStatus::Accepted)) + { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::CircuitClosed { + src_peer_id: circuit.src_peer_id, + dst_peer_id: circuit.dst_peer_id, + error: Some(std::io::ErrorKind::ConnectionAborted.into()), + }, + )); + } + } + + fn inject_event( + &mut self, + event_source: PeerId, + connection: ConnectionId, + event: RelayHandlerEvent, + ) { + match event { + RelayHandlerEvent::ReservationReqReceived { + inbound_reservation_req, + } => { + let event = if self + .reservations + .iter() + .map(|(_, cs)| cs.len()) + .sum::() + < self.config.max_reservations + { + self.reservations + .entry(event_source) + .or_default() + .insert(connection); + RelayHandlerIn::AcceptReservationReq { + inbound_reservation_req, + addrs: vec![], + } + } else { + RelayHandlerIn::DenyReservationReq { + inbound_reservation_req, + status: message_proto::Status::ResourceLimitExceeded, + } + }; + + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event, + }) + } + RelayHandlerEvent::ReservationReqAccepted { renewed } => { + // Ensure local eventual consistent reservation state matches handler (source of + // truth). + self.reservations + .entry(event_source) + .or_default() + .insert(connection); + + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::ReservationReqAccepted { + src_peer_id: event_source, + renewed, + }, + )); + } + RelayHandlerEvent::ReservationReqAcceptFailed { error } => { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::ReservationReqAcceptFailed { + src_peer_id: event_source, + error, + }, + )); + } + RelayHandlerEvent::ReservationReqDenied {} => { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::ReservationReqDenied { + src_peer_id: event_source, + }, + )); + } + RelayHandlerEvent::ReservationReqDenyFailed { error } => { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::ReservationReqDenyFailed { + src_peer_id: event_source, + error, + }, + )); + } + RelayHandlerEvent::ReservationTimedOut {} => { + self.reservations + .get_mut(&event_source) + .map(|cs| cs.remove(&connection)); + + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::ReservationTimedOut { + src_peer_id: event_source, + }, + )); + } + RelayHandlerEvent::CircuitReqReceived(inbound_circuit_req) => { + if self.circuits.len() >= self.config.max_circuits { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: RelayHandlerIn::DenyCircuitReq { + circuit_id: None, + inbound_circuit_req, + status: message_proto::Status::ResourceLimitExceeded, + }, + }); + } else if let Some(dst_conn) = self + .reservations + .get(&inbound_circuit_req.dst()) + .map(|cs| cs.iter().next()) + .flatten() + { + // TODO: Restrict the amount of circuits between two peers and one single + // peer. + + let circuit_id = self.circuits.insert(Circuit { + status: CircuitStatus::Accepting, + src_peer_id: event_source, + src_connection_id: connection, + dst_peer_id: inbound_circuit_req.dst(), + dst_connection_id: *dst_conn, + }); + + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(*dst_conn), + peer_id: event_source, + event: RelayHandlerIn::NegotiateOutboundConnect { + circuit_id, + inbound_circuit_req, + relay_peer_id: self.local_peer_id, + src_peer_id: event_source, + src_connection_id: connection, + }, + }); + } else { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: RelayHandlerIn::DenyCircuitReq { + circuit_id: None, + inbound_circuit_req, + status: message_proto::Status::NoReservation, + }, + }); + } + } + RelayHandlerEvent::CircuitReqDenied { + circuit_id, + dst_peer_id, + } => { + if let Some(circuit_id) = circuit_id { + self.circuits.remove(circuit_id); + } + + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::CircuitReqDenied { + src_peer_id: event_source, + dst_peer_id, + }, + )); + } + RelayHandlerEvent::CircuitReqDenyFailed { + circuit_id, + dst_peer_id, + error, + } => { + if let Some(circuit_id) = circuit_id { + self.circuits.remove(circuit_id); + } + + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::CircuitReqDenyFailed { + src_peer_id: event_source, + dst_peer_id, + error, + }, + )); + } + RelayHandlerEvent::OutboundConnectNegotiated { + circuit_id, + src_peer_id, + src_connection_id, + inbound_circuit_req, + dst_handler_notifier, + dst_stream, + dst_pending_data, + } => { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(src_connection_id), + peer_id: src_peer_id, + event: RelayHandlerIn::AcceptAndDriveCircuit { + circuit_id, + dst_peer_id: event_source, + inbound_circuit_req, + dst_handler_notifier, + dst_stream, + dst_pending_data, + }, + }); + } + RelayHandlerEvent::OutboundConnectNegotiationFailed { + circuit_id, + src_peer_id, + src_connection_id, + inbound_circuit_req, + status, + } => { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(src_connection_id), + peer_id: src_peer_id, + event: RelayHandlerIn::DenyCircuitReq { + circuit_id: Some(circuit_id), + inbound_circuit_req, + status, + }, + }); + } + RelayHandlerEvent::CircuitReqAccepted { + dst_peer_id, + circuit_id, + } => { + self.circuits.accepted(circuit_id); + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::CircuitReqAccepted { + src_peer_id: event_source, + dst_peer_id, + }, + )); + } + RelayHandlerEvent::CircuitReqAcceptFailed { + dst_peer_id, + circuit_id, + error, + } => { + self.circuits.remove(circuit_id); + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::CircuitReqAcceptFailed { + src_peer_id: event_source, + dst_peer_id, + error, + }, + )); + } + RelayHandlerEvent::CircuitClosed { + dst_peer_id, + circuit_id, + error, + } => { + self.circuits.remove(circuit_id); + + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + RelayEvent::CircuitClosed { + src_peer_id: event_source, + dst_peer_id, + error, + }, + )); + } + } + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + poll_parameters: &mut impl PollParameters, + ) -> Poll> { + if let Some(mut event) = self.queued_actions.pop_front() { + // Set external addresses in [`AcceptReservationReq`]. + if let NetworkBehaviourAction::NotifyHandler { + event: RelayHandlerIn::AcceptReservationReq { ref mut addrs, .. }, + .. + } = &mut event + { + *addrs = poll_parameters + .external_addresses() + .map(|a| { + a.addr + .with(Protocol::P2p((*poll_parameters.local_peer_id()).into())) + // TODO: Is it really required to add the p2p circuit protocol at the end? Why? + .with(Protocol::P2pCircuit) + }) + .collect(); + } + + return Poll::Ready(event); + } + + Poll::Pending + } +} + +#[derive(Default)] +struct CircuitsTracker { + next_id: CircuitId, + circuits: HashMap, +} + +impl CircuitsTracker { + fn len(&self) -> usize { + self.circuits.len() + } + + fn insert(&mut self, circuit: Circuit) -> CircuitId { + let id = self.next_id; + self.next_id = self.next_id + 1; + + self.circuits.insert(id, circuit); + + id + } + + fn accepted(&mut self, circuit_id: CircuitId) { + self.circuits + .get_mut(&circuit_id) + .map(|c| c.status = CircuitStatus::Accepted); + } + + fn remove(&mut self, circuit_id: CircuitId) -> Option { + self.circuits.remove(&circuit_id) + } + + fn remove_by_connection( + &mut self, + peer_id: PeerId, + connection_id: ConnectionId, + ) -> Vec { + let mut removed = vec![]; + + self.circuits.retain(|_circuit_id, circuit| { + let is_src = + circuit.src_peer_id == peer_id && circuit.src_connection_id == connection_id; + let is_dst = + circuit.dst_peer_id == peer_id && circuit.dst_connection_id == connection_id; + + if is_src || is_dst { + removed.push(circuit.clone()); + // Remove circuit from HashMap. + false + } else { + // Retain circuit in HashMap. + true + } + }); + + removed + } +} + +#[derive(Clone)] +struct Circuit { + src_peer_id: PeerId, + src_connection_id: ConnectionId, + dst_peer_id: PeerId, + dst_connection_id: ConnectionId, + status: CircuitStatus, +} + +#[derive(Clone)] +enum CircuitStatus { + Accepting, + Accepted, +} + +#[derive(Default, Clone, Copy, Hash, Eq, PartialEq)] +pub struct CircuitId(u64); + +impl Add for CircuitId { + type Output = CircuitId; + + fn add(self, rhs: u64) -> Self { + CircuitId(self.0 + rhs) + } +} diff --git a/protocols/relay/src/v2/copy_future.rs b/protocols/relay/src/v2/copy_future.rs new file mode 100644 index 00000000000..eec0ab01f6a --- /dev/null +++ b/protocols/relay/src/v2/copy_future.rs @@ -0,0 +1,149 @@ +// 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"), +// 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. + +//! Helper to interconnect two substreams, connecting the receiver side of A with the sender side of +//! B and vice versa. +//! +//! Inspired by [`futures::io::Copy`]. + +use futures::future::Future; +use futures::future::FutureExt; +use futures::io::{AsyncBufRead, BufReader}; +use futures::io::{AsyncRead, AsyncWrite}; +use futures::ready; +use futures_timer::Delay; +use std::convert::TryInto; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; + +pub struct CopyFuture { + src: BufReader, + dst: BufReader, + + max_circuit_duration: Delay, + max_circuit_bytes: u64, + bytes_sent: u64, +} + +impl CopyFuture { + pub fn new(src: S, dst: D, max_circuit_duration: Duration, max_circuit_bytes: u64) -> Self { + CopyFuture { + src: BufReader::new(src), + dst: BufReader::new(dst), + max_circuit_duration: Delay::new(max_circuit_duration), + max_circuit_bytes, + bytes_sent: Default::default(), + } + } +} + +impl Future for CopyFuture +where + S: AsyncRead + AsyncWrite + Unpin, + D: AsyncRead + AsyncWrite + Unpin, +{ + type Output = io::Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = &mut *self; + + loop { + if this.bytes_sent > this.max_circuit_bytes { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::Other, + "Max circuit bytes reached.", + ))); + } + + enum Status { + Pending, + Done, + Progressed, + } + + let src_status = match forward_data(&mut this.src, &mut this.dst, cx) { + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Ready(Ok(0)) => Status::Done, + Poll::Ready(Ok(i)) => { + this.bytes_sent += i; + Status::Progressed + } + Poll::Pending => Status::Pending, + }; + + let dst_status = match forward_data(&mut this.dst, &mut this.src, cx) { + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Ready(Ok(0)) => Status::Done, + Poll::Ready(Ok(i)) => { + this.bytes_sent += i; + Status::Progressed + } + Poll::Pending => Status::Pending, + }; + + match (src_status, dst_status) { + // Both source and destination are done sending data. + (Status::Done, Status::Done) => return Poll::Ready(Ok(())), + // Either source or destination made progress. + (Status::Progressed, _) | (_, Status::Progressed) => {} + // Both are pending. Check if max circuit duration timer fired, otherwise return + // Poll::Pending. + (Status::Pending, Status::Pending) => break, + // One is done sending data, the other is pending. Check if timer fired, otherwise + // return Poll::Pending. + (Status::Pending, Status::Done) | (Status::Done, Status::Pending) => break, + } + } + + if let Poll::Ready(()) = this.max_circuit_duration.poll_unpin(cx) { + return Poll::Ready(Err(io::ErrorKind::TimedOut.into())); + } + + Poll::Pending + } +} + +/// Forwards data from `source` to `destination`. +/// +/// Returns `0` when done, i.e. `source` having reached EOF, returns number of bytes sent otherwise, +/// thus indicating progress. +fn forward_data( + mut src: &mut S, + mut dst: &mut D, + cx: &mut Context<'_>, +) -> Poll> { + let buffer = ready!(Pin::new(&mut src).poll_fill_buf(cx))?; + if buffer.is_empty() { + ready!(Pin::new(&mut dst).poll_flush(cx))?; + ready!(Pin::new(&mut dst).poll_close(cx))?; + return Poll::Ready(Ok(0)); + } + + let i = ready!(Pin::new(dst).poll_write(cx, buffer))?; + if i == 0 { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + Pin::new(src).consume(i); + + Poll::Ready(Ok(i.try_into().expect("usize to fit into u64."))) +} diff --git a/protocols/relay/src/v2/handler.rs b/protocols/relay/src/v2/handler.rs new file mode 100644 index 00000000000..ceaa7790143 --- /dev/null +++ b/protocols/relay/src/v2/handler.rs @@ -0,0 +1,680 @@ +// 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 crate::v2::message_proto; + +use crate::v2::behaviour::CircuitId; +use crate::v2::copy_future::CopyFuture; +use crate::v2::message_proto::Status; +use crate::v2::protocol::{inbound_hop, outbound_stop}; +use bytes::Bytes; +use futures::channel::oneshot::{self, Canceled}; +use futures::future::{BoxFuture, FutureExt, TryFutureExt}; +use futures::io::AsyncWriteExt; +use futures::stream::{FuturesUnordered, StreamExt}; +use futures_timer::Delay; +use libp2p_core::connection::ConnectionId; +use libp2p_core::either::EitherError; +use libp2p_core::{upgrade, ConnectedPoint, Multiaddr, PeerId}; +use libp2p_swarm::protocols_handler::{InboundUpgradeSend, OutboundUpgradeSend}; +use libp2p_swarm::{ + IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, ProtocolsHandlerEvent, + ProtocolsHandlerUpgrErr, SubstreamProtocol, +}; +use std::collections::VecDeque; +use std::task::{Context, Poll}; +use std::time::Duration; + +pub struct Config { + pub reservation_duration: Duration, + pub max_circuit_duration: Duration, + pub max_circuit_bytes: u64, +} + +pub enum RelayHandlerIn { + AcceptReservationReq { + inbound_reservation_req: inbound_hop::ReservationReq, + addrs: Vec, + }, + DenyReservationReq { + inbound_reservation_req: inbound_hop::ReservationReq, + status: message_proto::Status, + }, + DenyCircuitReq { + circuit_id: Option, + inbound_circuit_req: inbound_hop::CircuitReq, + status: message_proto::Status, + }, + NegotiateOutboundConnect { + circuit_id: CircuitId, + inbound_circuit_req: inbound_hop::CircuitReq, + relay_peer_id: PeerId, + src_peer_id: PeerId, + src_connection_id: ConnectionId, + }, + AcceptAndDriveCircuit { + circuit_id: CircuitId, + dst_peer_id: PeerId, + inbound_circuit_req: inbound_hop::CircuitReq, + dst_handler_notifier: oneshot::Sender<()>, + dst_stream: NegotiatedSubstream, + dst_pending_data: Bytes, + }, +} + +pub enum RelayHandlerEvent { + ReservationReqReceived { + inbound_reservation_req: inbound_hop::ReservationReq, + }, + ReservationReqAccepted { + renewed: bool, + }, + ReservationReqAcceptFailed { + error: std::io::Error, + }, + ReservationReqDenied {}, + ReservationReqDenyFailed { + error: std::io::Error, + }, + ReservationTimedOut {}, + CircuitReqReceived(inbound_hop::CircuitReq), + CircuitReqDenied { + circuit_id: Option, + dst_peer_id: PeerId, + }, + CircuitReqDenyFailed { + circuit_id: Option, + dst_peer_id: PeerId, + error: std::io::Error, + }, + CircuitReqAccepted { + circuit_id: CircuitId, + dst_peer_id: PeerId, + }, + CircuitReqAcceptFailed { + circuit_id: CircuitId, + dst_peer_id: PeerId, + error: std::io::Error, + }, + OutboundConnectNegotiated { + circuit_id: CircuitId, + src_peer_id: PeerId, + src_connection_id: ConnectionId, + inbound_circuit_req: inbound_hop::CircuitReq, + dst_handler_notifier: oneshot::Sender<()>, + dst_stream: NegotiatedSubstream, + dst_pending_data: Bytes, + }, + OutboundConnectNegotiationFailed { + circuit_id: CircuitId, + src_peer_id: PeerId, + src_connection_id: ConnectionId, + inbound_circuit_req: inbound_hop::CircuitReq, + status: Status, + }, + CircuitClosed { + circuit_id: CircuitId, + dst_peer_id: PeerId, + error: Option, + }, +} + +pub struct RelayHandlerProto { + pub config: Config, +} + +impl IntoProtocolsHandler for RelayHandlerProto { + type Handler = RelayHandler; + + fn into_handler(self, _remote_peer_id: &PeerId, _endpoint: &ConnectedPoint) -> Self::Handler { + RelayHandler { + config: self.config, + queued_events: Default::default(), + pending_error: Default::default(), + reservation_accept_futures: Default::default(), + reservation_deny_futures: Default::default(), + circuit_accept_futures: Default::default(), + circuit_deny_futures: Default::default(), + alive_lend_out_substreams: Default::default(), + circuits: Default::default(), + active_reservation: Default::default(), + } + } + + fn inbound_protocol(&self) -> ::InboundProtocol { + inbound_hop::Upgrade { + reservation_duration: self.config.reservation_duration, + max_circuit_duration: self.config.max_circuit_duration, + max_circuit_bytes: self.config.max_circuit_bytes, + } + } +} + +pub struct RelayHandler { + config: Config, + + /// Queue of events to return when polled. + queued_events: VecDeque< + ProtocolsHandlerEvent< + ::OutboundProtocol, + ::OutboundOpenInfo, + ::OutEvent, + ::Error, + >, + >, + + /// A pending fatal error that results in the connection being closed. + pending_error: Option< + ProtocolsHandlerUpgrErr< + EitherError, + >, + >, + + reservation_accept_futures: FuturesUnordered>>, + reservation_deny_futures: FuturesUnordered>>, + + circuit_accept_futures: FuturesUnordered< + BoxFuture<'static, Result>, + >, + circuit_deny_futures: FuturesUnordered< + BoxFuture<'static, (Option, PeerId, Result<(), std::io::Error>)>, + >, + + alive_lend_out_substreams: FuturesUnordered>, + + circuits: FuturesUnordered)>>, + + active_reservation: Option, +} + +impl ProtocolsHandler for RelayHandler { + type InEvent = RelayHandlerIn; + type OutEvent = RelayHandlerEvent; + type Error = ProtocolsHandlerUpgrErr< + EitherError, + >; + type InboundProtocol = inbound_hop::Upgrade; + type OutboundProtocol = outbound_stop::Upgrade; + type OutboundOpenInfo = OutboundOpenInfo; + type InboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new( + inbound_hop::Upgrade { + reservation_duration: self.config.reservation_duration, + max_circuit_duration: self.config.max_circuit_duration, + max_circuit_bytes: self.config.max_circuit_bytes, + }, + (), + ) + } + + fn inject_fully_negotiated_inbound( + &mut self, + request: >::Output, + _request_id: Self::InboundOpenInfo, + ) { + match request { + inbound_hop::Req::Reserve(inbound_reservation_req) => { + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::ReservationReqReceived { + inbound_reservation_req, + }, + )); + } + inbound_hop::Req::Connect(circuit_req) => { + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::CircuitReqReceived(circuit_req), + )); + } + } + } + + fn inject_fully_negotiated_outbound( + &mut self, + (dst_stream, dst_pending_data): >::Output, + outbound_open_info: Self::OutboundOpenInfo, + ) { + let OutboundOpenInfo { + circuit_id, + inbound_circuit_req, + src_peer_id, + src_connection_id, + } = outbound_open_info; + let (tx, rx) = oneshot::channel(); + self.alive_lend_out_substreams.push(rx); + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::OutboundConnectNegotiated { + circuit_id, + src_peer_id, + src_connection_id, + inbound_circuit_req, + dst_handler_notifier: tx, + dst_stream, + dst_pending_data, + }, + )); + } + + fn inject_event(&mut self, event: Self::InEvent) { + match event { + RelayHandlerIn::AcceptReservationReq { + inbound_reservation_req, + addrs, + } => { + self.reservation_accept_futures + .push(inbound_reservation_req.accept(addrs).boxed()); + } + RelayHandlerIn::DenyReservationReq { + inbound_reservation_req, + status, + } => { + self.reservation_deny_futures + .push(inbound_reservation_req.deny(status).boxed()); + } + RelayHandlerIn::NegotiateOutboundConnect { + circuit_id, + inbound_circuit_req, + relay_peer_id, + src_peer_id, + src_connection_id, + } => { + self.queued_events + .push_back(ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new( + outbound_stop::Upgrade { + relay_peer_id, + max_circuit_duration: self.config.max_circuit_duration, + max_circuit_bytes: self.config.max_circuit_bytes, + }, + OutboundOpenInfo { + circuit_id, + inbound_circuit_req, + src_peer_id, + src_connection_id, + }, + ), + }); + } + RelayHandlerIn::DenyCircuitReq { + circuit_id, + inbound_circuit_req, + status, + } => { + let dst_peer_id = inbound_circuit_req.dst(); + self.circuit_deny_futures.push( + inbound_circuit_req + .deny(status) + .map(move |result| (circuit_id, dst_peer_id, result)) + .boxed(), + ); + } + RelayHandlerIn::AcceptAndDriveCircuit { + circuit_id, + dst_peer_id, + inbound_circuit_req, + dst_handler_notifier, + dst_stream, + dst_pending_data, + } => { + self.circuit_accept_futures.push( + inbound_circuit_req + .accept() + .map_ok(move |(src_stream, src_pending_data)| CircuitParts { + circuit_id, + src_stream, + src_pending_data, + dst_peer_id, + dst_handler_notifier, + dst_stream, + dst_pending_data, + }) + .map_err(move |e| (circuit_id, dst_peer_id, e)) + .boxed(), + ); + } + } + } + + fn inject_listen_upgrade_error( + &mut self, + _: Self::InboundOpenInfo, + error: ProtocolsHandlerUpgrErr<::Error>, + ) { + match error { + ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )) => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::ProtocolError(e), + )) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Select(upgrade::NegotiationError::ProtocolError(e)), + )); + } + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::A(error)), + )) + } + } + } + + fn inject_dial_upgrade_error( + &mut self, + open_info: Self::OutboundOpenInfo, + error: ProtocolsHandlerUpgrErr<::Error>, + ) { + let status = match error { + ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => { + Status::ConnectionFailed + } + // TODO: This should not happen, as the remote has previously done a reservation. + // Doing a reservation but not supporting the stop protocol seems odd. + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )) => Status::ConnectionFailed, + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::ProtocolError(e), + )) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Select(upgrade::NegotiationError::ProtocolError(e)), + )); + Status::ConnectionFailed + } + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { + match error { + outbound_stop::UpgradeError::Decode(_) + | outbound_stop::UpgradeError::Io(_) + | outbound_stop::UpgradeError::ParseTypeField + | outbound_stop::UpgradeError::MissingStatusField + | outbound_stop::UpgradeError::ParseStatusField + | outbound_stop::UpgradeError::UnexpectedTypeConnect => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )); + Status::ConnectionFailed + } + outbound_stop::UpgradeError::UnexpectedStatus(status) => { + match status { + Status::Ok => { + unreachable!("Status success is explicitly exempt.") + } + // A destination node returning nonsensical status is a protocol + // violation. Thus terminate the connection. + Status::ReservationRefused + | Status::NoReservation + | Status::ConnectionFailed => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )); + } + // With either status below there is no reason to stay connected. + // Thus terminate the connection. + Status::MalformedMessage | Status::UnexpectedMessage => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )) + } + // While useless for reaching this particular destination, the + // connection to the relay might still proof helpful for other + // destinations. Thus do not terminate the connection. + Status::ResourceLimitExceeded | Status::PermissionDenied => {} + } + status + } + } + } + }; + + let OutboundOpenInfo { + circuit_id, + inbound_circuit_req, + src_peer_id, + src_connection_id, + } = open_info; + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::OutboundConnectNegotiationFailed { + circuit_id, + src_peer_id, + src_connection_id, + inbound_circuit_req, + status, + }, + )); + } + + fn connection_keep_alive(&self) -> KeepAlive { + if self.reservation_deny_futures.is_empty() + && self.reservation_accept_futures.is_empty() + && self.active_reservation.is_none() + && self.alive_lend_out_substreams.is_empty() + { + KeepAlive::No + } else { + KeepAlive::Yes + } + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ProtocolsHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::OutEvent, + Self::Error, + >, + > { + // Check for a pending (fatal) error. + if let Some(err) = self.pending_error.take() { + // The handler will not be polled again by the `Swarm`. + return Poll::Ready(ProtocolsHandlerEvent::Close(err)); + } + + // Return queued events. + if let Some(event) = self.queued_events.pop_front() { + return Poll::Ready(event); + } + + while let Poll::Ready(Some((circuit_id, dst_peer_id, result))) = + self.circuits.poll_next_unpin(cx) + { + match result { + Ok(()) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::CircuitClosed { + circuit_id, + dst_peer_id, + error: None, + }, + )) + } + Err(e) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::CircuitClosed { + circuit_id, + dst_peer_id, + error: Some(e), + }, + )) + } + } + } + + while let Poll::Ready(Some(result)) = self.reservation_accept_futures.poll_next_unpin(cx) { + match result { + Ok(()) => { + let renewed = self + .active_reservation + .replace(Delay::new(self.config.reservation_duration)) + .is_some(); + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::ReservationReqAccepted { renewed }, + )); + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::ReservationReqAcceptFailed { error }, + )); + } + } + } + + while let Poll::Ready(Some(result)) = self.reservation_deny_futures.poll_next_unpin(cx) { + match result { + Ok(()) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::ReservationReqDenied {}, + )) + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::ReservationReqDenyFailed { error }, + )); + } + } + } + + while let Poll::Ready(Some(result)) = self.circuit_accept_futures.poll_next_unpin(cx) { + match result { + Ok(parts) => { + let CircuitParts { + circuit_id, + mut src_stream, + src_pending_data, + dst_peer_id, + dst_handler_notifier, + mut dst_stream, + dst_pending_data, + } = parts; + let max_circuit_duration = self.config.max_circuit_duration; + let max_circuit_bytes = self.config.max_circuit_bytes; + + let circuit = async move { + let (result_1, result_2) = futures::future::join( + src_stream.write_all(&dst_pending_data), + dst_stream.write_all(&src_pending_data), + ) + .await; + result_1?; + result_2?; + + CopyFuture::new( + src_stream, + dst_stream, + max_circuit_duration, + max_circuit_bytes, + ) + .await?; + + // Inform destination handler that the stream to the destination is dropped. + drop(dst_handler_notifier); + Ok(()) + } + .map(move |r| (circuit_id, dst_peer_id, r)) + .boxed(); + + self.circuits.push(circuit); + + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::CircuitReqAccepted { + circuit_id, + dst_peer_id, + }, + )); + } + Err((circuit_id, dst_peer_id, error)) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::CircuitReqAcceptFailed { + circuit_id, + dst_peer_id, + error, + }, + )); + } + } + } + + while let Poll::Ready(Some((circuit_id, dst_peer_id, result))) = + self.circuit_deny_futures.poll_next_unpin(cx) + { + match result { + Ok(()) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::CircuitReqDenied { + circuit_id, + dst_peer_id, + }, + )); + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::CircuitReqDenyFailed { + circuit_id, + dst_peer_id, + error, + }, + )); + } + } + } + + while let Poll::Ready(Some(Err(Canceled))) = + self.alive_lend_out_substreams.poll_next_unpin(cx) + {} + + if let Some(Poll::Ready(())) = self + .active_reservation + .as_mut() + .map(|fut| fut.poll_unpin(cx)) + { + self.active_reservation = None; + return Poll::Ready(ProtocolsHandlerEvent::Custom( + RelayHandlerEvent::ReservationTimedOut {}, + )); + } + + Poll::Pending + } +} + +pub struct OutboundOpenInfo { + circuit_id: CircuitId, + inbound_circuit_req: inbound_hop::CircuitReq, + src_peer_id: PeerId, + src_connection_id: ConnectionId, +} + +pub struct CircuitParts { + circuit_id: CircuitId, + src_stream: NegotiatedSubstream, + src_pending_data: Bytes, + dst_peer_id: PeerId, + dst_handler_notifier: oneshot::Sender<()>, + dst_stream: NegotiatedSubstream, + dst_pending_data: Bytes, +} diff --git a/protocols/relay/src/v2/message.proto b/protocols/relay/src/v2/message.proto new file mode 100644 index 00000000000..da6a29ca09c --- /dev/null +++ b/protocols/relay/src/v2/message.proto @@ -0,0 +1,63 @@ +syntax = "proto2"; + +package message_v2.pb; + +message HopMessage { + enum Type { + RESERVE = 0; + CONNECT = 1; + STATUS = 2; + } + + required Type type = 1; + + optional Peer peer = 2; + optional Reservation reservation = 3; + optional Limit limit = 4; + + optional Status status = 5; +} + +message StopMessage { + enum Type { + CONNECT = 0; + STATUS = 1; + } + + required Type type = 1; + + optional Peer peer = 2; + optional Limit limit = 3; + + optional Status status = 4; +} + +message Peer { + required bytes id = 1; + repeated bytes addrs = 2; +} + +message Reservation { + // TODO: Should this be a timestamp instead? + optional int64 expire = 1; // Unix expiration time (UTC) + repeated bytes addrs = 2; // relay addrs for reserving peer + optional bytes voucher = 3; // reservation voucher +} + +message Limit { + // TODO: Why use an int32 instead of a uint32? + optional int32 duration = 1; // seconds + // TODO: Why use an int64 instead of a uint64? + optional int64 data = 2; // bytes +} + +enum Status { + OK = 100; + RESERVATION_REFUSED = 200; + RESOURCE_LIMIT_EXCEEDED = 201; + PERMISSION_DENIED = 202; + CONNECTION_FAILED = 203; + NO_RESERVATION = 204; + MALFORMED_MESSAGE = 400; + UNEXPECTED_MESSAGE = 401; +} diff --git a/protocols/relay/src/v2/protocol.rs b/protocols/relay/src/v2/protocol.rs new file mode 100644 index 00000000000..ad0b81eaff3 --- /dev/null +++ b/protocols/relay/src/v2/protocol.rs @@ -0,0 +1,27 @@ +// 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. + +pub mod inbound_hop; +pub mod outbound_stop; + +const HOP_PROTOCOL_NAME: &[u8; 31] = b"/libp2p/circuit/relay/0.2.0/hop"; +const STOP_PROTOCOL_NAME: &[u8; 32] = b"/libp2p/circuit/relay/0.2.0/stop"; + +const MAX_MESSAGE_SIZE: usize = 4096; diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs new file mode 100644 index 00000000000..665d8d822c6 --- /dev/null +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -0,0 +1,279 @@ +// 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 crate::v2::message_proto::{hop_message, HopMessage, Limit, Reservation, Status}; +use crate::v2::protocol::{HOP_PROTOCOL_NAME, MAX_MESSAGE_SIZE}; +use asynchronous_codec::{Framed, FramedParts}; +use bytes::{Bytes, BytesMut}; +use futures::{future::BoxFuture, prelude::*}; +use libp2p_core::{upgrade, Multiaddr, PeerId}; +use libp2p_swarm::NegotiatedSubstream; +use prost::Message; +use std::convert::TryInto; +use std::io::Cursor; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{error, fmt, iter}; +use unsigned_varint::codec::UviBytes; + +pub struct Upgrade { + pub reservation_duration: Duration, + pub max_circuit_duration: Duration, + pub max_circuit_bytes: u64, +} + +impl upgrade::UpgradeInfo for Upgrade { + type Info = &'static [u8]; + type InfoIter = iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + iter::once(HOP_PROTOCOL_NAME) + } +} + +impl upgrade::InboundUpgrade for Upgrade { + type Output = Req; + type Error = UpgradeError; + type Future = BoxFuture<'static, Result>; + + fn upgrade_inbound(self, substream: NegotiatedSubstream, _: Self::Info) -> Self::Future { + async move { + let mut codec = UviBytes::::default(); + codec.set_max_len(MAX_MESSAGE_SIZE); + let mut substream = Framed::new(substream, codec); + + let msg: bytes::BytesMut = substream + .next() + .await + .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + + let HopMessage { + r#type, + peer, + reservation: _, + limit: _, + status: _, + } = HopMessage::decode(Cursor::new(msg))?; + + let r#type = hop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + match r#type { + hop_message::Type::Reserve => Ok(Req::Reserve(ReservationReq { + substream, + reservation_duration: self.reservation_duration, + max_circuit_duration: self.max_circuit_duration, + max_circuit_bytes: self.max_circuit_bytes, + })), + hop_message::Type::Connect => { + let dst = PeerId::from_bytes(&peer.ok_or(UpgradeError::MissingPeer)?.id) + .map_err(|_| UpgradeError::ParsePeerId)?; + Ok(Req::Connect(CircuitReq { substream, dst })) + } + hop_message::Type::Status => Err(UpgradeError::UnexpectedTypeStatus), + } + } + .boxed() + } +} + +#[derive(Debug)] +pub enum UpgradeError { + Decode(prost::DecodeError), + Io(std::io::Error), + ParseTypeField, + ParsePeerId, + MissingPeer, + UnexpectedTypeStatus, +} + +impl From for UpgradeError { + fn from(e: std::io::Error) -> Self { + UpgradeError::Io(e) + } +} + +impl From for UpgradeError { + fn from(e: prost::DecodeError) -> Self { + UpgradeError::Decode(e) + } +} + +impl fmt::Display for UpgradeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UpgradeError::Decode(e) => { + write!(f, "Failed to decode response: {}.", e) + } + UpgradeError::Io(e) => { + write!(f, "Io error {}", e) + } + UpgradeError::ParseTypeField => { + write!(f, "Failed to parse response type field.") + } + UpgradeError::ParsePeerId => { + write!(f, "Failed to parse peer id.") + } + UpgradeError::MissingPeer => { + write!(f, "Expected 'peer' field to be set.") + } + UpgradeError::UnexpectedTypeStatus => { + write!(f, "Unexpected message type 'status'") + } + } + } +} + +impl error::Error for UpgradeError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + UpgradeError::Decode(e) => Some(e), + UpgradeError::Io(e) => Some(e), + UpgradeError::ParseTypeField => None, + UpgradeError::ParsePeerId => None, + UpgradeError::MissingPeer => None, + UpgradeError::UnexpectedTypeStatus => None, + } + } +} + +pub enum Req { + Reserve(ReservationReq), + Connect(CircuitReq), +} + +pub struct ReservationReq { + substream: Framed, + reservation_duration: Duration, + max_circuit_duration: Duration, + max_circuit_bytes: u64, +} + +impl ReservationReq { + pub async fn accept(self, addrs: Vec) -> Result<(), std::io::Error> { + let msg = HopMessage { + r#type: hop_message::Type::Status.into(), + peer: None, + reservation: Some(Reservation { + addrs: addrs.into_iter().map(|a| a.to_vec()).collect(), + expire: Some( + (SystemTime::now() + self.reservation_duration) + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + .try_into() + // TODO: Can we do better? Why represent time as an i64 in protobuf in the first place? + .expect("Time to fit i64."), + ), + // TODO: Does this need to be set? + voucher: None, + }), + limit: Some(Limit { + // TODO: Handle the unwrap. Why use an i32 in protobuf in the first place? + duration: Some(self.max_circuit_duration.as_secs().try_into().unwrap()), + // TODO: Handle the unwrap. Why use an i64 instead of a u64? + data: Some(self.max_circuit_bytes.try_into().unwrap()), + }), + status: Some(Status::Ok.into()), + }; + + self.send(msg).await + } + + pub async fn deny(self, status: Status) -> Result<(), std::io::Error> { + let msg = HopMessage { + r#type: hop_message::Type::Status.into(), + peer: None, + reservation: None, + limit: None, + status: Some(status.into()), + }; + + self.send(msg).await + } + + async fn send(mut self, msg: HopMessage) -> Result<(), std::io::Error> { + let mut msg_bytes = BytesMut::new(); + msg.encode(&mut msg_bytes) + // TODO: Sure panicing is safe here? + .expect("all the mandatory fields are always filled; QED"); + self.substream.send(msg_bytes.freeze()).await?; + self.substream.flush().await?; + self.substream.close().await?; + + Ok(()) + } +} + +pub struct CircuitReq { + dst: PeerId, + substream: Framed, +} + +impl CircuitReq { + pub fn dst(&self) -> PeerId { + self.dst + } + + pub async fn accept(mut self) -> Result<(NegotiatedSubstream, Bytes), std::io::Error> { + let msg = HopMessage { + r#type: hop_message::Type::Status.into(), + peer: None, + reservation: None, + limit: None, + status: Some(Status::Ok.into()), + }; + + self.send(msg).await?; + + let FramedParts { + io, + read_buffer, + write_buffer, + .. + } = self.substream.into_parts(); + assert!( + write_buffer.is_empty(), + "Expect a flushed Framed to have an empty write buffer." + ); + + Ok((io, read_buffer.freeze())) + } + + pub async fn deny(mut self, status: Status) -> Result<(), std::io::Error> { + let msg = HopMessage { + r#type: hop_message::Type::Status.into(), + peer: None, + reservation: None, + limit: None, + status: Some(status.into()), + }; + self.send(msg).await?; + self.substream.close().await + } + + async fn send(&mut self, msg: HopMessage) -> Result<(), std::io::Error> { + let mut msg_bytes = BytesMut::new(); + msg.encode(&mut msg_bytes) + // TODO: Sure panicing is safe here? + .expect("all the mandatory fields are always filled; QED"); + self.substream.send(msg_bytes.freeze()).await?; + self.substream.flush().await?; + + Ok(()) + } +} diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs new file mode 100644 index 00000000000..c8a969be004 --- /dev/null +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -0,0 +1,188 @@ +// 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 crate::v2::message_proto::{stop_message, Limit, Peer, Status, StopMessage}; +use crate::v2::protocol::{MAX_MESSAGE_SIZE, STOP_PROTOCOL_NAME}; +use asynchronous_codec::{Framed, FramedParts}; +use bytes::Bytes; +use futures::{future::BoxFuture, prelude::*}; +use libp2p_core::{upgrade, PeerId}; +use libp2p_swarm::NegotiatedSubstream; +use prost::Message; +use std::convert::TryInto; +use std::io::Cursor; +use std::time::Duration; +use std::{error, fmt, iter}; +use unsigned_varint::codec::UviBytes; + +pub struct Upgrade { + pub relay_peer_id: PeerId, + pub max_circuit_duration: Duration, + pub max_circuit_bytes: u64, +} + +impl upgrade::UpgradeInfo for Upgrade { + type Info = &'static [u8]; + type InfoIter = iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + iter::once(STOP_PROTOCOL_NAME) + } +} + +impl upgrade::OutboundUpgrade for Upgrade { + type Output = (NegotiatedSubstream, Bytes); + type Error = UpgradeError; + type Future = BoxFuture<'static, Result>; + + fn upgrade_outbound(self, substream: NegotiatedSubstream, _: Self::Info) -> Self::Future { + let msg = StopMessage { + r#type: stop_message::Type::Connect.into(), + peer: Some(Peer { + id: self.relay_peer_id.to_bytes(), + addrs: vec![], + }), + limit: Some(Limit { + // TODO: Handle the unwrap. Why use an i32 in protobuf in the first place? + duration: Some(self.max_circuit_duration.as_secs().try_into().unwrap()), + // TODO: Handle the unwrap. Why use an i64 instead of a u64? + data: Some(self.max_circuit_bytes.try_into().unwrap()), + }), + status: None, + }; + + let mut encoded_msg = Vec::new(); + msg.encode(&mut encoded_msg) + // TODO: Double check. Safe to panic here? + .expect("all the mandatory fields are always filled; QED"); + + let mut codec = UviBytes::default(); + codec.set_max_len(MAX_MESSAGE_SIZE); + let mut substream = Framed::new(substream, codec); + + async move { + substream.send(std::io::Cursor::new(encoded_msg)).await?; + let msg: bytes::BytesMut = substream + .next() + .await + .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + + let StopMessage { + r#type, + peer: _, + limit: _, + status, + } = StopMessage::decode(Cursor::new(msg))?; + + let r#type = + stop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + match r#type { + stop_message::Type::Connect => return Err(UpgradeError::UnexpectedTypeConnect), + stop_message::Type::Status => {} + } + + let status = Status::from_i32(status.ok_or(UpgradeError::MissingStatusField)?) + .ok_or(UpgradeError::ParseStatusField)?; + match status { + Status::Ok => {} + s => return Err(UpgradeError::UnexpectedStatus(s)), + } + + let FramedParts { + io, + read_buffer, + write_buffer, + .. + } = substream.into_parts(); + assert!( + write_buffer.is_empty(), + "Expect a flushed Framed to have an empty write buffer." + ); + + Ok((io, read_buffer.freeze())) + } + .boxed() + } +} + +#[derive(Debug)] +pub enum UpgradeError { + Decode(prost::DecodeError), + Io(std::io::Error), + MissingStatusField, + ParseTypeField, + UnexpectedTypeConnect, + ParseStatusField, + UnexpectedStatus(Status), +} + +impl From for UpgradeError { + fn from(e: std::io::Error) -> Self { + UpgradeError::Io(e) + } +} + +impl From for UpgradeError { + fn from(e: prost::DecodeError) -> Self { + UpgradeError::Decode(e) + } +} + +impl fmt::Display for UpgradeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UpgradeError::Decode(e) => { + write!(f, "Failed to decode response: {}.", e) + } + UpgradeError::Io(e) => { + write!(f, "Io error {}", e) + } + UpgradeError::MissingStatusField => { + write!(f, "Expected 'status' field to be set.") + } + UpgradeError::ParseTypeField => { + write!(f, "Failed to parse response type field.") + } + UpgradeError::UnexpectedTypeConnect => { + write!(f, "Unexpected message type 'connect'") + } + UpgradeError::ParseStatusField => { + write!(f, "Failed to parse response type field.") + } + UpgradeError::UnexpectedStatus(status) => { + write!(f, "Unexpected message status '{:?}'", status) + } + } + } +} + +impl error::Error for UpgradeError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + UpgradeError::Decode(e) => Some(e), + UpgradeError::Io(e) => Some(e), + UpgradeError::MissingStatusField => None, + UpgradeError::ParseTypeField => None, + UpgradeError::UnexpectedTypeConnect => None, + UpgradeError::ParseStatusField => None, + UpgradeError::UnexpectedStatus(_) => None, + } + } +} diff --git a/protocols/relay/tests/lib.rs b/protocols/relay/tests/v1.rs similarity index 97% rename from protocols/relay/tests/lib.rs rename to protocols/relay/tests/v1.rs index 40890fdb024..a0494c6fc4f 100644 --- a/protocols/relay/tests/lib.rs +++ b/protocols/relay/tests/v1.rs @@ -34,7 +34,7 @@ use libp2p_identify::{Identify, IdentifyConfig, IdentifyEvent, IdentifyInfo}; use libp2p_kad::{GetClosestPeersOk, Kademlia, KademliaEvent, QueryResult}; use libp2p_ping::{Ping, PingConfig, PingEvent}; use libp2p_plaintext::PlainText2Config; -use libp2p_relay::{Relay, RelayConfig}; +use libp2p_relay::v1::{new_transport_and_behaviour, Relay, RelayConfig}; use libp2p_swarm::protocols_handler::{ KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, }; @@ -73,7 +73,9 @@ fn src_connect_to_dst_listening_via_relay() { relay_swarm.listen_on(relay_addr.clone()).unwrap(); spawn_swarm_on_pool(&pool, relay_swarm); - dst_swarm.listen_on(dst_listen_addr_via_relay.clone()).unwrap(); + dst_swarm + .listen_on(dst_listen_addr_via_relay.clone()) + .unwrap(); pool.run_until(async { // Destination Node dialing Relay. @@ -381,9 +383,9 @@ fn src_try_connect_to_offline_dst() { loop { match src_swarm.next_event().await { - SwarmEvent::UnreachableAddr { address, peer_id, .. } - if address == dst_addr_via_relay => - { + SwarmEvent::UnreachableAddr { + address, peer_id, .. + } if address == dst_addr_via_relay => { assert_eq!(peer_id, dst_peer_id); break; } @@ -438,9 +440,9 @@ fn src_try_connect_to_unsupported_dst() { loop { match src_swarm.next_event().await { - SwarmEvent::UnreachableAddr { address, peer_id, .. } - if address == dst_addr_via_relay => - { + SwarmEvent::UnreachableAddr { + address, peer_id, .. + } if address == dst_addr_via_relay => { assert_eq!(peer_id, dst_peer_id); break; } @@ -488,10 +490,11 @@ fn src_try_connect_to_offline_dst_via_offline_relay() { // Source Node fail to reach Destination Node due to failure reaching Relay. match src_swarm.next_event().await { - SwarmEvent::UnreachableAddr { address, peer_id, .. } - if address == dst_addr_via_relay => { - assert_eq!(peer_id, dst_peer_id); - } + SwarmEvent::UnreachableAddr { + address, peer_id, .. + } if address == dst_addr_via_relay => { + assert_eq!(peer_id, dst_peer_id); + } e => panic!("{:?}", e), } }); @@ -904,8 +907,12 @@ fn yield_incoming_connection_through_correct_listener() { relay_3_swarm.listen_on(relay_3_addr.clone()).unwrap(); spawn_swarm_on_pool(&pool, relay_3_swarm); - dst_swarm.listen_on(relay_1_addr_incl_circuit.clone()).unwrap(); - dst_swarm.listen_on(relay_2_addr_incl_circuit.clone()).unwrap(); + dst_swarm + .listen_on(relay_1_addr_incl_circuit.clone()) + .unwrap(); + dst_swarm + .listen_on(relay_2_addr_incl_circuit.clone()) + .unwrap(); // Listen on own address in order for relay 3 to be able to connect to destination node. dst_swarm.listen_on(dst_addr.clone()).unwrap(); @@ -1014,9 +1021,9 @@ fn yield_incoming_connection_through_correct_listener() { } match src_3_swarm.next_event().boxed().poll_unpin(cx) { - Poll::Ready(SwarmEvent::UnreachableAddr { address, peer_id, .. }) - if address == dst_addr_via_relay_3 => - { + Poll::Ready(SwarmEvent::UnreachableAddr { + address, peer_id, .. + }) if address == dst_addr_via_relay_3 => { assert_eq!(peer_id, dst_peer_id); return Poll::Ready(()); } @@ -1219,7 +1226,7 @@ fn build_swarm(reachability: Reachability, relay_mode: RelayMode) -> Swarm EitherTransport::Right(transport), }; - let (transport, relay_behaviour) = libp2p_relay::new_transport_and_behaviour( + let (transport, relay_behaviour) = new_transport_and_behaviour( RelayConfig { actively_connect_to_dst_nodes: relay_mode.into(), ..Default::default() @@ -1261,7 +1268,7 @@ fn build_keep_alive_swarm() -> Swarm { let transport = MemoryTransport::default(); let (transport, relay_behaviour) = - libp2p_relay::new_transport_and_behaviour(RelayConfig::default(), transport); + new_transport_and_behaviour(RelayConfig::default(), transport); let transport = transport .upgrade(upgrade::Version::V1) From 125e3c3e5740fb9334a3ccc3e9598f2479fbd635 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 26 Apr 2021 17:55:03 +0200 Subject: [PATCH 002/108] misc/multistream-select: Ignore simultaneous open 'iamclient' --- misc/multistream-select/src/protocol.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/misc/multistream-select/src/protocol.rs b/misc/multistream-select/src/protocol.rs index 1d056de75ec..eeffc87c888 100644 --- a/misc/multistream-select/src/protocol.rs +++ b/misc/multistream-select/src/protocol.rs @@ -42,6 +42,7 @@ const MSG_MULTISTREAM_1_0: &[u8] = b"/multistream/1.0.0\n"; const MSG_PROTOCOL_NA: &[u8] = b"na\n"; /// The encoded form of a multistream-select 'ls' message. const MSG_LS: &[u8] = b"ls\n"; +const MSG_IAMCLIENT: &[u8] = b"iamclient\n"; /// The multistream-select header lines preceeding negotiation. /// @@ -171,6 +172,13 @@ impl Message { return Ok(Message::ListProtocols) } + // TODO: This is a hack for now. + // + // See https://github.com/libp2p/specs/pull/196#discussion_r615846605 for details. + if msg == MSG_IAMCLIENT { + return Ok(Message::Protocol(Protocol("iamclient".into()))) + } + // If it starts with a `/`, ends with a line feed without any // other line feeds in-between, it must be a protocol name. if msg.get(0) == Some(&b'/') && msg.last() == Some(&b'\n') && From 4281bd547e6f40782ba495b51b137c6f1f34a89d Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 30 Apr 2021 10:04:04 +0200 Subject: [PATCH 003/108] protocols/relay: Ensure connections of HOP connect are kept alive --- protocols/relay/src/v2/handler.rs | 34 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/protocols/relay/src/v2/handler.rs b/protocols/relay/src/v2/handler.rs index ceaa7790143..ce10439c43b 100644 --- a/protocols/relay/src/v2/handler.rs +++ b/protocols/relay/src/v2/handler.rs @@ -40,7 +40,7 @@ use libp2p_swarm::{ }; use std::collections::VecDeque; use std::task::{Context, Poll}; -use std::time::Duration; +use std::time::{Duration, Instant}; pub struct Config { pub reservation_duration: Duration, @@ -155,6 +155,7 @@ impl IntoProtocolsHandler for RelayHandlerProto { alive_lend_out_substreams: Default::default(), circuits: Default::default(), active_reservation: Default::default(), + keep_alive: KeepAlive::Yes, } } @@ -202,6 +203,7 @@ pub struct RelayHandler { circuits: FuturesUnordered)>>, active_reservation: Option, + keep_alive: KeepAlive, } impl ProtocolsHandler for RelayHandler { @@ -466,16 +468,9 @@ impl ProtocolsHandler for RelayHandler { )); } + // TODO: Why is this not a mut reference? If it were the case, we could do all keep alive handling in here. fn connection_keep_alive(&self) -> KeepAlive { - if self.reservation_deny_futures.is_empty() - && self.reservation_accept_futures.is_empty() - && self.active_reservation.is_none() - && self.alive_lend_out_substreams.is_empty() - { - KeepAlive::No - } else { - KeepAlive::Yes - } + self.keep_alive } fn poll( @@ -658,6 +653,25 @@ impl ProtocolsHandler for RelayHandler { )); } + if self.reservation_accept_futures.is_empty() + && self.reservation_deny_futures.is_empty() + && self.circuit_accept_futures.is_empty() + && self.circuit_deny_futures.is_empty() + && self.alive_lend_out_substreams.is_empty() + && self.circuits.is_empty() + && self.active_reservation.is_none() + { + match self.keep_alive { + KeepAlive::Yes => { + self.keep_alive = KeepAlive::Until(Instant::now() + Duration::from_secs(10)); + } + KeepAlive::Until(_) => {} + KeepAlive::No => panic!("RelayHandler never sets KeepAlive::No."), + } + } else { + self.keep_alive = KeepAlive::Yes; + } + Poll::Pending } } From 778512d4bf00319ded1e87369cc627bf1df6dc6f Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 3 May 2021 15:55:19 +0200 Subject: [PATCH 004/108] protocols/relay: Improve documentation --- protocols/relay/src/lib.rs | 6 +-- protocols/relay/src/v1/behaviour.rs | 2 +- protocols/relay/src/v1/transport.rs | 2 +- protocols/relay/src/v2.rs | 3 ++ protocols/relay/src/v2/behaviour.rs | 16 ++++++- protocols/relay/src/v2/handler.rs | 71 +++++++++++++++++++---------- 6 files changed, 69 insertions(+), 31 deletions(-) diff --git a/protocols/relay/src/lib.rs b/protocols/relay/src/lib.rs index 391051ed22b..02cfefbe639 100644 --- a/protocols/relay/src/lib.rs +++ b/protocols/relay/src/lib.rs @@ -1,4 +1,5 @@ // Copyright 2019 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"), @@ -18,14 +19,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -mod message_proto_v2 { - include!(concat!(env!("OUT_DIR"), "/message_v2.pb.rs")); -} +//! libp2p circuit relay implementations pub mod v1; pub mod v2; - // Check that we can safely cast a `usize` to a `u64`. static_assertions::const_assert! { std::mem::size_of::() <= std::mem::size_of::() diff --git a/protocols/relay/src/v1/behaviour.rs b/protocols/relay/src/v1/behaviour.rs index 6be0a5da60b..5884568d2d8 100644 --- a/protocols/relay/src/v1/behaviour.rs +++ b/protocols/relay/src/v1/behaviour.rs @@ -769,7 +769,7 @@ enum RelayListener { impl RelayListener { /// Returns whether the channel to the - /// [`RelayListener`](crate::transport::RelayListener) is closed. + /// [`RelayListener`](crate::v1::RelayListener) is closed. fn is_closed(&self) -> bool { match self { RelayListener::Connecting { to_listener, .. } diff --git a/protocols/relay/src/v1/transport.rs b/protocols/relay/src/v1/transport.rs index 65497cb6978..db5b835ccf0 100644 --- a/protocols/relay/src/v1/transport.rs +++ b/protocols/relay/src/v1/transport.rs @@ -533,7 +533,7 @@ impl std::fmt::Display for RelayError { impl std::error::Error for RelayError {} -/// Message from the [`RelayTransport`] to the [`Relay`](crate::Relay) +/// Message from the [`RelayTransport`] to the [`Relay`](crate::v1::Relay) /// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). pub enum TransportToBehaviourMsg { /// Dial destination node via relay node. diff --git a/protocols/relay/src/v2.rs b/protocols/relay/src/v2.rs index 37aa2995775..f98e4394027 100644 --- a/protocols/relay/src/v2.rs +++ b/protocols/relay/src/v2.rs @@ -18,6 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +//! Implementation of the [libp2p circuit relay v2 +//! specification](https://github.com/libp2p/specs/issues/314). + mod message_proto { include!(concat!(env!("OUT_DIR"), "/message_v2.pb.rs")); } diff --git a/protocols/relay/src/v2/behaviour.rs b/protocols/relay/src/v2/behaviour.rs index 0b30d5459a4..da3cff4f211 100644 --- a/protocols/relay/src/v2/behaviour.rs +++ b/protocols/relay/src/v2/behaviour.rs @@ -60,45 +60,56 @@ impl Default for Config { } } +/// The events produced by the [`Relay`] behaviour. #[derive(Debug)] pub enum RelayEvent { - // TODO: Note that this can both be a new one or a renewal. + /// An inbound reservation request has been accepted. ReservationReqAccepted { src_peer_id: PeerId, + /// Indicates whether the request replaces an existing reservation. renewed: bool, }, + /// Accepting an inbound reservation request failed. ReservationReqAcceptFailed { src_peer_id: PeerId, error: std::io::Error, }, + /// An inbound reservation request has been denied. ReservationReqDenied { src_peer_id: PeerId, }, + /// Denying an inbound reservation request has failed. ReservationReqDenyFailed { src_peer_id: PeerId, error: std::io::Error, }, + /// An inbound reservation has timed out. ReservationTimedOut { src_peer_id: PeerId, }, + /// An inbound circuit request has been denied. CircuitReqDenied { src_peer_id: PeerId, dst_peer_id: PeerId, }, + /// Denying an inbound circuit request failed. CircuitReqDenyFailed { src_peer_id: PeerId, dst_peer_id: PeerId, error: std::io::Error, }, + /// An inbound cirucit request has been accepted. CircuitReqAccepted { src_peer_id: PeerId, dst_peer_id: PeerId, }, + /// Accepting an inbound circuit request failed. CircuitReqAcceptFailed { src_peer_id: PeerId, dst_peer_id: PeerId, error: std::io::Error, }, + /// An inbound circuit has closed. CircuitClosed { src_peer_id: PeerId, dst_peer_id: PeerId, @@ -106,7 +117,8 @@ pub enum RelayEvent { }, } -// TODO: Consider having one Behaviour for relay and one for both source and destination. +/// [`Relay`] is a [`NetworkBehaviour`] that implements the relay server +/// functionality of the circuit relay v2 protocol. pub struct Relay { config: Config, diff --git a/protocols/relay/src/v2/handler.rs b/protocols/relay/src/v2/handler.rs index ce10439c43b..583bbb7b27f 100644 --- a/protocols/relay/src/v2/handler.rs +++ b/protocols/relay/src/v2/handler.rs @@ -18,8 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::v2::message_proto; - use crate::v2::behaviour::CircuitId; use crate::v2::copy_future::CopyFuture; use crate::v2::message_proto::Status; @@ -55,12 +53,12 @@ pub enum RelayHandlerIn { }, DenyReservationReq { inbound_reservation_req: inbound_hop::ReservationReq, - status: message_proto::Status, + status: Status, }, DenyCircuitReq { circuit_id: Option, inbound_circuit_req: inbound_hop::CircuitReq, - status: message_proto::Status, + status: Status, }, NegotiateOutboundConnect { circuit_id: CircuitId, @@ -79,40 +77,51 @@ pub enum RelayHandlerIn { }, } +/// The events produced by the [`RelayHandler`]. pub enum RelayHandlerEvent { + /// An inbound reservation request has been received. ReservationReqReceived { inbound_reservation_req: inbound_hop::ReservationReq, }, + /// An inbound reservation request has been accepted. ReservationReqAccepted { + /// Indicates whether the request replaces an existing reservation. renewed: bool, }, - ReservationReqAcceptFailed { - error: std::io::Error, - }, + /// Accepting an inbound reservation request failed. + ReservationReqAcceptFailed { error: std::io::Error }, + /// An inbound reservation request has been denied. ReservationReqDenied {}, - ReservationReqDenyFailed { - error: std::io::Error, - }, + /// Denying an inbound reservation request has failed. + ReservationReqDenyFailed { error: std::io::Error }, + /// An inbound reservation has timed out. ReservationTimedOut {}, + /// An inbound circuit request has been received. CircuitReqReceived(inbound_hop::CircuitReq), + /// An inbound circuit request has been denied. CircuitReqDenied { circuit_id: Option, dst_peer_id: PeerId, }, + /// Denying an inbound circuit request failed. CircuitReqDenyFailed { circuit_id: Option, dst_peer_id: PeerId, error: std::io::Error, }, + /// An inbound cirucit request has been accepted. CircuitReqAccepted { circuit_id: CircuitId, dst_peer_id: PeerId, }, + /// Accepting an inbound circuit request failed. CircuitReqAcceptFailed { circuit_id: CircuitId, dst_peer_id: PeerId, error: std::io::Error, }, + /// An outbound substream for an inbound circuit request has been + /// negotiated. OutboundConnectNegotiated { circuit_id: CircuitId, src_peer_id: PeerId, @@ -122,6 +131,7 @@ pub enum RelayHandlerEvent { dst_stream: NegotiatedSubstream, dst_pending_data: Bytes, }, + /// Negotiating an outbound substream for an inbound circuit request failed. OutboundConnectNegotiationFailed { circuit_id: CircuitId, src_peer_id: PeerId, @@ -129,6 +139,7 @@ pub enum RelayHandlerEvent { inbound_circuit_req: inbound_hop::CircuitReq, status: Status, }, + /// An inbound circuit has closed. CircuitClosed { circuit_id: CircuitId, dst_peer_id: PeerId, @@ -168,7 +179,10 @@ impl IntoProtocolsHandler for RelayHandlerProto { } } +/// [`ProtocolsHandler`] that manages substreams for a relay on a single +/// connection with a peer. pub struct RelayHandler { + /// Static [`RelayHandler`] [`Config`]. config: Config, /// Queue of events to return when polled. @@ -188,24 +202,35 @@ pub struct RelayHandler { >, >, - reservation_accept_futures: FuturesUnordered>>, - reservation_deny_futures: FuturesUnordered>>, + /// Until when to keep the connection alive. + keep_alive: KeepAlive, - circuit_accept_futures: FuturesUnordered< - BoxFuture<'static, Result>, - >, - circuit_deny_futures: FuturesUnordered< - BoxFuture<'static, (Option, PeerId, Result<(), std::io::Error>)>, - >, + /// Futures accepting an inbound reservation request. + reservation_accept_futures: Futures>, + /// Futures denying an inbound reservation request. + reservation_deny_futures: Futures>, + /// Timeout for the currently active reservation. + active_reservation: Option, + /// Futures accepting an inbound circuit request. + circuit_accept_futures: Futures>, + /// Futures deying an inbound circuit request. + circuit_deny_futures: Futures<(Option, PeerId, Result<(), std::io::Error>)>, + /// Tracks substreams lend out to other [`RelayHandler`]s. + /// + /// Contains a [`futures::future::Future`] for each lend out substream that + /// resolves once the substream is dropped. + /// + /// Once all substreams are dropped and this handler has no other work, + /// [`KeepAlive::Until`] can be set, allowing the connection to be closed + /// eventually. alive_lend_out_substreams: FuturesUnordered>, - - circuits: FuturesUnordered)>>, - - active_reservation: Option, - keep_alive: KeepAlive, + /// Futures relaying data for circuit between two peers. + circuits: Futures<(CircuitId, PeerId, Result<(), std::io::Error>)>, } +type Futures = FuturesUnordered>; + impl ProtocolsHandler for RelayHandler { type InEvent = RelayHandlerIn; type OutEvent = RelayHandlerEvent; From dfe1285126ac25d160e6969870d357ce092d1259 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 8 May 2021 11:36:38 +0200 Subject: [PATCH 005/108] protocols/relay: Implement v2 client logic --- protocols/relay/examples/relay_v2.rs | 14 +- protocols/relay/src/v2.rs | 14 +- protocols/relay/src/v2/client.rs | 502 ++++++++++++++++ protocols/relay/src/v2/client/handler.rs | 306 ++++++++++ protocols/relay/src/v2/client/transport.rs | 559 ++++++++++++++++++ protocols/relay/src/v2/protocol.rs | 2 + .../relay/src/v2/protocol/inbound_hop.rs | 8 +- .../relay/src/v2/protocol/inbound_stop.rs | 190 ++++++ .../relay/src/v2/protocol/outbound_hop.rs | 282 +++++++++ .../relay/src/v2/{behaviour.rs => relay.rs} | 98 +-- protocols/relay/src/v2/{ => relay}/handler.rs | 70 +-- protocols/relay/tests/v2.rs | 254 ++++++++ 12 files changed, 2203 insertions(+), 96 deletions(-) create mode 100644 protocols/relay/src/v2/client.rs create mode 100644 protocols/relay/src/v2/client/handler.rs create mode 100644 protocols/relay/src/v2/client/transport.rs create mode 100644 protocols/relay/src/v2/protocol/inbound_stop.rs create mode 100644 protocols/relay/src/v2/protocol/outbound_hop.rs rename protocols/relay/src/v2/{behaviour.rs => relay.rs} (87%) rename protocols/relay/src/v2/{ => relay}/handler.rs (93%) create mode 100644 protocols/relay/tests/v2.rs diff --git a/protocols/relay/examples/relay_v2.rs b/protocols/relay/examples/relay_v2.rs index 87072fdf1f8..cd9b9f5ca1b 100644 --- a/protocols/relay/examples/relay_v2.rs +++ b/protocols/relay/examples/relay_v2.rs @@ -22,10 +22,10 @@ use futures::executor::block_on; use futures::stream::StreamExt; use libp2p::core::upgrade; -use libp2p::identify::{Identify, IdentifyEvent, IdentifyConfig}; +use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; use libp2p::noise; -use libp2p::ping::{Ping, PingEvent, PingConfig}; -use libp2p::relay::v2::{Relay, RelayEvent}; +use libp2p::ping::{Ping, PingConfig, PingEvent}; +use libp2p::relay::v2::relay::{self, Relay}; use libp2p::tcp::TcpConfig; use libp2p::Transport; use libp2p::{identity, NetworkBehaviour, PeerId, Swarm}; @@ -63,7 +63,7 @@ fn main() -> Result<(), Box> { enum Event { Ping(PingEvent), Identify(IdentifyEvent), - Relay(RelayEvent), + Relay(relay::Event), } impl From for Event { @@ -78,8 +78,8 @@ fn main() -> Result<(), Box> { } } - impl From for Event { - fn from(e: RelayEvent) -> Self { + impl From for Event { + fn from(e: relay::Event) -> Self { Event::Relay(e) } } @@ -103,7 +103,7 @@ fn main() -> Result<(), Box> { loop { match swarm.poll_next_unpin(cx) { Poll::Ready(Some(Event::Relay(event))) => println!("{:?}", event), - Poll::Ready(Some(_)) => {}, + Poll::Ready(Some(_)) => {} Poll::Ready(None) => return Poll::Ready(Ok(())), Poll::Pending => { if !listening { diff --git a/protocols/relay/src/v2.rs b/protocols/relay/src/v2.rs index f98e4394027..a50d13a126f 100644 --- a/protocols/relay/src/v2.rs +++ b/protocols/relay/src/v2.rs @@ -25,9 +25,17 @@ mod message_proto { include!(concat!(env!("OUT_DIR"), "/message_v2.pb.rs")); } -mod behaviour; +pub mod client; mod copy_future; -mod handler; mod protocol; +pub mod relay; -pub use behaviour::{Relay, RelayEvent}; \ No newline at end of file +/// The ID of an outgoing / incoming, relay / destination request. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct RequestId(u64); + +impl RequestId { + fn new() -> RequestId { + RequestId(rand::random()) + } +} diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs new file mode 100644 index 00000000000..6ea10a66c97 --- /dev/null +++ b/protocols/relay/src/v2/client.rs @@ -0,0 +1,502 @@ +// 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. + +//! [`NetworkBehaviour`] to act as a circuit relay v2 **client**. + +mod handler; +mod transport; + +use crate::v2::protocol::inbound_stop; +use bytes::{Bytes}; +use futures::channel::mpsc::{Receiver, Sender}; +use futures::channel::oneshot; +use futures::future::{BoxFuture, FutureExt}; +use futures::io::{AsyncRead, AsyncWrite}; +use futures::ready; +use futures::stream::StreamExt; +use libp2p_core::connection::{ConnectedPoint, ConnectionId}; +use libp2p_core::{Multiaddr, PeerId, Transport}; +use libp2p_swarm::NegotiatedSubstream; +use libp2p_swarm::{ + DialPeerCondition, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, +}; +use std::collections::{HashMap, VecDeque}; +use std::io::{Error, IoSlice}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +#[derive(Debug)] +pub enum Event { + Reserved { relay_peer_id: PeerId }, +} + +pub struct Client { + local_peer_id: PeerId, + + from_transport: Receiver, + connected_peers: HashMap>, + rqsts_pending_connection: HashMap>, + + /// Queue of actions to return when polled. + queued_actions: VecDeque>, +} + +impl Client { + pub fn new_transport_and_behaviour( + local_peer_id: PeerId, + transport: T, + ) -> (transport::ClientTransport, Self) { + let (transport, from_transport) = transport::ClientTransport::new(transport); + let behaviour = Client { + local_peer_id, + from_transport, + connected_peers: Default::default(), + rqsts_pending_connection: Default::default(), + queued_actions: Default::default(), + }; + (transport, behaviour) + } +} + +impl NetworkBehaviour for Client { + type ProtocolsHandler = handler::Prototype; + type OutEvent = Event; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + handler::Prototype::new(self.local_peer_id) + } + + fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { + self.rqsts_pending_connection + .get(peer_id) + .map(|rqsts| rqsts.iter().map(|r| r.relay_addr().clone()).collect()) + .unwrap_or_default() + } + + fn inject_connected(&mut self, _peer_id: &PeerId) {} + + fn inject_connection_established( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + _: &ConnectedPoint, + ) { + self.connected_peers + .entry(*peer_id) + .or_default() + .push(*connection_id); + + for rqst in self + .rqsts_pending_connection + .remove(peer_id) + .map(|rqsts| rqsts.into_iter()) + .into_iter() + .flatten() + { + match rqst { + RqstPendingConnection::Reservation { to_listener, .. } => { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + peer_id: *peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::Reserve { to_listener }, + }); + } + RqstPendingConnection::Circuit { + send_back, + dst_peer_id, + .. + } => { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + peer_id: *peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::EstablishCircuit { + send_back, + dst_peer_id, + }, + }); + } + } + } + } + + fn inject_dial_failure(&mut self, _peer_id: &PeerId) { + todo!(); + } + + fn inject_disconnected(&mut self, _peer: &PeerId) {} + + fn inject_connection_closed( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + _: &ConnectedPoint, + ) { + self.connected_peers.get_mut(peer_id).map(|cs| { + cs.remove( + cs.iter() + .position(|c| c == connection_id) + .expect("Connection to be known."), + ) + }); + } + + fn inject_event( + &mut self, + event_source: PeerId, + _connection: ConnectionId, + handler_event: handler::Event, + ) { + match handler_event { + handler::Event::Reserved => { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent(Event::Reserved { + relay_peer_id: event_source, + })) + } + } + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + _poll_parameters: &mut impl PollParameters, + ) -> Poll> { + if let Some(event) = self.queued_actions.pop_front() { + return Poll::Ready(event); + } + + loop { + match self.from_transport.poll_next_unpin(cx) { + Poll::Ready(Some(transport::TransportToBehaviourMsg::ListenReq { + relay_peer_id, + relay_addr, + to_listener, + })) => { + match self + .connected_peers + .get(&relay_peer_id) + .and_then(|cs| cs.get(0)) + { + Some(_connection) => { + todo!() + } + None => { + self.rqsts_pending_connection + .entry(relay_peer_id) + .or_default() + .push(RqstPendingConnection::Reservation { + relay_addr, + to_listener, + }); + return Poll::Ready(NetworkBehaviourAction::DialPeer { + peer_id: relay_peer_id, + condition: DialPeerCondition::Disconnected, + }); + } + } + } + Poll::Ready(Some(transport::TransportToBehaviourMsg::DialReq { + relay_addr, + relay_peer_id, + dst_peer_id, + send_back, + .. + })) => { + match self + .connected_peers + .get(&relay_peer_id) + .and_then(|cs| cs.get(0)) + { + Some(_connection) => { + todo!() + } + None => { + self.rqsts_pending_connection + .entry(relay_peer_id) + .or_default() + .push(RqstPendingConnection::Circuit { + relay_addr, + dst_peer_id, + send_back, + }); + return Poll::Ready(NetworkBehaviourAction::DialPeer { + peer_id: relay_peer_id, + condition: DialPeerCondition::Disconnected, + }); + } + } + } + Poll::Ready(None) => unreachable!( + "`Relay` `NetworkBehaviour` polled after channel from \ + `RelayTransport` has been closed.", + ), + Poll::Pending => break, + } + } + + // TODO: Should we return NetworkBehaviourAction::ReportObservedAddr when an outbound + // reservation is acknowledged with the addresses of the relay? + + Poll::Pending + } +} + +/// A [`NegotiatedSubstream`] acting as a relayed [`Connection`]. +// TODO: Rename to Circuit +pub enum Connection { + InboundAccepting { + accept: BoxFuture<'static, Result<(NegotiatedSubstream, Bytes), std::io::Error>>, + drop_notifier: oneshot::Sender<()>, + }, + Operational { + read_buffer: Bytes, + substream: NegotiatedSubstream, + drop_notifier: oneshot::Sender<()>, + }, + Poisoned, +} + +impl Unpin for Connection {} + +impl Connection { + pub(crate) fn new_inbound( + circuit: inbound_stop::Circuit, + drop_notifier: oneshot::Sender<()>, + ) -> Self { + Connection::InboundAccepting { + accept: circuit.accept().boxed(), + drop_notifier, + } + } + + pub(crate) fn new_outbound( + substream: NegotiatedSubstream, + read_buffer: Bytes, + drop_notifier: oneshot::Sender<()>, + ) -> Self { + Connection::Operational { + substream, + read_buffer, + drop_notifier, + } + } + + fn accept_inbound( + self: &mut Pin<&mut Self>, + cx: &mut Context, + mut accept: BoxFuture<'static, Result<(NegotiatedSubstream, Bytes), std::io::Error>>, + drop_notifier: oneshot::Sender<()>, + ) -> Poll> { + match accept.poll_unpin(cx) { + Poll::Ready(Ok((substream, read_buffer))) => { + **self = Connection::Operational { + substream, + read_buffer, + drop_notifier, + }; + return Poll::Ready(Ok(())) + } + Poll::Ready(Err(e)) => { + return Poll::Ready(Err(e)); + } + Poll::Pending => { + **self = Connection::InboundAccepting { + accept, + drop_notifier, + }; + return Poll::Pending; + } + } + } +} + +impl AsyncWrite for Connection { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + loop { + match std::mem::replace(&mut *self, Connection::Poisoned) { + Connection::InboundAccepting { + accept, + drop_notifier, + } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, + Connection::Operational { + mut substream, + read_buffer, + drop_notifier, + } => { + let result = Pin::new(&mut substream).poll_write(cx, buf); + *self = Connection::Operational { + substream, + read_buffer, + drop_notifier, + }; + return result; + } + Connection::Poisoned => todo!(), + } + } + } + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + loop { + match std::mem::replace(&mut *self, Connection::Poisoned) { + Connection::InboundAccepting { + accept, + drop_notifier, + } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, + Connection::Operational { + mut substream, + read_buffer, + drop_notifier, + } => { + let result = Pin::new(&mut substream).poll_flush(cx); + *self = Connection::Operational { + substream, + read_buffer, + drop_notifier, + }; + return result; + } + Connection::Poisoned => todo!(), + } + } + } + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + loop { + match std::mem::replace(&mut *self, Connection::Poisoned) { + Connection::InboundAccepting { + accept, + drop_notifier, + } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, + Connection::Operational { + mut substream, + read_buffer, + drop_notifier, + } => { + let result = Pin::new(&mut substream).poll_close(cx); + *self = Connection::Operational { + substream, + read_buffer, + drop_notifier, + }; + return result; + } + Connection::Poisoned => todo!(), + } + } + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context, + bufs: &[IoSlice], + ) -> Poll> { + loop { + match std::mem::replace(&mut *self, Connection::Poisoned) { + Connection::InboundAccepting { + accept, + drop_notifier, + } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, + Connection::Operational { + mut substream, + read_buffer, + drop_notifier, + } => { + let result = Pin::new(&mut substream).poll_write_vectored(cx, bufs); + *self = Connection::Operational { + substream, + read_buffer, + drop_notifier, + }; + return result; + } + Connection::Poisoned => todo!(), + } + } + } +} + +impl AsyncRead for Connection { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + loop { + match std::mem::replace(&mut *self, Connection::Poisoned) { + Connection::InboundAccepting { + accept, + drop_notifier, + } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, + Connection::Operational { + mut substream, + mut read_buffer, + drop_notifier, + } => { + if !read_buffer.is_empty() { + let n = std::cmp::min(read_buffer.len(), buf.len()); + let data = read_buffer.split_to(n); + buf[0..n].copy_from_slice(&data[..]); + *self = Connection::Operational { + substream, + read_buffer, + drop_notifier, + }; + return Poll::Ready(Ok(n)); + } + + let result = Pin::new(&mut substream).poll_read(cx, buf); + *self = Connection::Operational { + substream, + read_buffer, + drop_notifier, + }; + return result; + } + Connection::Poisoned => todo!(), + } + } + } +} + +enum RqstPendingConnection { + Reservation { + relay_addr: Multiaddr, + to_listener: Sender, + }, + Circuit { + dst_peer_id: PeerId, + relay_addr: Multiaddr, + send_back: oneshot::Sender>, + }, +} + +impl RqstPendingConnection { + fn relay_addr(&self) -> &Multiaddr { + match self { + RqstPendingConnection::Reservation { relay_addr, .. } => relay_addr, + RqstPendingConnection::Circuit { relay_addr, .. } => relay_addr, + } + } +} diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs new file mode 100644 index 00000000000..7f5ffbb8a03 --- /dev/null +++ b/protocols/relay/src/v2/client/handler.rs @@ -0,0 +1,306 @@ +// 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 crate::v2::client::transport; +use crate::v2::protocol::{inbound_stop, outbound_hop}; +use futures::channel::mpsc::Sender; +use futures::channel::oneshot; +use futures::stream::FuturesUnordered; +use futures::future::FutureExt; +use futures_timer::Delay; +use libp2p_core::either::EitherError; +use libp2p_core::multiaddr::Protocol; +use libp2p_core::{upgrade, ConnectedPoint, Multiaddr, PeerId}; +use libp2p_swarm::protocols_handler::{InboundUpgradeSend, OutboundUpgradeSend}; +use libp2p_swarm::{ + IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, ProtocolsHandlerEvent, + ProtocolsHandlerUpgrErr, SubstreamProtocol, +}; +use std::collections::VecDeque; +use std::task::{Context, Poll}; +use std::time::Instant; + +pub enum In { + Reserve { + to_listener: Sender, + }, + EstablishCircuit { + dst_peer_id: PeerId, + send_back: oneshot::Sender>, + }, +} + +pub enum Event { + Reserved, +} + +pub struct Prototype { + local_peer_id: PeerId, +} + +impl Prototype { + pub(crate) fn new(local_peer_id: PeerId) -> Self { + Self { local_peer_id } + } +} + +impl IntoProtocolsHandler for Prototype { + type Handler = Handler; + + fn into_handler(self, remote_peer_id: &PeerId, _endpoint: &ConnectedPoint) -> Self::Handler { + Handler { + remote_peer_id: *remote_peer_id, + local_peer_id: self.local_peer_id, + queued_events: Default::default(), + reservation: None, + alive_lend_out_substreams: Default::default(), + } + } + + fn inbound_protocol(&self) -> ::InboundProtocol { + inbound_stop::Upgrade {} + } +} + +pub struct Handler { + local_peer_id: PeerId, + remote_peer_id: PeerId, + + /// Queue of events to return when polled. + queued_events: VecDeque< + ProtocolsHandlerEvent< + ::OutboundProtocol, + ::OutboundOpenInfo, + ::OutEvent, + ::Error, + >, + >, + + reservation: Option, + + /// Tracks substreams lend out to the transport. + /// + /// Contains a [`futures::future::Future`] for each lend out substream that + /// resolves once the substream is dropped. + /// + /// Once all substreams are dropped and this handler has no other work, + /// [`KeepAlive::Until`] can be set, allowing the connection to be closed + /// eventually. + alive_lend_out_substreams: FuturesUnordered>, +} + +struct Reservation { + renewal_timeout: Delay, + pending_msgs: VecDeque, + to_listener: Sender, +} + +impl ProtocolsHandler for Handler { + type InEvent = In; + type OutEvent = Event; + type Error = ProtocolsHandlerUpgrErr< + EitherError, + >; + type InboundProtocol = inbound_stop::Upgrade; + type OutboundProtocol = outbound_hop::Upgrade; + type OutboundOpenInfo = OutboundOpenInfo; + type InboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(inbound_stop::Upgrade {}, ()) + } + + fn inject_fully_negotiated_inbound( + &mut self, + inbound_circuit: >::Output, + _: Self::InboundOpenInfo, + ) { + if let Some(reservation) = &mut self.reservation { + let src_peer_id = inbound_circuit.src_peer_id(); + let (tx, rx) = oneshot::channel(); + self.alive_lend_out_substreams.push(rx); + let connection = super::Connection::new_inbound(inbound_circuit, tx); + reservation.pending_msgs.push_back( + transport::ToListenerMsg::IncomingRelayedConnection { + stream: connection, + src_peer_id, + relay_peer_id: self.remote_peer_id, + // TODO: Fix + relay_addr: Multiaddr::empty(), + }, + ) + } else { + todo!("deny") + } + } + + fn inject_fully_negotiated_outbound( + &mut self, + output: >::Output, + info: Self::OutboundOpenInfo, + ) { + match (output, info) { + ( + outbound_hop::Output::Reservation { expire, addrs }, + OutboundOpenInfo::Reserve { to_listener }, + ) => { + self.queued_events + .push_back(ProtocolsHandlerEvent::Custom(Event::Reserved)); + match self.reservation { + Some(_) => todo!(), + None => { + let local_peer_id = self.local_peer_id; + let mut pending_msgs = VecDeque::new(); + pending_msgs.push_back(transport::ToListenerMsg::Reservation { + addrs: addrs + .into_iter() + .map(|a| a.with(Protocol::P2p(local_peer_id.into()))) + .collect(), + }); + self.reservation = Some(Reservation { + // TODO: Should timeout fire early to make sure reservation never expires? + renewal_timeout: Delay::new( + expire + .expect("TODO handle where no expire") + .checked_duration_since(Instant::now()) + .unwrap(), + ), + pending_msgs, + to_listener, + }) + } + } + } + ( + outbound_hop::Output::Circuit { + substream, + read_buffer, + }, + OutboundOpenInfo::Connect { send_back }, + ) => { + let (tx, rx) = oneshot::channel(); + self.alive_lend_out_substreams.push(rx); + let _ = send_back.send(Ok(super::Connection::new_outbound(substream, read_buffer, tx))); + } + _ => unreachable!(), + } + } + + fn inject_event(&mut self, event: Self::InEvent) { + match event { + In::Reserve { to_listener } => { + self.queued_events + .push_back(ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new( + outbound_hop::Upgrade::Reserve, + OutboundOpenInfo::Reserve { to_listener }, + ), + }); + } + In::EstablishCircuit { + send_back, + dst_peer_id, + } => { + self.queued_events + .push_back(ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new( + outbound_hop::Upgrade::Connect { dst_peer_id }, + OutboundOpenInfo::Connect { send_back }, + ), + }); + } + } + } + + fn inject_listen_upgrade_error( + &mut self, + _: Self::InboundOpenInfo, + _error: ProtocolsHandlerUpgrErr<::Error>, + ) { + todo!() + } + + fn inject_dial_upgrade_error( + &mut self, + _open_info: Self::OutboundOpenInfo, + error: ProtocolsHandlerUpgrErr<::Error>, + ) { + panic!("{:?}", error) + } + + // TODO: Why is this not a mut reference? If it were the case, we could do all keep alive handling in here. + fn connection_keep_alive(&self) -> KeepAlive { + // TODO + // TODO: cover lend out substreams. + KeepAlive::Yes + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ProtocolsHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::OutEvent, + Self::Error, + >, + > { + // Return queued events. + if let Some(event) = self.queued_events.pop_front() { + return Poll::Ready(event); + } + + if let Some(reservation) = &mut self.reservation { + if !reservation.pending_msgs.is_empty() { + match reservation.to_listener.poll_ready(cx) { + Poll::Ready(Ok(())) => { + reservation + .to_listener + .start_send( + reservation + .pending_msgs + .pop_front() + .expect("Called !is_empty()."), + ) + .unwrap(); + } + Poll::Ready(Err(e)) => todo!("{:?}", e), + Poll::Pending => {} + } + } + match reservation.renewal_timeout.poll_unpin(cx) { + Poll::Ready(()) => todo!(), + Poll::Pending => {} + } + } + + Poll::Pending + } +} + +pub enum OutboundOpenInfo { + Reserve { + to_listener: Sender, + }, + Connect { + send_back: oneshot::Sender>, + }, +} diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs new file mode 100644 index 00000000000..0c210d011f8 --- /dev/null +++ b/protocols/relay/src/v2/client/transport.rs @@ -0,0 +1,559 @@ +// 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"), +// 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::v2::client::Connection; +use crate::v2::RequestId; +use futures::channel::mpsc; +use futures::channel::oneshot; +use futures::future::{BoxFuture, Future, FutureExt}; +use futures::sink::SinkExt; +use futures::stream::{Stream, StreamExt}; +use libp2p_core::either::{EitherError, EitherFuture, EitherOutput}; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use libp2p_core::transport::{ListenerEvent, TransportError}; +use libp2p_core::{PeerId, Transport}; +use pin_project::pin_project; +use std::pin::Pin; +use std::task::{Context, Poll}; + +/// A [`Transport`] wrapping another [`Transport`] enabling client relay capabilities. +/// +/// Allows the local node to: +/// +/// TODO: Update! +/// 1. Use inner wrapped transport as before. +/// +/// ``` +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport}; +/// # use libp2p_core::transport::memory::MemoryTransport; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +/// # let inner_transport = MemoryTransport::default(); +/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( +/// # RelayConfig::default(), +/// # inner_transport, +/// # ); +/// relay_transport.dial(Multiaddr::empty().with(Protocol::Memory(42))); +/// ``` +/// +/// 2. Establish relayed connections by dialing `/p2p-circuit` addresses. +/// +/// ``` +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; +/// # use libp2p_core::transport::memory::MemoryTransport; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +/// # let inner_transport = MemoryTransport::default(); +/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( +/// # RelayConfig::default(), +/// # inner_transport, +/// # ); +/// let dst_addr_via_relay = Multiaddr::empty() +/// .with(Protocol::Memory(40)) // Relay address. +/// .with(Protocol::P2p(PeerId::random().into())) // Relay peer id. +/// .with(Protocol::P2pCircuit) // Signal to connect via relay and not directly. +/// .with(Protocol::Memory(42)) // Destination address. +/// .with(Protocol::P2p(PeerId::random().into())); // Destination peer id. +/// relay_transport.dial(dst_addr_via_relay).unwrap(); +/// ``` +/// +/// 3. Listen for incoming relayed connections via specific relay. +/// +/// ``` +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; +/// # use libp2p_core::transport::memory::MemoryTransport; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +/// # let inner_transport = MemoryTransport::default(); +/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( +/// # RelayConfig::default(), +/// # inner_transport, +/// # ); +/// let relay_addr = Multiaddr::empty() +/// .with(Protocol::Memory(40)) // Relay address. +/// .with(Protocol::P2p(PeerId::random().into())) // Relay peer id. +/// .with(Protocol::P2pCircuit); // Signal to listen via remote relay node. +/// relay_transport.listen_on(relay_addr).unwrap(); +/// ``` +/// TODO Is (4) still supported? +/// +/// 4. Listen for incoming relayed connections via any relay. +/// +/// Note: Without this listener, incoming relayed connections from relays, that the local node is +/// not explicitly listening via, are dropped. +/// +/// ``` +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; +/// # use libp2p_core::transport::memory::MemoryTransport; +/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +/// # let inner_transport = MemoryTransport::default(); +/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( +/// # RelayConfig::default(), +/// # inner_transport, +/// # ); +/// let addr = Multiaddr::empty() +/// .with(Protocol::P2pCircuit); // Signal to listen via any relay. +/// relay_transport.listen_on(addr).unwrap(); +/// ``` +#[derive(Clone)] +pub struct ClientTransport { + to_behaviour: mpsc::Sender, + + inner_transport: T, +} + +impl ClientTransport { + /// Create a new [`ClientTransport`] by wrapping an existing [`Transport`] in a + /// [`ClientTransport`]. + /// + ///``` + /// # use libp2p_core::transport::dummy::DummyTransport; + /// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; + /// + /// let inner_transport = DummyTransport::<()>::new(); + /// let (relay_transport, relay_behaviour) = new_transport_and_behaviour( + /// RelayConfig::default(), + /// inner_transport, + /// ); + ///``` + pub(crate) fn new(t: T) -> (Self, mpsc::Receiver) { + let (to_behaviour, from_transport) = mpsc::channel(0); + + let transport = ClientTransport { + to_behaviour, + + inner_transport: t, + }; + + (transport, from_transport) + } +} + +impl Transport for ClientTransport { + type Output = EitherOutput<::Output, Connection>; + type Error = EitherError<::Error, RelayError>; + type Listener = RelayListener; + type ListenerUpgrade = RelayedListenerUpgrade; + type Dial = EitherFuture<::Dial, RelayedDial>; + + fn listen_on(self, addr: Multiaddr) -> Result> { + match parse_relayed_multiaddr(addr)? { + // Address does not contain circuit relay protocol. Use inner transport. + Err(addr) => { + let inner_listener = match self.inner_transport.listen_on(addr) { + Ok(listener) => listener, + Err(TransportError::MultiaddrNotSupported(addr)) => { + return Err(TransportError::MultiaddrNotSupported(addr)) + } + Err(TransportError::Other(err)) => { + return Err(TransportError::Other(EitherError::A(err))) + } + }; + Ok(RelayListener::Inner(inner_listener)) + } + // Address does contain circuit relay protocol. Use relayed listener. + Ok(relayed_addr) => { + let (relay_peer_id, relay_addr) = match relayed_addr { + RelayedMultiaddr { + relay_peer_id: None, + relay_addr: _, + .. + } => return Err(RelayError::MissingDstPeerId.into()), + RelayedMultiaddr { + relay_peer_id: _, + relay_addr: None, + .. + } => return Err(RelayError::MissingRelayAddr.into()), + RelayedMultiaddr { + relay_peer_id: Some(peer_id), + relay_addr: Some(addr), + .. + } => (peer_id, addr), + }; + + let (to_listener, from_behaviour) = mpsc::channel(0); + let mut to_behaviour = self.to_behaviour.clone(); + let msg_to_behaviour = Some( + async move { + to_behaviour + .send(TransportToBehaviourMsg::ListenReq { + relay_peer_id, + relay_addr, + to_listener, + }) + .await + } + .boxed(), + ); + + Ok(RelayListener::Relayed { + from_behaviour, + msg_to_behaviour, + }) + } + } + } + + fn dial(self, addr: Multiaddr) -> Result> { + match parse_relayed_multiaddr(addr)? { + // Address does not contain circuit relay protocol. Use inner transport. + Err(addr) => match self.inner_transport.dial(addr) { + Ok(dialer) => Ok(EitherFuture::First(dialer)), + Err(TransportError::MultiaddrNotSupported(addr)) => { + Err(TransportError::MultiaddrNotSupported(addr)) + } + Err(TransportError::Other(err)) => Err(TransportError::Other(EitherError::A(err))), + }, + // Address does contain circuit relay protocol. Dial destination via relay. + Ok(RelayedMultiaddr { + relay_peer_id, + relay_addr, + dst_peer_id, + dst_addr, + }) => { + // TODO: In the future we might want to support dialing a relay by its address only. + let relay_peer_id = relay_peer_id.ok_or(RelayError::MissingRelayPeerId)?; + let relay_addr = relay_addr.ok_or(RelayError::MissingRelayAddr)?; + let dst_peer_id = dst_peer_id.ok_or(RelayError::MissingDstPeerId)?; + + let mut to_behaviour = self.to_behaviour.clone(); + Ok(EitherFuture::Second( + async move { + let (tx, rx) = oneshot::channel(); + to_behaviour + .send(TransportToBehaviourMsg::DialReq { + request_id: RequestId::new(), + relay_addr, + relay_peer_id, + dst_addr, + dst_peer_id, + send_back: tx, + }) + .await?; + let stream = rx.await??; + Ok(stream) + } + .boxed(), + )) + } + } + } + + fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { + self.inner_transport.address_translation(server, observed) + } +} + +#[derive(Default)] +struct RelayedMultiaddr { + relay_peer_id: Option, + relay_addr: Option, + dst_peer_id: Option, + dst_addr: Option, +} + +/// Parse a [`Multiaddr`] containing a [`Protocol::P2pCircuit`]. +/// +/// Returns `Ok(Err(provided_addr))` when passed address contains no [`Protocol::P2pCircuit`]. +/// +/// Returns `Err(_)` when address is malformed. +fn parse_relayed_multiaddr( + addr: Multiaddr, +) -> Result, RelayError> { + if !addr.iter().any(|p| matches!(p, Protocol::P2pCircuit)) { + return Ok(Err(addr)); + } + + let mut relayed_multiaddr = RelayedMultiaddr::default(); + + let mut before_circuit = true; + for protocol in addr.into_iter() { + match protocol { + Protocol::P2pCircuit => { + if before_circuit { + before_circuit = false; + } else { + return Err(RelayError::MultipleCircuitRelayProtocolsUnsupported); + } + } + Protocol::P2p(hash) => { + let peer_id = PeerId::from_multihash(hash).map_err(|_| RelayError::InvalidHash)?; + + if before_circuit { + if relayed_multiaddr.relay_peer_id.is_some() { + return Err(RelayError::MalformedMultiaddr); + } + relayed_multiaddr.relay_peer_id = Some(peer_id) + } else { + if relayed_multiaddr.dst_peer_id.is_some() { + return Err(RelayError::MalformedMultiaddr); + } + relayed_multiaddr.dst_peer_id = Some(peer_id) + } + } + p => { + if before_circuit { + relayed_multiaddr + .relay_addr + .get_or_insert(Multiaddr::empty()) + .push(p); + } else { + relayed_multiaddr + .dst_addr + .get_or_insert(Multiaddr::empty()) + .push(p); + } + } + } + } + + Ok(Ok(relayed_multiaddr)) +} + +#[pin_project(project = RelayListenerProj)] +pub enum RelayListener { + Inner(#[pin] ::Listener), + Relayed { + from_behaviour: mpsc::Receiver, + + msg_to_behaviour: Option>>, + }, +} + +impl Stream for RelayListener { + type Item = Result< + ListenerEvent, EitherError<::Error, RelayError>>, + EitherError<::Error, RelayError>, + >; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + + match this { + RelayListenerProj::Inner(listener) => match listener.poll_next(cx) { + Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(EitherError::A(e)))), + Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + upgrade, + local_addr, + remote_addr, + }))) => { + return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + upgrade: RelayedListenerUpgrade::Inner(upgrade), + local_addr, + remote_addr, + }))) + } + Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))) => { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))) + } + Poll::Ready(Some(Ok(ListenerEvent::AddressExpired(addr)))) => { + return Poll::Ready(Some(Ok(ListenerEvent::AddressExpired(addr)))) + } + Poll::Ready(Some(Ok(ListenerEvent::Error(err)))) => { + return Poll::Ready(Some(Ok(ListenerEvent::Error(EitherError::A(err))))) + } + Poll::Ready(None) => return Poll::Ready(None), + Poll::Pending => {} + }, + RelayListenerProj::Relayed { + from_behaviour, + msg_to_behaviour, + } => { + if let Some(msg) = msg_to_behaviour { + match Future::poll(msg.as_mut(), cx) { + Poll::Ready(Ok(())) => *msg_to_behaviour = None, + Poll::Ready(Err(e)) => { + return Poll::Ready(Some(Err(EitherError::B(e.into())))) + } + Poll::Pending => {} + } + } + + match from_behaviour.poll_next_unpin(cx) { + Poll::Ready(Some(ToListenerMsg::IncomingRelayedConnection { + stream, + src_peer_id, + relay_addr, + relay_peer_id: _, + })) => { + return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + upgrade: RelayedListenerUpgrade::Relayed(Some(stream)), + local_addr: relay_addr.with(Protocol::P2pCircuit), + remote_addr: Protocol::P2p(src_peer_id.into()).into(), + }))); + } + Poll::Ready(Some(ToListenerMsg::Reservation { addrs })) => { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress( + // TODO: What about the other addresses? + addrs.into_iter().next().unwrap() + )))); + } + Poll::Ready(None) => return Poll::Ready(None), + Poll::Pending => {} + } + } + } + + Poll::Pending + } +} + +pub type RelayedDial = BoxFuture<'static, Result>; + +#[pin_project(project = RelayedListenerUpgradeProj)] +pub enum RelayedListenerUpgrade { + Inner(#[pin] ::ListenerUpgrade), + Relayed(Option), +} + +impl Future for RelayedListenerUpgrade { + type Output = Result< + EitherOutput<::Output, Connection>, + EitherError<::Error, RelayError>, + >; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.project() { + RelayedListenerUpgradeProj::Inner(upgrade) => match upgrade.poll(cx) { + Poll::Ready(Ok(out)) => return Poll::Ready(Ok(EitherOutput::First(out))), + Poll::Ready(Err(err)) => return Poll::Ready(Err(EitherError::A(err))), + Poll::Pending => {} + }, + RelayedListenerUpgradeProj::Relayed(substream) => { + return Poll::Ready(Ok(EitherOutput::Second( + substream.take().expect("Future polled after completion."), + ))) + } + } + + Poll::Pending + } +} + +/// Error that occurred during relay connection setup. +#[derive(Debug, Eq, PartialEq)] +pub enum RelayError { + MissingRelayPeerId, + MissingRelayAddr, + MissingDstPeerId, + InvalidHash, + SendingMessageToBehaviour(mpsc::SendError), + ResponseFromBehaviourCanceled, + DialingRelay, + MultipleCircuitRelayProtocolsUnsupported, + MalformedMultiaddr, +} + +impl From for TransportError> { + fn from(error: RelayError) -> Self { + TransportError::Other(EitherError::B(error)) + } +} + +impl From for RelayError { + fn from(error: mpsc::SendError) -> Self { + RelayError::SendingMessageToBehaviour(error) + } +} + +impl From for RelayError { + fn from(_: oneshot::Canceled) -> Self { + RelayError::ResponseFromBehaviourCanceled + } +} + +impl From for RelayError { + fn from(error: OutgoingRelayReqError) -> Self { + match error { + OutgoingRelayReqError::DialingRelay => RelayError::DialingRelay, + } + } +} + +impl std::fmt::Display for RelayError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RelayError::MissingRelayPeerId => { + write!(f, "Missing relay peer id.") + } + RelayError::MissingRelayAddr => { + write!(f, "Missing relay address.") + } + RelayError::MissingDstPeerId => { + write!(f, "Missing destination peer id.") + } + RelayError::InvalidHash => { + write!(f, "Invalid peer id hash.") + } + RelayError::SendingMessageToBehaviour(e) => { + write!(f, "Failed to send message to relay behaviour: {:?}", e) + } + RelayError::ResponseFromBehaviourCanceled => { + write!(f, "Response from behaviour was canceled") + } + RelayError::DialingRelay => { + write!(f, "Dialing relay failed") + } + RelayError::MultipleCircuitRelayProtocolsUnsupported => { + write!(f, "Address contains multiple circuit relay protocols (`p2p-circuit`) which is not supported.") + } + RelayError::MalformedMultiaddr => { + write!(f, "One of the provided multiaddresses is malformed.") + } + } + } +} + +impl std::error::Error for RelayError {} + +/// Message from the [`ClientTransport`] to the [`Relay`](crate::v1::Relay) +/// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). +pub enum TransportToBehaviourMsg { + /// Dial destination node via relay node. + DialReq { + request_id: RequestId, + relay_addr: Multiaddr, + relay_peer_id: PeerId, + dst_addr: Option, + dst_peer_id: PeerId, + send_back: oneshot::Sender>, + }, + /// Listen for incoming relayed connections via relay node. + ListenReq { + relay_peer_id: PeerId, + relay_addr: Multiaddr, + to_listener: mpsc::Sender, + }, +} + +pub enum ToListenerMsg { + Reservation { + addrs: Vec, + }, + // TODO: Rename to circuit. + IncomingRelayedConnection { + stream: Connection, + src_peer_id: PeerId, + relay_peer_id: PeerId, + relay_addr: Multiaddr, + }, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum OutgoingRelayReqError { + DialingRelay, +} diff --git a/protocols/relay/src/v2/protocol.rs b/protocols/relay/src/v2/protocol.rs index ad0b81eaff3..b76ef6379d4 100644 --- a/protocols/relay/src/v2/protocol.rs +++ b/protocols/relay/src/v2/protocol.rs @@ -19,6 +19,8 @@ // DEALINGS IN THE SOFTWARE. pub mod inbound_hop; +pub mod inbound_stop; +pub mod outbound_hop; pub mod outbound_stop; const HOP_PROTOCOL_NAME: &[u8; 31] = b"/libp2p/circuit/relay/0.2.0/hop"; diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 665d8d822c6..0ff153759e5 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -53,11 +53,11 @@ impl upgrade::InboundUpgrade for Upgrade { type Future = BoxFuture<'static, Result>; fn upgrade_inbound(self, substream: NegotiatedSubstream, _: Self::Info) -> Self::Future { - async move { - let mut codec = UviBytes::::default(); - codec.set_max_len(MAX_MESSAGE_SIZE); - let mut substream = Framed::new(substream, codec); + let mut codec = UviBytes::::default(); + codec.set_max_len(MAX_MESSAGE_SIZE); + let mut substream = Framed::new(substream, codec); + async move { let msg: bytes::BytesMut = substream .next() .await diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs new file mode 100644 index 00000000000..2a644e5d17f --- /dev/null +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -0,0 +1,190 @@ +// 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 crate::v2::message_proto::{stop_message, Status, StopMessage}; +use crate::v2::protocol::{MAX_MESSAGE_SIZE, STOP_PROTOCOL_NAME}; +use asynchronous_codec::{Framed, FramedParts}; +use bytes::{Bytes, BytesMut}; +use futures::{future::BoxFuture, prelude::*}; +use libp2p_core::{upgrade, PeerId}; +use libp2p_swarm::NegotiatedSubstream; +use prost::Message; +use std::io::Cursor; +use std::{error, fmt, iter}; +use unsigned_varint::codec::UviBytes; + +pub struct Upgrade {} + +impl upgrade::UpgradeInfo for Upgrade { + type Info = &'static [u8]; + type InfoIter = iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + iter::once(STOP_PROTOCOL_NAME) + } +} + +impl upgrade::InboundUpgrade for Upgrade { + type Output = Circuit; + type Error = UpgradeError; + type Future = BoxFuture<'static, Result>; + + fn upgrade_inbound(self, substream: NegotiatedSubstream, _: Self::Info) -> Self::Future { + let mut codec = UviBytes::::default(); + codec.set_max_len(MAX_MESSAGE_SIZE); + let mut substream = Framed::new(substream, codec); + + async move { + let msg: bytes::BytesMut = substream + .next() + .await + .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + + let StopMessage { + r#type, + peer, + limit: _, + status: _, + } = StopMessage::decode(Cursor::new(msg))?; + + let r#type = + stop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + match r#type { + stop_message::Type::Connect => { + let src_peer_id = + PeerId::from_bytes(&peer.ok_or(UpgradeError::MissingPeer)?.id) + .map_err(|_| UpgradeError::ParsePeerId)?; + Ok(Circuit { + substream, + src_peer_id, + }) + } + stop_message::Type::Status => Err(UpgradeError::UnexpectedTypeStatus), + } + } + .boxed() + } +} + +#[derive(Debug)] +pub enum UpgradeError { + Decode(prost::DecodeError), + Io(std::io::Error), + ParseTypeField, + ParsePeerId, + MissingPeer, + UnexpectedTypeStatus, +} + +impl From for UpgradeError { + fn from(e: std::io::Error) -> Self { + UpgradeError::Io(e) + } +} + +impl From for UpgradeError { + fn from(e: prost::DecodeError) -> Self { + UpgradeError::Decode(e) + } +} + +impl fmt::Display for UpgradeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UpgradeError::Decode(e) => { + write!(f, "Failed to decode response: {}.", e) + } + UpgradeError::Io(e) => { + write!(f, "Io error {}", e) + } + UpgradeError::ParseTypeField => { + write!(f, "Failed to parse response type field.") + } + UpgradeError::ParsePeerId => { + write!(f, "Failed to parse peer id.") + } + UpgradeError::MissingPeer => { + write!(f, "Expected 'peer' field to be set.") + } + UpgradeError::UnexpectedTypeStatus => { + write!(f, "Unexpected message type 'status'") + } + } + } +} + +impl error::Error for UpgradeError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + UpgradeError::Decode(e) => Some(e), + UpgradeError::Io(e) => Some(e), + UpgradeError::ParseTypeField => None, + UpgradeError::ParsePeerId => None, + UpgradeError::MissingPeer => None, + UpgradeError::UnexpectedTypeStatus => None, + } + } +} + +pub struct Circuit { + substream: Framed, + src_peer_id: PeerId, +} + +impl Circuit { + pub(crate) fn src_peer_id(&self) -> PeerId { + self.src_peer_id + } + + pub async fn accept(mut self) -> Result<(NegotiatedSubstream, Bytes), std::io::Error> { + let msg = StopMessage { + r#type: stop_message::Type::Status.into(), + peer: None, + limit: None, + status: Some(Status::Ok.into()), + }; + + self.send(msg).await?; + + let FramedParts { + io, + read_buffer, + write_buffer, + .. + } = self.substream.into_parts(); + assert!( + write_buffer.is_empty(), + "Expect a flushed Framed to have an empty write buffer." + ); + + Ok((io, read_buffer.freeze())) + } + + async fn send(&mut self, msg: StopMessage) -> Result<(), std::io::Error> { + let mut msg_bytes = BytesMut::new(); + msg.encode(&mut msg_bytes) + // TODO: Sure panicing is safe here? + .expect("all the mandatory fields are always filled; QED"); + self.substream.send(msg_bytes.freeze()).await?; + self.substream.flush().await?; + + Ok(()) + } +} diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs new file mode 100644 index 00000000000..dfbabde622d --- /dev/null +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -0,0 +1,282 @@ +// 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 crate::v2::message_proto::{hop_message, HopMessage, Peer, Status}; +use asynchronous_codec::{Framed, FramedParts}; +use crate::v2::protocol::{HOP_PROTOCOL_NAME, MAX_MESSAGE_SIZE}; +use bytes::{Bytes}; +use futures::{future::BoxFuture, prelude::*}; +use libp2p_core::{upgrade, Multiaddr, PeerId}; +use libp2p_swarm::NegotiatedSubstream; +use prost::Message; +use std::convert::{TryFrom, TryInto}; +use std::io::Cursor; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use std::{error, fmt, iter}; +use unsigned_varint::codec::UviBytes; + +pub enum Upgrade { + Reserve, + Connect { dst_peer_id: PeerId }, +} + +impl upgrade::UpgradeInfo for Upgrade { + type Info = &'static [u8]; + type InfoIter = iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + iter::once(HOP_PROTOCOL_NAME) + } +} + +impl upgrade::OutboundUpgrade for Upgrade { + type Output = Output; + type Error = UpgradeError; + type Future = BoxFuture<'static, Result>; + + fn upgrade_outbound(self, substream: NegotiatedSubstream, _: Self::Info) -> Self::Future { + let msg = match self { + Upgrade::Reserve => HopMessage { + r#type: hop_message::Type::Reserve.into(), + peer: None, + reservation: None, + limit: None, + status: None, + }, + Upgrade::Connect { dst_peer_id } => HopMessage { + r#type: hop_message::Type::Connect.into(), + peer: Some(Peer { + id: dst_peer_id.to_bytes(), + addrs: vec![], + }), + reservation: None, + limit: None, + status: None, + }, + }; + + let mut encoded_msg = Vec::new(); + msg.encode(&mut encoded_msg) + // TODO: Double check. Safe to panic here? + .expect("all the mandatory fields are always filled; QED"); + + let mut codec = UviBytes::default(); + codec.set_max_len(MAX_MESSAGE_SIZE); + let mut substream = Framed::new(substream, codec); + + async move { + substream.send(std::io::Cursor::new(encoded_msg)).await?; + let msg: bytes::BytesMut = substream + .next() + .await + .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + + let HopMessage { + r#type, + peer: _, + reservation, + limit: _, + status, + } = HopMessage::decode(Cursor::new(msg))?; + + let r#type = hop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + match r#type { + hop_message::Type::Connect => return Err(UpgradeError::UnexpectedTypeConnect), + hop_message::Type::Reserve => return Err(UpgradeError::UnexpectedTypeReserve), + hop_message::Type::Status => {} + } + + let status = Status::from_i32(status.ok_or(UpgradeError::MissingStatusField)?) + .ok_or(UpgradeError::ParseStatusField)?; + match status { + Status::Ok => {} + s => return Err(UpgradeError::UnexpectedStatus(s)), + } + + let output = match self { + Upgrade::Reserve => { + let reservation = reservation.ok_or(UpgradeError::MissingReservationField)?; + + let addrs = if reservation.addrs.is_empty() { + return Err(UpgradeError::NoAddressesinReservation); + } else { + reservation + .addrs + .into_iter() + .map(TryFrom::try_from) + .collect::, _>>() + .map_err(|_| UpgradeError::InvalidReservationAddrs)? + }; + + let expire = if let Some(expires) = reservation.expire { + Some( + unix_timestamp_to_instant(expires) + .ok_or(UpgradeError::InvalidReservationExpiration)?, + ) + } else { + None + }; + + substream.close().await?; + + Output::Reservation { expire, addrs } + } + Upgrade::Connect { .. } => { + let FramedParts { + io, + read_buffer, + write_buffer, + .. + } = substream.into_parts(); + assert!( + write_buffer.is_empty(), + "Expect a flushed Framed to have empty write buffer." + ); + + Output::Circuit { + substream: io, + read_buffer: read_buffer.freeze(), + } + } + }; + + Ok(output) + } + .boxed() + } +} + +#[derive(Debug)] +pub enum UpgradeError { + Decode(prost::DecodeError), + Io(std::io::Error), + MissingStatusField, + MissingReservationField, + NoAddressesinReservation, + InvalidReservationExpiration, + InvalidReservationAddrs, + ParseTypeField, + UnexpectedTypeConnect, + UnexpectedTypeReserve, + ParseStatusField, + UnexpectedStatus(Status), +} + +impl From for UpgradeError { + fn from(e: std::io::Error) -> Self { + UpgradeError::Io(e) + } +} + +impl From for UpgradeError { + fn from(e: prost::DecodeError) -> Self { + UpgradeError::Decode(e) + } +} + +impl fmt::Display for UpgradeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UpgradeError::Decode(e) => { + write!(f, "Failed to decode response: {}.", e) + } + UpgradeError::Io(e) => { + write!(f, "Io error {}", e) + } + UpgradeError::MissingStatusField => { + write!(f, "Expected 'status' field to be set.") + } + UpgradeError::MissingReservationField => { + write!(f, "Expected 'reservation' field to be set.") + } + UpgradeError::NoAddressesinReservation => { + write!(f, "Expected at least one address in reservation.") + } + UpgradeError::InvalidReservationExpiration => { + write!(f, "Invalid expiration timestamp in reservation.") + } + UpgradeError::InvalidReservationAddrs => { + write!(f, "Invalid addresses in reservation.") + } + UpgradeError::ParseTypeField => { + write!(f, "Failed to parse response type field.") + } + UpgradeError::UnexpectedTypeConnect => { + write!(f, "Unexpected message type 'connect'") + } + UpgradeError::UnexpectedTypeReserve => { + write!(f, "Unexpected message type 'reserve'") + } + UpgradeError::ParseStatusField => { + write!(f, "Failed to parse response type field.") + } + UpgradeError::UnexpectedStatus(status) => { + write!(f, "Unexpected message status '{:?}'", status) + } + } + } +} + +impl error::Error for UpgradeError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + UpgradeError::Decode(e) => Some(e), + UpgradeError::Io(e) => Some(e), + UpgradeError::MissingStatusField => None, + UpgradeError::MissingReservationField => None, + UpgradeError::NoAddressesinReservation => None, + UpgradeError::InvalidReservationExpiration => None, + UpgradeError::InvalidReservationAddrs => None, + UpgradeError::ParseTypeField => None, + UpgradeError::UnexpectedTypeConnect => None, + UpgradeError::UnexpectedTypeReserve => None, + UpgradeError::ParseStatusField => None, + UpgradeError::UnexpectedStatus(_) => None, + } + } +} + +// TODO: This should use a u64. +fn unix_timestamp_to_instant(secs: i64) -> Option { + Instant::now().checked_add(Duration::from_secs( + secs.checked_sub( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + .try_into() + // TODO: Can we do better? Why represent time as an i64 in protobuf in the first place? + .expect("Time to fit i64."), + )? + .try_into() + .ok()?, + )) +} + +pub enum Output { + Reservation { + expire: Option, + addrs: Vec, + }, + Circuit { + substream: NegotiatedSubstream, + read_buffer: Bytes, + }, +} diff --git a/protocols/relay/src/v2/behaviour.rs b/protocols/relay/src/v2/relay.rs similarity index 87% rename from protocols/relay/src/v2/behaviour.rs rename to protocols/relay/src/v2/relay.rs index da3cff4f211..0c520b34ee3 100644 --- a/protocols/relay/src/v2/behaviour.rs +++ b/protocols/relay/src/v2/relay.rs @@ -18,7 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::v2::handler::{self, RelayHandlerEvent, RelayHandlerIn, RelayHandlerProto}; +//! [`NetworkBehaviour`] to act as a circuit relay v2 **relay**. + +mod handler; + use crate::v2::message_proto; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; use libp2p_core::multiaddr::Protocol; @@ -29,6 +32,7 @@ use std::ops::Add; use std::task::{Context, Poll}; use std::time::Duration; +// TODO: Expose this as RelayConfig? pub struct Config { // TODO: Should we use u32? max_reservations: usize, @@ -61,8 +65,10 @@ impl Default for Config { } /// The events produced by the [`Relay`] behaviour. +// +// TODO: Should this be renamed to Event? Would be inline with [`Config`]. #[derive(Debug)] -pub enum RelayEvent { +pub enum Event { /// An inbound reservation request has been accepted. ReservationReqAccepted { src_peer_id: PeerId, @@ -75,18 +81,14 @@ pub enum RelayEvent { error: std::io::Error, }, /// An inbound reservation request has been denied. - ReservationReqDenied { - src_peer_id: PeerId, - }, + ReservationReqDenied { src_peer_id: PeerId }, /// Denying an inbound reservation request has failed. ReservationReqDenyFailed { src_peer_id: PeerId, error: std::io::Error, }, /// An inbound reservation has timed out. - ReservationTimedOut { - src_peer_id: PeerId, - }, + ReservationTimedOut { src_peer_id: PeerId }, /// An inbound circuit request has been denied. CircuitReqDenied { src_peer_id: PeerId, @@ -128,7 +130,7 @@ pub struct Relay { circuits: CircuitsTracker, /// Queue of actions to return when polled. - queued_actions: VecDeque>, + queued_actions: VecDeque>, } impl Relay { @@ -144,11 +146,11 @@ impl Relay { } impl NetworkBehaviour for Relay { - type ProtocolsHandler = RelayHandlerProto; - type OutEvent = RelayEvent; + type ProtocolsHandler = handler::Prototype; + type OutEvent = Event; fn new_handler(&mut self) -> Self::ProtocolsHandler { - RelayHandlerProto { + handler::Prototype { config: handler::Config { reservation_duration: self.config.reservation_duration, max_circuit_duration: self.config.max_circuit_duration, @@ -185,7 +187,7 @@ impl NetworkBehaviour for Relay { { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::CircuitClosed { + Event::CircuitClosed { src_peer_id: circuit.src_peer_id, dst_peer_id: circuit.dst_peer_id, error: Some(std::io::ErrorKind::ConnectionAborted.into()), @@ -198,10 +200,10 @@ impl NetworkBehaviour for Relay { &mut self, event_source: PeerId, connection: ConnectionId, - event: RelayHandlerEvent, + event: handler::Event, ) { match event { - RelayHandlerEvent::ReservationReqReceived { + handler::Event::ReservationReqReceived { inbound_reservation_req, } => { let event = if self @@ -215,12 +217,12 @@ impl NetworkBehaviour for Relay { .entry(event_source) .or_default() .insert(connection); - RelayHandlerIn::AcceptReservationReq { + handler::In::AcceptReservationReq { inbound_reservation_req, addrs: vec![], } } else { - RelayHandlerIn::DenyReservationReq { + handler::In::DenyReservationReq { inbound_reservation_req, status: message_proto::Status::ResourceLimitExceeded, } @@ -233,7 +235,7 @@ impl NetworkBehaviour for Relay { event, }) } - RelayHandlerEvent::ReservationReqAccepted { renewed } => { + handler::Event::ReservationReqAccepted { renewed } => { // Ensure local eventual consistent reservation state matches handler (source of // truth). self.reservations @@ -243,57 +245,57 @@ impl NetworkBehaviour for Relay { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::ReservationReqAccepted { + Event::ReservationReqAccepted { src_peer_id: event_source, renewed, }, )); } - RelayHandlerEvent::ReservationReqAcceptFailed { error } => { + handler::Event::ReservationReqAcceptFailed { error } => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::ReservationReqAcceptFailed { + Event::ReservationReqAcceptFailed { src_peer_id: event_source, error, }, )); } - RelayHandlerEvent::ReservationReqDenied {} => { + handler::Event::ReservationReqDenied {} => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::ReservationReqDenied { + Event::ReservationReqDenied { src_peer_id: event_source, }, )); } - RelayHandlerEvent::ReservationReqDenyFailed { error } => { + handler::Event::ReservationReqDenyFailed { error } => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::ReservationReqDenyFailed { + Event::ReservationReqDenyFailed { src_peer_id: event_source, error, }, )); } - RelayHandlerEvent::ReservationTimedOut {} => { + handler::Event::ReservationTimedOut {} => { self.reservations .get_mut(&event_source) .map(|cs| cs.remove(&connection)); self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::ReservationTimedOut { + Event::ReservationTimedOut { src_peer_id: event_source, }, )); } - RelayHandlerEvent::CircuitReqReceived(inbound_circuit_req) => { + handler::Event::CircuitReqReceived(inbound_circuit_req) => { if self.circuits.len() >= self.config.max_circuits { self.queued_actions .push_back(NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, - event: RelayHandlerIn::DenyCircuitReq { + event: handler::In::DenyCircuitReq { circuit_id: None, inbound_circuit_req, status: message_proto::Status::ResourceLimitExceeded, @@ -320,7 +322,7 @@ impl NetworkBehaviour for Relay { .push_back(NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(*dst_conn), peer_id: event_source, - event: RelayHandlerIn::NegotiateOutboundConnect { + event: handler::In::NegotiateOutboundConnect { circuit_id, inbound_circuit_req, relay_peer_id: self.local_peer_id, @@ -333,7 +335,7 @@ impl NetworkBehaviour for Relay { .push_back(NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, - event: RelayHandlerIn::DenyCircuitReq { + event: handler::In::DenyCircuitReq { circuit_id: None, inbound_circuit_req, status: message_proto::Status::NoReservation, @@ -341,7 +343,7 @@ impl NetworkBehaviour for Relay { }); } } - RelayHandlerEvent::CircuitReqDenied { + handler::Event::CircuitReqDenied { circuit_id, dst_peer_id, } => { @@ -351,13 +353,13 @@ impl NetworkBehaviour for Relay { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::CircuitReqDenied { + Event::CircuitReqDenied { src_peer_id: event_source, dst_peer_id, }, )); } - RelayHandlerEvent::CircuitReqDenyFailed { + handler::Event::CircuitReqDenyFailed { circuit_id, dst_peer_id, error, @@ -368,14 +370,14 @@ impl NetworkBehaviour for Relay { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::CircuitReqDenyFailed { + Event::CircuitReqDenyFailed { src_peer_id: event_source, dst_peer_id, error, }, )); } - RelayHandlerEvent::OutboundConnectNegotiated { + handler::Event::OutboundConnectNegotiated { circuit_id, src_peer_id, src_connection_id, @@ -388,7 +390,7 @@ impl NetworkBehaviour for Relay { .push_back(NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(src_connection_id), peer_id: src_peer_id, - event: RelayHandlerIn::AcceptAndDriveCircuit { + event: handler::In::AcceptAndDriveCircuit { circuit_id, dst_peer_id: event_source, inbound_circuit_req, @@ -398,7 +400,7 @@ impl NetworkBehaviour for Relay { }, }); } - RelayHandlerEvent::OutboundConnectNegotiationFailed { + handler::Event::OutboundConnectNegotiationFailed { circuit_id, src_peer_id, src_connection_id, @@ -409,27 +411,27 @@ impl NetworkBehaviour for Relay { .push_back(NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(src_connection_id), peer_id: src_peer_id, - event: RelayHandlerIn::DenyCircuitReq { + event: handler::In::DenyCircuitReq { circuit_id: Some(circuit_id), inbound_circuit_req, status, }, }); } - RelayHandlerEvent::CircuitReqAccepted { + handler::Event::CircuitReqAccepted { dst_peer_id, circuit_id, } => { self.circuits.accepted(circuit_id); self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::CircuitReqAccepted { + Event::CircuitReqAccepted { src_peer_id: event_source, dst_peer_id, }, )); } - RelayHandlerEvent::CircuitReqAcceptFailed { + handler::Event::CircuitReqAcceptFailed { dst_peer_id, circuit_id, error, @@ -437,14 +439,14 @@ impl NetworkBehaviour for Relay { self.circuits.remove(circuit_id); self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::CircuitReqAcceptFailed { + Event::CircuitReqAcceptFailed { src_peer_id: event_source, dst_peer_id, error, }, )); } - RelayHandlerEvent::CircuitClosed { + handler::Event::CircuitClosed { dst_peer_id, circuit_id, error, @@ -453,7 +455,7 @@ impl NetworkBehaviour for Relay { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - RelayEvent::CircuitClosed { + Event::CircuitClosed { src_peer_id: event_source, dst_peer_id, error, @@ -467,11 +469,11 @@ impl NetworkBehaviour for Relay { &mut self, _cx: &mut Context<'_>, poll_parameters: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { if let Some(mut event) = self.queued_actions.pop_front() { // Set external addresses in [`AcceptReservationReq`]. if let NetworkBehaviourAction::NotifyHandler { - event: RelayHandlerIn::AcceptReservationReq { ref mut addrs, .. }, + event: handler::In::AcceptReservationReq { ref mut addrs, .. }, .. } = &mut event { @@ -484,6 +486,8 @@ impl NetworkBehaviour for Relay { .with(Protocol::P2pCircuit) }) .collect(); + // TODO remove + assert!(!addrs.is_empty()) } return Poll::Ready(event); diff --git a/protocols/relay/src/v2/handler.rs b/protocols/relay/src/v2/relay/handler.rs similarity index 93% rename from protocols/relay/src/v2/handler.rs rename to protocols/relay/src/v2/relay/handler.rs index 583bbb7b27f..11776e6e81e 100644 --- a/protocols/relay/src/v2/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::v2::behaviour::CircuitId; +use crate::v2::relay::CircuitId; use crate::v2::copy_future::CopyFuture; use crate::v2::message_proto::Status; use crate::v2::protocol::{inbound_hop, outbound_stop}; @@ -46,7 +46,7 @@ pub struct Config { pub max_circuit_bytes: u64, } -pub enum RelayHandlerIn { +pub enum In { AcceptReservationReq { inbound_reservation_req: inbound_hop::ReservationReq, addrs: Vec, @@ -77,8 +77,8 @@ pub enum RelayHandlerIn { }, } -/// The events produced by the [`RelayHandler`]. -pub enum RelayHandlerEvent { +/// The events produced by the [`Handler`]. +pub enum Event { /// An inbound reservation request has been received. ReservationReqReceived { inbound_reservation_req: inbound_hop::ReservationReq, @@ -147,15 +147,15 @@ pub enum RelayHandlerEvent { }, } -pub struct RelayHandlerProto { +pub struct Prototype { pub config: Config, } -impl IntoProtocolsHandler for RelayHandlerProto { - type Handler = RelayHandler; +impl IntoProtocolsHandler for Prototype { + type Handler = Handler; fn into_handler(self, _remote_peer_id: &PeerId, _endpoint: &ConnectedPoint) -> Self::Handler { - RelayHandler { + Handler { config: self.config, queued_events: Default::default(), pending_error: Default::default(), @@ -181,8 +181,8 @@ impl IntoProtocolsHandler for RelayHandlerProto { /// [`ProtocolsHandler`] that manages substreams for a relay on a single /// connection with a peer. -pub struct RelayHandler { - /// Static [`RelayHandler`] [`Config`]. +pub struct Handler { + /// Static [`Handler`] [`Config`]. config: Config, /// Queue of events to return when polled. @@ -216,7 +216,7 @@ pub struct RelayHandler { circuit_accept_futures: Futures>, /// Futures deying an inbound circuit request. circuit_deny_futures: Futures<(Option, PeerId, Result<(), std::io::Error>)>, - /// Tracks substreams lend out to other [`RelayHandler`]s. + /// Tracks substreams lend out to other [`Handler`]s. /// /// Contains a [`futures::future::Future`] for each lend out substream that /// resolves once the substream is dropped. @@ -231,9 +231,9 @@ pub struct RelayHandler { type Futures = FuturesUnordered>; -impl ProtocolsHandler for RelayHandler { - type InEvent = RelayHandlerIn; - type OutEvent = RelayHandlerEvent; +impl ProtocolsHandler for Handler { + type InEvent = In; + type OutEvent = Event; type Error = ProtocolsHandlerUpgrErr< EitherError, >; @@ -261,14 +261,14 @@ impl ProtocolsHandler for RelayHandler { match request { inbound_hop::Req::Reserve(inbound_reservation_req) => { self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::ReservationReqReceived { + Event::ReservationReqReceived { inbound_reservation_req, }, )); } inbound_hop::Req::Connect(circuit_req) => { self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::CircuitReqReceived(circuit_req), + Event::CircuitReqReceived(circuit_req), )); } } @@ -291,7 +291,7 @@ impl ProtocolsHandler for RelayHandler { self.alive_lend_out_substreams.push(rx); self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::OutboundConnectNegotiated { + Event::OutboundConnectNegotiated { circuit_id, src_peer_id, src_connection_id, @@ -305,21 +305,21 @@ impl ProtocolsHandler for RelayHandler { fn inject_event(&mut self, event: Self::InEvent) { match event { - RelayHandlerIn::AcceptReservationReq { + In::AcceptReservationReq { inbound_reservation_req, addrs, } => { self.reservation_accept_futures .push(inbound_reservation_req.accept(addrs).boxed()); } - RelayHandlerIn::DenyReservationReq { + In::DenyReservationReq { inbound_reservation_req, status, } => { self.reservation_deny_futures .push(inbound_reservation_req.deny(status).boxed()); } - RelayHandlerIn::NegotiateOutboundConnect { + In::NegotiateOutboundConnect { circuit_id, inbound_circuit_req, relay_peer_id, @@ -343,7 +343,7 @@ impl ProtocolsHandler for RelayHandler { ), }); } - RelayHandlerIn::DenyCircuitReq { + In::DenyCircuitReq { circuit_id, inbound_circuit_req, status, @@ -356,7 +356,7 @@ impl ProtocolsHandler for RelayHandler { .boxed(), ); } - RelayHandlerIn::AcceptAndDriveCircuit { + In::AcceptAndDriveCircuit { circuit_id, dst_peer_id, inbound_circuit_req, @@ -483,7 +483,7 @@ impl ProtocolsHandler for RelayHandler { } = open_info; self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::OutboundConnectNegotiationFailed { + Event::OutboundConnectNegotiationFailed { circuit_id, src_peer_id, src_connection_id, @@ -526,7 +526,7 @@ impl ProtocolsHandler for RelayHandler { match result { Ok(()) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::CircuitClosed { + Event::CircuitClosed { circuit_id, dst_peer_id, error: None, @@ -535,7 +535,7 @@ impl ProtocolsHandler for RelayHandler { } Err(e) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::CircuitClosed { + Event::CircuitClosed { circuit_id, dst_peer_id, error: Some(e), @@ -553,12 +553,12 @@ impl ProtocolsHandler for RelayHandler { .replace(Delay::new(self.config.reservation_duration)) .is_some(); return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::ReservationReqAccepted { renewed }, + Event::ReservationReqAccepted { renewed }, )); } Err(error) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::ReservationReqAcceptFailed { error }, + Event::ReservationReqAcceptFailed { error }, )); } } @@ -568,12 +568,12 @@ impl ProtocolsHandler for RelayHandler { match result { Ok(()) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::ReservationReqDenied {}, + Event::ReservationReqDenied {}, )) } Err(error) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::ReservationReqDenyFailed { error }, + Event::ReservationReqDenyFailed { error }, )); } } @@ -621,7 +621,7 @@ impl ProtocolsHandler for RelayHandler { self.circuits.push(circuit); return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::CircuitReqAccepted { + Event::CircuitReqAccepted { circuit_id, dst_peer_id, }, @@ -629,7 +629,7 @@ impl ProtocolsHandler for RelayHandler { } Err((circuit_id, dst_peer_id, error)) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::CircuitReqAcceptFailed { + Event::CircuitReqAcceptFailed { circuit_id, dst_peer_id, error, @@ -645,7 +645,7 @@ impl ProtocolsHandler for RelayHandler { match result { Ok(()) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::CircuitReqDenied { + Event::CircuitReqDenied { circuit_id, dst_peer_id, }, @@ -653,7 +653,7 @@ impl ProtocolsHandler for RelayHandler { } Err(error) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::CircuitReqDenyFailed { + Event::CircuitReqDenyFailed { circuit_id, dst_peer_id, error, @@ -674,7 +674,7 @@ impl ProtocolsHandler for RelayHandler { { self.active_reservation = None; return Poll::Ready(ProtocolsHandlerEvent::Custom( - RelayHandlerEvent::ReservationTimedOut {}, + Event::ReservationTimedOut {}, )); } @@ -691,7 +691,7 @@ impl ProtocolsHandler for RelayHandler { self.keep_alive = KeepAlive::Until(Instant::now() + Duration::from_secs(10)); } KeepAlive::Until(_) => {} - KeepAlive::No => panic!("RelayHandler never sets KeepAlive::No."), + KeepAlive::No => panic!("Handler never sets KeepAlive::No."), } } else { self.keep_alive = KeepAlive::Yes; diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs new file mode 100644 index 00000000000..c4e42bc8a67 --- /dev/null +++ b/protocols/relay/tests/v2.rs @@ -0,0 +1,254 @@ +// 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::executor::LocalPool; +use futures::future::FutureExt; +use futures::io::{AsyncRead, AsyncWrite}; +use futures::stream::StreamExt; +use futures::task::Spawn; +use libp2p::core::multiaddr::{Multiaddr, Protocol}; +use libp2p::core::muxing::StreamMuxerBox; +use libp2p::core::transport::{Boxed, MemoryTransport, Transport}; +use libp2p::core::PublicKey; +use libp2p::core::{identity, upgrade, PeerId}; +use libp2p::ping::{Ping, PingConfig, PingEvent}; +use libp2p::plaintext::PlainText2Config; +use libp2p::relay::v2::client; +use libp2p::relay::v2::relay; +use libp2p::NetworkBehaviour; +use libp2p_swarm::{ + AddressScore, NetworkBehaviour, Swarm, SwarmEvent, +}; + +#[test] +fn reservation() { + let _ = env_logger::try_init(); + let mut pool = LocalPool::new(); + + let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); + let mut relay = build_relay(); + let relay_peer_id = *relay.local_peer_id(); + + relay.listen_on(relay_addr.clone()).unwrap(); + relay.add_external_address(relay_addr.clone(), AddressScore::Infinite); + spawn_swarm_on_pool(&pool, relay); + + let client_addr = relay_addr + .clone() + .with(Protocol::P2p(relay_peer_id.into())) + .with(Protocol::P2pCircuit); + let mut client = build_client(); + let client_peer_id = *client.local_peer_id(); + + client.listen_on(client_addr.clone()).unwrap(); + + pool.run_until(wait_for_reservation( + &mut client, + client_addr.with(Protocol::P2p(client_peer_id.into())), + relay_peer_id, + )); +} + +#[test] +fn connect() { + let _ = env_logger::try_init(); + let mut pool = LocalPool::new(); + + let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); + let mut relay = build_relay(); + let relay_peer_id = *relay.local_peer_id(); + + relay.listen_on(relay_addr.clone()).unwrap(); + relay.add_external_address(relay_addr.clone(), AddressScore::Infinite); + spawn_swarm_on_pool(&pool, relay); + + let mut dst = build_client(); + let dst_peer_id = *dst.local_peer_id(); + let dst_addr = relay_addr + .clone() + .with(Protocol::P2p(relay_peer_id.into())) + .with(Protocol::P2pCircuit) + .with(Protocol::P2p(dst_peer_id.into())); + + dst.listen_on(dst_addr.clone()).unwrap(); + + pool.run_until(wait_for_reservation( + &mut dst, + dst_addr.clone(), + relay_peer_id, + )); + spawn_swarm_on_pool(&pool, dst); + + let mut src = build_client(); + + src.dial_addr(dst_addr).unwrap(); + + pool.run_until(async { + loop { + match src.next_event().await { + SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} + SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => {} + SwarmEvent::Behaviour(ClientEvent::Ping(PingEvent { peer, .. })) + if peer == dst_peer_id => + { + break + } + SwarmEvent::Behaviour(ClientEvent::Ping(PingEvent { peer, .. })) + if peer == relay_peer_id => {} + SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == dst_peer_id => { + break + } + e => panic!("{:?}", e), + } + } + }) +} + +fn build_relay() -> Swarm { + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); + let local_peer_id = local_public_key.clone().into_peer_id(); + + let transport = build_transport(MemoryTransport::default().boxed(), local_public_key); + + Swarm::new( + transport, + Relay { + ping: Ping::new(PingConfig::new()), + relay: relay::Relay::new(local_peer_id, Default::default()), + }, + local_peer_id, + ) +} + +fn build_client() -> Swarm { + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); + let local_peer_id = local_public_key.clone().into_peer_id(); + + let (transport, behaviour) = + client::Client::new_transport_and_behaviour(local_peer_id, MemoryTransport::default()); + let transport = build_transport(transport.boxed(), local_public_key); + + Swarm::new( + transport, + Client { + ping: Ping::new(PingConfig::new()), + relay: behaviour, + }, + local_peer_id, + ) +} + +fn build_transport( + transport: Boxed, + local_public_key: PublicKey, +) -> Boxed<(PeerId, StreamMuxerBox)> +where + StreamSink: AsyncRead + AsyncWrite + Send + Unpin + 'static, +{ + let transport = transport + .upgrade(upgrade::Version::V1) + .authenticate(PlainText2Config { local_public_key }) + .multiplex(libp2p_yamux::YamuxConfig::default()) + .boxed(); + + transport +} + +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "RelayEvent", event_process = false)] +struct Relay { + relay: relay::Relay, + ping: Ping, +} + +#[derive(Debug)] +enum RelayEvent { + Relay(relay::Event), + Ping(PingEvent), +} + +impl From for RelayEvent { + fn from(event: relay::Event) -> Self { + RelayEvent::Relay(event) + } +} + +impl From for RelayEvent { + fn from(event: PingEvent) -> Self { + RelayEvent::Ping(event) + } +} + +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "ClientEvent", event_process = false)] +struct Client { + relay: client::Client, + ping: Ping, +} + +#[derive(Debug)] +enum ClientEvent { + Relay(client::Event), + Ping(PingEvent), +} + +impl From for ClientEvent { + fn from(event: client::Event) -> Self { + ClientEvent::Relay(event) + } +} + +impl From for ClientEvent { + fn from(event: PingEvent) -> Self { + ClientEvent::Ping(event) + } +} + +fn spawn_swarm_on_pool(pool: &LocalPool, swarm: Swarm) { + pool.spawner() + .spawn_obj(swarm.collect::>().map(|_| ()).boxed().into()) + .unwrap(); +} + +async fn wait_for_reservation( + client: &mut Swarm, + client_addr: Multiaddr, + relay_peer_id: PeerId, +) { + loop { + match client.next_event().await { + SwarmEvent::Behaviour(ClientEvent::Relay(client::Event::Reserved { + relay_peer_id: peer_id, + })) if relay_peer_id == peer_id => break, + SwarmEvent::Behaviour(ClientEvent::Ping(_)) => {} + SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} + SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => {} + e => panic!("{:?}", e), + } + } + + // Wait for `NewListenAddr` event. + match client.next_event().await { + SwarmEvent::NewListenAddr(addr) if addr == client_addr => {} + e => panic!("{:?}", e), + } +} From 59c8741ec46494c4a9178a0b30bf13fa095df27f Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 16 May 2021 15:29:31 +0200 Subject: [PATCH 006/108] protocols/relay: Handle dial failure --- protocols/relay/src/v2/client.rs | 4 ++-- protocols/relay/tests/v2.rs | 35 +++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 6ea10a66c97..ec5ce91251b 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -138,8 +138,8 @@ impl NetworkBehaviour for Client { } } - fn inject_dial_failure(&mut self, _peer_id: &PeerId) { - todo!(); + fn inject_dial_failure(&mut self, peer_id: &PeerId) { + self.rqsts_pending_connection.remove(peer_id); } fn inject_disconnected(&mut self, _peer: &PeerId) {} diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index c4e42bc8a67..f8af205dfe3 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -33,9 +33,7 @@ use libp2p::plaintext::PlainText2Config; use libp2p::relay::v2::client; use libp2p::relay::v2::relay; use libp2p::NetworkBehaviour; -use libp2p_swarm::{ - AddressScore, NetworkBehaviour, Swarm, SwarmEvent, -}; +use libp2p_swarm::{AddressScore, NetworkBehaviour, Swarm, SwarmEvent}; #[test] fn reservation() { @@ -121,6 +119,37 @@ fn connect() { }) } +#[test] +fn handle_dial_failure() { + let _ = env_logger::try_init(); + let mut pool = LocalPool::new(); + + let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); + let relay_peer_id = PeerId::random(); + + let mut client = build_client(); + let client_peer_id = *client.local_peer_id(); + let client_addr = relay_addr + .clone() + .with(Protocol::P2p(relay_peer_id.into())) + .with(Protocol::P2pCircuit) + .with(Protocol::P2p(client_peer_id.into())); + + client.listen_on(client_addr.clone()).unwrap(); + + pool.run_until(async { + match client.next_event().await { + SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} + e => panic!("{:?}", e), + } + + match client.next_event().await { + SwarmEvent::UnreachableAddr { peer_id, .. } if peer_id == relay_peer_id => {} + e => panic!("{:?}", e), + } + }) +} + fn build_relay() -> Swarm { let local_key = identity::Keypair::generate_ed25519(); let local_public_key = local_key.public(); From 54cc9db43935cf9be07a712fbd08442279b177a2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 16 May 2021 15:48:55 +0200 Subject: [PATCH 007/108] protocols/relay: Reuse connection --- protocols/relay/src/v2/client.rs | 21 +++++++++--- protocols/relay/tests/v2.rs | 55 ++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index ec5ce91251b..dec8191b8d7 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -196,8 +196,13 @@ impl NetworkBehaviour for Client { .get(&relay_peer_id) .and_then(|cs| cs.get(0)) { - Some(_connection) => { - todo!() + Some(connection_id) => { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + peer_id: relay_peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::Reserve { to_listener }, + }); } None => { self.rqsts_pending_connection @@ -226,8 +231,16 @@ impl NetworkBehaviour for Client { .get(&relay_peer_id) .and_then(|cs| cs.get(0)) { - Some(_connection) => { - todo!() + Some(connection_id) => { + self.queued_actions + .push_back(NetworkBehaviourAction::NotifyHandler { + peer_id: relay_peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::EstablishCircuit { + send_back, + dst_peer_id, + }, + }); } None => { self.rqsts_pending_connection diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index f8af205dfe3..9a47917223a 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -136,18 +136,38 @@ fn handle_dial_failure() { .with(Protocol::P2p(client_peer_id.into())); client.listen_on(client_addr.clone()).unwrap(); + assert!(!pool.run_until(wait_for_dial(&mut client, relay_peer_id))); +} - pool.run_until(async { - match client.next_event().await { - SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} - e => panic!("{:?}", e), - } +#[test] +fn reuse_connection() { + let _ = env_logger::try_init(); + let mut pool = LocalPool::new(); - match client.next_event().await { - SwarmEvent::UnreachableAddr { peer_id, .. } if peer_id == relay_peer_id => {} - e => panic!("{:?}", e), - } - }) + let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); + let mut relay = build_relay(); + let relay_peer_id = *relay.local_peer_id(); + + relay.listen_on(relay_addr.clone()).unwrap(); + relay.add_external_address(relay_addr.clone(), AddressScore::Infinite); + spawn_swarm_on_pool(&pool, relay); + + let client_addr = relay_addr + .clone() + .with(Protocol::P2p(relay_peer_id.into())) + .with(Protocol::P2pCircuit); + let mut client = build_client(); + let client_peer_id = *client.local_peer_id(); + + client.dial_addr(relay_addr).unwrap(); + assert!(pool.run_until(wait_for_dial(&mut client, relay_peer_id))); + + client.listen_on(client_addr.clone()).unwrap(); + pool.run_until(wait_for_reservation( + &mut client, + client_addr.with(Protocol::P2p(client_peer_id.into())), + relay_peer_id, + )); } fn build_relay() -> Swarm { @@ -281,3 +301,18 @@ async fn wait_for_reservation( e => panic!("{:?}", e), } } + +async fn wait_for_dial(client: &mut Swarm, relay_peer_id: PeerId) -> bool { + loop { + match client.next_event().await { + SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} + SwarmEvent::UnreachableAddr { peer_id, .. } if peer_id == relay_peer_id => { + return false + } + SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => { + return true + } + e => panic!("{:?}", e), + } + } +} From 8e7b6a0b3861f1af55db20fc9d76949b66526ad2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 17 May 2021 17:18:49 +0200 Subject: [PATCH 008/108] protocols/relay: Rename Connection to RelayedConnection --- protocols/relay/src/v2/client.rs | 75 +++++++++++----------- protocols/relay/src/v2/client/handler.rs | 16 +++-- protocols/relay/src/v2/client/transport.rs | 14 ++-- 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index dec8191b8d7..07617514802 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -273,9 +273,8 @@ impl NetworkBehaviour for Client { } } -/// A [`NegotiatedSubstream`] acting as a relayed [`Connection`]. -// TODO: Rename to Circuit -pub enum Connection { +/// A [`NegotiatedSubstream`] acting as a [`RelayedConnection`]. +pub enum RelayedConnection { InboundAccepting { accept: BoxFuture<'static, Result<(NegotiatedSubstream, Bytes), std::io::Error>>, drop_notifier: oneshot::Sender<()>, @@ -288,14 +287,14 @@ pub enum Connection { Poisoned, } -impl Unpin for Connection {} +impl Unpin for RelayedConnection {} -impl Connection { +impl RelayedConnection { pub(crate) fn new_inbound( circuit: inbound_stop::Circuit, drop_notifier: oneshot::Sender<()>, ) -> Self { - Connection::InboundAccepting { + RelayedConnection::InboundAccepting { accept: circuit.accept().boxed(), drop_notifier, } @@ -306,7 +305,7 @@ impl Connection { read_buffer: Bytes, drop_notifier: oneshot::Sender<()>, ) -> Self { - Connection::Operational { + RelayedConnection::Operational { substream, read_buffer, drop_notifier, @@ -321,7 +320,7 @@ impl Connection { ) -> Poll> { match accept.poll_unpin(cx) { Poll::Ready(Ok((substream, read_buffer))) => { - **self = Connection::Operational { + **self = RelayedConnection::Operational { substream, read_buffer, drop_notifier, @@ -332,7 +331,7 @@ impl Connection { return Poll::Ready(Err(e)); } Poll::Pending => { - **self = Connection::InboundAccepting { + **self = RelayedConnection::InboundAccepting { accept, drop_notifier, }; @@ -342,80 +341,80 @@ impl Connection { } } -impl AsyncWrite for Connection { +impl AsyncWrite for RelayedConnection { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { loop { - match std::mem::replace(&mut *self, Connection::Poisoned) { - Connection::InboundAccepting { + match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { + RelayedConnection::InboundAccepting { accept, drop_notifier, } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - Connection::Operational { + RelayedConnection::Operational { mut substream, read_buffer, drop_notifier, } => { let result = Pin::new(&mut substream).poll_write(cx, buf); - *self = Connection::Operational { + *self = RelayedConnection::Operational { substream, read_buffer, drop_notifier, }; return result; } - Connection::Poisoned => todo!(), + RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { loop { - match std::mem::replace(&mut *self, Connection::Poisoned) { - Connection::InboundAccepting { + match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { + RelayedConnection::InboundAccepting { accept, drop_notifier, } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - Connection::Operational { + RelayedConnection::Operational { mut substream, read_buffer, drop_notifier, } => { let result = Pin::new(&mut substream).poll_flush(cx); - *self = Connection::Operational { + *self = RelayedConnection::Operational { substream, read_buffer, drop_notifier, }; return result; } - Connection::Poisoned => todo!(), + RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { loop { - match std::mem::replace(&mut *self, Connection::Poisoned) { - Connection::InboundAccepting { + match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { + RelayedConnection::InboundAccepting { accept, drop_notifier, } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - Connection::Operational { + RelayedConnection::Operational { mut substream, read_buffer, drop_notifier, } => { let result = Pin::new(&mut substream).poll_close(cx); - *self = Connection::Operational { + *self = RelayedConnection::Operational { substream, read_buffer, drop_notifier, }; return result; } - Connection::Poisoned => todo!(), + RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } @@ -426,43 +425,43 @@ impl AsyncWrite for Connection { bufs: &[IoSlice], ) -> Poll> { loop { - match std::mem::replace(&mut *self, Connection::Poisoned) { - Connection::InboundAccepting { + match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { + RelayedConnection::InboundAccepting { accept, drop_notifier, } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - Connection::Operational { + RelayedConnection::Operational { mut substream, read_buffer, drop_notifier, } => { let result = Pin::new(&mut substream).poll_write_vectored(cx, bufs); - *self = Connection::Operational { + *self = RelayedConnection::Operational { substream, read_buffer, drop_notifier, }; return result; } - Connection::Poisoned => todo!(), + RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } } -impl AsyncRead for Connection { +impl AsyncRead for RelayedConnection { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { loop { - match std::mem::replace(&mut *self, Connection::Poisoned) { - Connection::InboundAccepting { + match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { + RelayedConnection::InboundAccepting { accept, drop_notifier, } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - Connection::Operational { + RelayedConnection::Operational { mut substream, mut read_buffer, drop_notifier, @@ -471,7 +470,7 @@ impl AsyncRead for Connection { let n = std::cmp::min(read_buffer.len(), buf.len()); let data = read_buffer.split_to(n); buf[0..n].copy_from_slice(&data[..]); - *self = Connection::Operational { + *self = RelayedConnection::Operational { substream, read_buffer, drop_notifier, @@ -480,14 +479,14 @@ impl AsyncRead for Connection { } let result = Pin::new(&mut substream).poll_read(cx, buf); - *self = Connection::Operational { + *self = RelayedConnection::Operational { substream, read_buffer, drop_notifier, }; return result; } - Connection::Poisoned => todo!(), + RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } @@ -501,7 +500,7 @@ enum RqstPendingConnection { Circuit { dst_peer_id: PeerId, relay_addr: Multiaddr, - send_back: oneshot::Sender>, + send_back: oneshot::Sender>, }, } diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 7f5ffbb8a03..5b3faa00154 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -22,8 +22,8 @@ use crate::v2::client::transport; use crate::v2::protocol::{inbound_stop, outbound_hop}; use futures::channel::mpsc::Sender; use futures::channel::oneshot; -use futures::stream::FuturesUnordered; use futures::future::FutureExt; +use futures::stream::FuturesUnordered; use futures_timer::Delay; use libp2p_core::either::EitherError; use libp2p_core::multiaddr::Protocol; @@ -43,7 +43,8 @@ pub enum In { }, EstablishCircuit { dst_peer_id: PeerId, - send_back: oneshot::Sender>, + send_back: + oneshot::Sender>, }, } @@ -136,7 +137,7 @@ impl ProtocolsHandler for Handler { let src_peer_id = inbound_circuit.src_peer_id(); let (tx, rx) = oneshot::channel(); self.alive_lend_out_substreams.push(rx); - let connection = super::Connection::new_inbound(inbound_circuit, tx); + let connection = super::RelayedConnection::new_inbound(inbound_circuit, tx); reservation.pending_msgs.push_back( transport::ToListenerMsg::IncomingRelayedConnection { stream: connection, @@ -197,7 +198,11 @@ impl ProtocolsHandler for Handler { ) => { let (tx, rx) = oneshot::channel(); self.alive_lend_out_substreams.push(rx); - let _ = send_back.send(Ok(super::Connection::new_outbound(substream, read_buffer, tx))); + let _ = send_back.send(Ok(super::RelayedConnection::new_outbound( + substream, + read_buffer, + tx, + ))); } _ => unreachable!(), } @@ -301,6 +306,7 @@ pub enum OutboundOpenInfo { to_listener: Sender, }, Connect { - send_back: oneshot::Sender>, + send_back: + oneshot::Sender>, }, } diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 0c210d011f8..2cfef465975 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -19,7 +19,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::v2::client::Connection; +use crate::v2::client::RelayedConnection; use crate::v2::RequestId; use futures::channel::mpsc; use futures::channel::oneshot; @@ -145,7 +145,7 @@ impl ClientTransport { } impl Transport for ClientTransport { - type Output = EitherOutput<::Output, Connection>; + type Output = EitherOutput<::Output, RelayedConnection>; type Error = EitherError<::Error, RelayError>; type Listener = RelayListener; type ListenerUpgrade = RelayedListenerUpgrade; @@ -413,17 +413,17 @@ impl Stream for RelayListener { } } -pub type RelayedDial = BoxFuture<'static, Result>; +pub type RelayedDial = BoxFuture<'static, Result>; #[pin_project(project = RelayedListenerUpgradeProj)] pub enum RelayedListenerUpgrade { Inner(#[pin] ::ListenerUpgrade), - Relayed(Option), + Relayed(Option), } impl Future for RelayedListenerUpgrade { type Output = Result< - EitherOutput<::Output, Connection>, + EitherOutput<::Output, RelayedConnection>, EitherError<::Error, RelayError>, >; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -530,7 +530,7 @@ pub enum TransportToBehaviourMsg { relay_peer_id: PeerId, dst_addr: Option, dst_peer_id: PeerId, - send_back: oneshot::Sender>, + send_back: oneshot::Sender>, }, /// Listen for incoming relayed connections via relay node. ListenReq { @@ -546,7 +546,7 @@ pub enum ToListenerMsg { }, // TODO: Rename to circuit. IncomingRelayedConnection { - stream: Connection, + stream: RelayedConnection, src_peer_id: PeerId, relay_peer_id: PeerId, relay_addr: Multiaddr, From a1589cc6e78f5392f349368032b92b7371e2fc41 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 17 May 2021 17:31:51 +0200 Subject: [PATCH 009/108] protocols/relay: Update transport doc examples --- protocols/relay/src/v2/client/transport.rs | 67 ++++++++-------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 2cfef465975..54119a47f02 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -38,30 +38,29 @@ use std::task::{Context, Poll}; /// /// Allows the local node to: /// -/// TODO: Update! /// 1. Use inner wrapped transport as before. /// /// ``` -/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport}; +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; /// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +/// # use libp2p_relay::v2::client; /// # let inner_transport = MemoryTransport::default(); -/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( -/// # RelayConfig::default(), +/// # let (transport, behaviour) = client::Client::new_transport_and_behaviour( +/// # PeerId::random(), /// # inner_transport, /// # ); -/// relay_transport.dial(Multiaddr::empty().with(Protocol::Memory(42))); +/// transport.dial(Multiaddr::empty().with(Protocol::Memory(42))); /// ``` /// /// 2. Establish relayed connections by dialing `/p2p-circuit` addresses. /// /// ``` -/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; /// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +/// # use libp2p_relay::v2::client; /// # let inner_transport = MemoryTransport::default(); -/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( -/// # RelayConfig::default(), +/// # let (transport, behaviour) = client::Client::new_transport_and_behaviour( +/// # PeerId::random(), /// # inner_transport, /// # ); /// let dst_addr_via_relay = Multiaddr::empty() @@ -70,45 +69,25 @@ use std::task::{Context, Poll}; /// .with(Protocol::P2pCircuit) // Signal to connect via relay and not directly. /// .with(Protocol::Memory(42)) // Destination address. /// .with(Protocol::P2p(PeerId::random().into())); // Destination peer id. -/// relay_transport.dial(dst_addr_via_relay).unwrap(); +/// transport.dial(dst_addr_via_relay).unwrap(); /// ``` /// /// 3. Listen for incoming relayed connections via specific relay. /// /// ``` -/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; /// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; +/// # use libp2p_relay::v2::client; /// # let inner_transport = MemoryTransport::default(); -/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( -/// # RelayConfig::default(), +/// # let (transport, behaviour) = client::Client::new_transport_and_behaviour( +/// # PeerId::random(), /// # inner_transport, /// # ); /// let relay_addr = Multiaddr::empty() /// .with(Protocol::Memory(40)) // Relay address. /// .with(Protocol::P2p(PeerId::random().into())) // Relay peer id. /// .with(Protocol::P2pCircuit); // Signal to listen via remote relay node. -/// relay_transport.listen_on(relay_addr).unwrap(); -/// ``` -/// TODO Is (4) still supported? -/// -/// 4. Listen for incoming relayed connections via any relay. -/// -/// Note: Without this listener, incoming relayed connections from relays, that the local node is -/// not explicitly listening via, are dropped. -/// -/// ``` -/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, PeerId, Transport}; -/// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; -/// # let inner_transport = MemoryTransport::default(); -/// # let (relay_transport, relay_behaviour) = new_transport_and_behaviour( -/// # RelayConfig::default(), -/// # inner_transport, -/// # ); -/// let addr = Multiaddr::empty() -/// .with(Protocol::P2pCircuit); // Signal to listen via any relay. -/// relay_transport.listen_on(addr).unwrap(); +/// transport.listen_on(relay_addr).unwrap(); /// ``` #[derive(Clone)] pub struct ClientTransport { @@ -122,13 +101,13 @@ impl ClientTransport { /// [`ClientTransport`]. /// ///``` - /// # use libp2p_core::transport::dummy::DummyTransport; - /// # use libp2p_relay::v1::{RelayConfig, new_transport_and_behaviour}; - /// - /// let inner_transport = DummyTransport::<()>::new(); - /// let (relay_transport, relay_behaviour) = new_transport_and_behaviour( - /// RelayConfig::default(), - /// inner_transport, + /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; + /// # use libp2p_core::transport::memory::MemoryTransport; + /// # use libp2p_relay::v2::client; + /// let inner_transport = MemoryTransport::default(); + /// let (transport, behaviour) = client::Client::new_transport_and_behaviour( + /// PeerId::random(), + /// inner_transport, /// ); ///``` pub(crate) fn new(t: T) -> (Self, mpsc::Receiver) { @@ -520,7 +499,7 @@ impl std::fmt::Display for RelayError { impl std::error::Error for RelayError {} -/// Message from the [`ClientTransport`] to the [`Relay`](crate::v1::Relay) +/// Message from the [`ClientTransport`] to the [`Relay`](crate::v2::Relay) /// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). pub enum TransportToBehaviourMsg { /// Dial destination node via relay node. From 4af855e2d5ccb27f0eae601978ba1cdb431fc3d9 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 17 May 2021 17:40:57 +0200 Subject: [PATCH 010/108] protocols/relay: Pass relay addr to transport --- protocols/relay/src/v2/client/handler.rs | 7 ++++--- protocols/relay/src/v2/client/transport.rs | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 5b3faa00154..346251a8884 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -65,9 +65,10 @@ impl Prototype { impl IntoProtocolsHandler for Prototype { type Handler = Handler; - fn into_handler(self, remote_peer_id: &PeerId, _endpoint: &ConnectedPoint) -> Self::Handler { + fn into_handler(self, remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { Handler { remote_peer_id: *remote_peer_id, + remote_addr: endpoint.get_remote_address().clone(), local_peer_id: self.local_peer_id, queued_events: Default::default(), reservation: None, @@ -83,6 +84,7 @@ impl IntoProtocolsHandler for Prototype { pub struct Handler { local_peer_id: PeerId, remote_peer_id: PeerId, + remote_addr: Multiaddr, /// Queue of events to return when polled. queued_events: VecDeque< @@ -143,8 +145,7 @@ impl ProtocolsHandler for Handler { stream: connection, src_peer_id, relay_peer_id: self.remote_peer_id, - // TODO: Fix - relay_addr: Multiaddr::empty(), + relay_addr: self.remote_addr.clone(), }, ) } else { diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 54119a47f02..da62f00500f 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -523,7 +523,6 @@ pub enum ToListenerMsg { Reservation { addrs: Vec, }, - // TODO: Rename to circuit. IncomingRelayedConnection { stream: RelayedConnection, src_peer_id: PeerId, From 8934664df09e30ba23165cbc379fb0a4545d1856 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 18 May 2021 14:22:21 +0200 Subject: [PATCH 011/108] protocols/relay: Implement inbound stop denial --- protocols/relay/src/v2/client.rs | 31 +++++- protocols/relay/src/v2/client/handler.rs | 102 ++++++++++++------ .../relay/src/v2/protocol/inbound_stop.rs | 13 ++- protocols/relay/src/v2/relay.rs | 2 - 4 files changed, 108 insertions(+), 40 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 07617514802..032cd8f3957 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -24,7 +24,7 @@ mod handler; mod transport; use crate::v2::protocol::inbound_stop; -use bytes::{Bytes}; +use bytes::Bytes; use futures::channel::mpsc::{Receiver, Sender}; use futures::channel::oneshot; use futures::future::{BoxFuture, FutureExt}; @@ -42,9 +42,23 @@ use std::io::{Error, IoSlice}; use std::pin::Pin; use std::task::{Context, Poll}; +/// The events produced by the [`Client`] behaviour. #[derive(Debug)] pub enum Event { - Reserved { relay_peer_id: PeerId }, + /// An outbound reservation has been accepted. + // TODO: Should this be renamed to ReservationAccepted? + Reserved { + relay_peer_id: PeerId, + }, + /// An inbound circuit request has been denied. + CircuitReqDenied { + src_peer_id: PeerId, + }, + /// Denying an inbound circuit request failed. + CircuitReqDenyFailed { + src_peer_id: PeerId, + error: std::io::Error, + }, } pub struct Client { @@ -172,6 +186,17 @@ impl NetworkBehaviour for Client { relay_peer_id: event_source, })) } + handler::Event::CircuitReqDenied { src_peer_id } => { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::CircuitReqDenied { src_peer_id }, + )) + } + handler::Event::CircuitReqDenyFailed { src_peer_id, error } => self + .queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::CircuitReqDenyFailed { src_peer_id, error }, + )), } } @@ -325,7 +350,7 @@ impl RelayedConnection { read_buffer, drop_notifier, }; - return Poll::Ready(Ok(())) + return Poll::Ready(Ok(())); } Poll::Ready(Err(e)) => { return Poll::Ready(Err(e)); diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 346251a8884..f0d7aeda47b 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -19,11 +19,12 @@ // DEALINGS IN THE SOFTWARE. use crate::v2::client::transport; +use crate::v2::message_proto::Status; use crate::v2::protocol::{inbound_stop, outbound_hop}; use futures::channel::mpsc::Sender; use futures::channel::oneshot; -use futures::future::FutureExt; -use futures::stream::FuturesUnordered; +use futures::future::{BoxFuture, FutureExt}; +use futures::stream::{FuturesUnordered, StreamExt}; use futures_timer::Delay; use libp2p_core::either::EitherError; use libp2p_core::multiaddr::Protocol; @@ -49,7 +50,17 @@ pub enum In { } pub enum Event { - Reserved, + Reserved { + /// Indicates whether the request replaces an existing reservation. + renewed: bool, + }, + /// An inbound circuit request has been denied. + CircuitReqDenied { src_peer_id: PeerId }, + /// Denying an inbound circuit request failed. + CircuitReqDenyFailed { + src_peer_id: PeerId, + error: std::io::Error, + }, } pub struct Prototype { @@ -73,6 +84,7 @@ impl IntoProtocolsHandler for Prototype { queued_events: Default::default(), reservation: None, alive_lend_out_substreams: Default::default(), + circuit_deny_futs: Default::default(), } } @@ -107,12 +119,8 @@ pub struct Handler { /// [`KeepAlive::Until`] can be set, allowing the connection to be closed /// eventually. alive_lend_out_substreams: FuturesUnordered>, -} -struct Reservation { - renewal_timeout: Delay, - pending_msgs: VecDeque, - to_listener: Sender, + circuit_deny_futs: FuturesUnordered)>>, } impl ProtocolsHandler for Handler { @@ -149,7 +157,13 @@ impl ProtocolsHandler for Handler { }, ) } else { - todo!("deny") + let src_peer_id = inbound_circuit.src_peer_id(); + self.circuit_deny_futs.push( + inbound_circuit + .deny(Status::NoReservation) + .map(move |result| (src_peer_id, result)) + .boxed(), + ) } } @@ -164,31 +178,28 @@ impl ProtocolsHandler for Handler { OutboundOpenInfo::Reserve { to_listener }, ) => { self.queued_events - .push_back(ProtocolsHandlerEvent::Custom(Event::Reserved)); - match self.reservation { - Some(_) => todo!(), - None => { - let local_peer_id = self.local_peer_id; - let mut pending_msgs = VecDeque::new(); - pending_msgs.push_back(transport::ToListenerMsg::Reservation { - addrs: addrs - .into_iter() - .map(|a| a.with(Protocol::P2p(local_peer_id.into()))) - .collect(), - }); - self.reservation = Some(Reservation { - // TODO: Should timeout fire early to make sure reservation never expires? - renewal_timeout: Delay::new( - expire - .expect("TODO handle where no expire") - .checked_duration_since(Instant::now()) - .unwrap(), - ), - pending_msgs, - to_listener, - }) - } - } + .push_back(ProtocolsHandlerEvent::Custom(Event::Reserved { + renewed: self.reservation.is_some(), + })); + let local_peer_id = self.local_peer_id; + let mut pending_msgs = VecDeque::new(); + pending_msgs.push_back(transport::ToListenerMsg::Reservation { + addrs: addrs + .into_iter() + .map(|a| a.with(Protocol::P2p(local_peer_id.into()))) + .collect(), + }); + self.reservation = Some(Reservation { + // TODO: Should timeout fire early to make sure reservation never expires? + renewal_timeout: Delay::new( + expire + .expect("TODO handle where no expire") + .checked_duration_since(Instant::now()) + .unwrap(), + ), + pending_msgs, + to_listener, + }) } ( outbound_hop::Output::Circuit { @@ -298,10 +309,33 @@ impl ProtocolsHandler for Handler { } } + while let Poll::Ready(Some((src_peer_id, result))) = + self.circuit_deny_futs.poll_next_unpin(cx) + { + match result { + Ok(()) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom(Event::CircuitReqDenied { + src_peer_id, + })) + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::CircuitReqDenyFailed { src_peer_id, error }, + )) + } + } + } + Poll::Pending } } +struct Reservation { + renewal_timeout: Delay, + pending_msgs: VecDeque, + to_listener: Sender, +} + pub enum OutboundOpenInfo { Reserve { to_listener: Sender, diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 2a644e5d17f..3bf8c00866d 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -177,10 +177,21 @@ impl Circuit { Ok((io, read_buffer.freeze())) } + pub async fn deny(mut self, status: Status) -> Result<(), std::io::Error> { + let msg = StopMessage { + r#type: stop_message::Type::Status.into(), + peer: None, + limit: None, + status: Some(status.into()), + }; + + self.send(msg).await + } + async fn send(&mut self, msg: StopMessage) -> Result<(), std::io::Error> { let mut msg_bytes = BytesMut::new(); msg.encode(&mut msg_bytes) - // TODO: Sure panicing is safe here? + // TODO: Sure panicing is safe here? .expect("all the mandatory fields are always filled; QED"); self.substream.send(msg_bytes.freeze()).await?; self.substream.flush().await?; diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 0c520b34ee3..ce5145d736f 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -65,8 +65,6 @@ impl Default for Config { } /// The events produced by the [`Relay`] behaviour. -// -// TODO: Should this be renamed to Event? Would be inline with [`Config`]. #[derive(Debug)] pub enum Event { /// An inbound reservation request has been accepted. From 79fd6be2ca9199dd40811bbe1fa30e9e82712c8c Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 18 May 2021 15:13:28 +0200 Subject: [PATCH 012/108] protocols/relay: Renew reservations --- protocols/relay/src/v2/client.rs | 5 +- protocols/relay/src/v2/client/handler.rs | 114 ++++++++++++++--------- protocols/relay/src/v2/relay.rs | 14 +-- protocols/relay/tests/v2.rs | 27 +++++- 4 files changed, 108 insertions(+), 52 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 032cd8f3957..33cc27a08d1 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -49,6 +49,8 @@ pub enum Event { // TODO: Should this be renamed to ReservationAccepted? Reserved { relay_peer_id: PeerId, + /// Indicates whether the request replaces an existing reservation. + renewed: bool, }, /// An inbound circuit request has been denied. CircuitReqDenied { @@ -180,10 +182,11 @@ impl NetworkBehaviour for Client { handler_event: handler::Event, ) { match handler_event { - handler::Event::Reserved => { + handler::Event::Reserved { renewed } => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent(Event::Reserved { relay_peer_id: event_source, + renewed, })) } handler::Event::CircuitReqDenied { src_peer_id } => { diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index f0d7aeda47b..addbd671e46 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -143,27 +143,29 @@ impl ProtocolsHandler for Handler { inbound_circuit: >::Output, _: Self::InboundOpenInfo, ) { - if let Some(reservation) = &mut self.reservation { - let src_peer_id = inbound_circuit.src_peer_id(); - let (tx, rx) = oneshot::channel(); - self.alive_lend_out_substreams.push(rx); - let connection = super::RelayedConnection::new_inbound(inbound_circuit, tx); - reservation.pending_msgs.push_back( - transport::ToListenerMsg::IncomingRelayedConnection { + match &mut self.reservation { + Some(Reservation::Accepted { pending_msgs, .. }) + | Some(Reservation::Renewal { pending_msgs, .. }) => { + let src_peer_id = inbound_circuit.src_peer_id(); + let (tx, rx) = oneshot::channel(); + self.alive_lend_out_substreams.push(rx); + let connection = super::RelayedConnection::new_inbound(inbound_circuit, tx); + pending_msgs.push_back(transport::ToListenerMsg::IncomingRelayedConnection { stream: connection, src_peer_id, relay_peer_id: self.remote_peer_id, relay_addr: self.remote_addr.clone(), - }, - ) - } else { - let src_peer_id = inbound_circuit.src_peer_id(); - self.circuit_deny_futs.push( - inbound_circuit - .deny(Status::NoReservation) - .map(move |result| (src_peer_id, result)) - .boxed(), - ) + }) + } + _ => { + let src_peer_id = inbound_circuit.src_peer_id(); + self.circuit_deny_futs.push( + inbound_circuit + .deny(Status::NoReservation) + .map(move |result| (src_peer_id, result)) + .boxed(), + ) + } } } @@ -189,14 +191,15 @@ impl ProtocolsHandler for Handler { .map(|a| a.with(Protocol::P2p(local_peer_id.into()))) .collect(), }); - self.reservation = Some(Reservation { + self.reservation = Some(Reservation::Accepted { // TODO: Should timeout fire early to make sure reservation never expires? - renewal_timeout: Delay::new( - expire - .expect("TODO handle where no expire") - .checked_duration_since(Instant::now()) - .unwrap(), - ), + renewal_timeout: expire.map(|e| { + Delay::new( + e.checked_duration_since(Instant::now()) + // TODO Handle + .unwrap(), + ) + }), pending_msgs, to_listener, }) @@ -259,6 +262,7 @@ impl ProtocolsHandler for Handler { _open_info: Self::OutboundOpenInfo, error: ProtocolsHandlerUpgrErr<::Error>, ) { + // TODO: If this was a reservation request, set reservation to `None`. panic!("{:?}", error) } @@ -285,27 +289,47 @@ impl ProtocolsHandler for Handler { return Poll::Ready(event); } - if let Some(reservation) = &mut self.reservation { - if !reservation.pending_msgs.is_empty() { - match reservation.to_listener.poll_ready(cx) { + // Maintain existing reservation. + if let Some(Reservation::Accepted { + renewal_timeout, + pending_msgs, + to_listener, + }) = &mut self.reservation + { + if !pending_msgs.is_empty() { + match to_listener.poll_ready(cx) { Poll::Ready(Ok(())) => { - reservation - .to_listener - .start_send( - reservation - .pending_msgs - .pop_front() - .expect("Called !is_empty()."), - ) + to_listener + .start_send(pending_msgs.pop_front().expect("Called !is_empty().")) + // TODO: Handle .unwrap(); } Poll::Ready(Err(e)) => todo!("{:?}", e), Poll::Pending => {} } } - match reservation.renewal_timeout.poll_unpin(cx) { - Poll::Ready(()) => todo!(), - Poll::Pending => {} + match renewal_timeout.as_mut().map(|t| t.poll_unpin(cx)) { + Some(Poll::Ready(())) => { + // TODO: Can we do better? + match self.reservation.take() { + Some(Reservation::Accepted { + to_listener, + pending_msgs, + .. + }) => { + self.reservation = Some(Reservation::Renewal { pending_msgs }); + return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new( + outbound_hop::Upgrade::Reserve, + OutboundOpenInfo::Reserve { to_listener }, + ), + }); + } + _ => unreachable!(), + } + } + Some(Poll::Pending) => {} + None => {} } } @@ -330,10 +354,16 @@ impl ProtocolsHandler for Handler { } } -struct Reservation { - renewal_timeout: Delay, - pending_msgs: VecDeque, - to_listener: Sender, +enum Reservation { + Accepted { + /// [`None`] if reservation does not expire. + renewal_timeout: Option, + pending_msgs: VecDeque, + to_listener: Sender, + }, + Renewal { + pending_msgs: VecDeque, + }, } pub enum OutboundOpenInfo { diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index ce5145d736f..15cf492e6cb 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -35,16 +35,16 @@ use std::time::Duration; // TODO: Expose this as RelayConfig? pub struct Config { // TODO: Should we use u32? - max_reservations: usize, - _max_reservations_per_ip: u32, + pub max_reservations: usize, + pub _max_reservations_per_ip: u32, // TODO: Good idea? - _max_reservations_per_asn: u32, - reservation_duration: Duration, + pub _max_reservations_per_asn: u32, + pub reservation_duration: Duration, // TODO: Should we use u32? - max_circuits: usize, - max_circuit_duration: Duration, - max_circuit_bytes: u64, + pub max_circuits: usize, + pub max_circuit_duration: Duration, + pub max_circuit_bytes: u64, } impl Default for Config { diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index 9a47917223a..b8f42c5df3e 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -34,6 +34,7 @@ use libp2p::relay::v2::client; use libp2p::relay::v2::relay; use libp2p::NetworkBehaviour; use libp2p_swarm::{AddressScore, NetworkBehaviour, Swarm, SwarmEvent}; +use std::time::Duration; #[test] fn reservation() { @@ -57,10 +58,22 @@ fn reservation() { client.listen_on(client_addr.clone()).unwrap(); + // Wait for initial reservation. + pool.run_until(wait_for_reservation( + &mut client, + client_addr + .clone() + .with(Protocol::P2p(client_peer_id.into())), + relay_peer_id, + false, // No renewal. + )); + + // Wait for renewal. pool.run_until(wait_for_reservation( &mut client, client_addr.with(Protocol::P2p(client_peer_id.into())), relay_peer_id, + true, // Renewal. )); } @@ -91,6 +104,7 @@ fn connect() { &mut dst, dst_addr.clone(), relay_peer_id, + false, // No renewal. )); spawn_swarm_on_pool(&pool, dst); @@ -167,6 +181,7 @@ fn reuse_connection() { &mut client, client_addr.with(Protocol::P2p(client_peer_id.into())), relay_peer_id, + false, // No renewal. )); } @@ -181,7 +196,13 @@ fn build_relay() -> Swarm { transport, Relay { ping: Ping::new(PingConfig::new()), - relay: relay::Relay::new(local_peer_id, Default::default()), + relay: relay::Relay::new( + local_peer_id, + relay::Config { + reservation_duration: Duration::from_secs(2), + ..Default::default() + }, + ), }, local_peer_id, ) @@ -282,12 +303,14 @@ async fn wait_for_reservation( client: &mut Swarm, client_addr: Multiaddr, relay_peer_id: PeerId, + renewal: bool, ) { loop { match client.next_event().await { SwarmEvent::Behaviour(ClientEvent::Relay(client::Event::Reserved { relay_peer_id: peer_id, - })) if relay_peer_id == peer_id => break, + renewed, + })) if relay_peer_id == peer_id && renewed == renewal => break, SwarmEvent::Behaviour(ClientEvent::Ping(_)) => {} SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => {} From 0ed31538dc6345b1dce63b2c0f24c2ee60bb5cfe Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 18 May 2021 16:01:00 +0200 Subject: [PATCH 013/108] protocols/relay: Handle invalid expiration in the past --- protocols/relay/src/v2/client/handler.rs | 46 +++++++++++-------- .../relay/src/v2/protocol/outbound_hop.rs | 16 +++++-- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index addbd671e46..d7484554852 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -36,7 +36,6 @@ use libp2p_swarm::{ }; use std::collections::VecDeque; use std::task::{Context, Poll}; -use std::time::Instant; pub enum In { Reserve { @@ -82,6 +81,7 @@ impl IntoProtocolsHandler for Prototype { remote_addr: endpoint.get_remote_address().clone(), local_peer_id: self.local_peer_id, queued_events: Default::default(), + pending_error: Default::default(), reservation: None, alive_lend_out_substreams: Default::default(), circuit_deny_futs: Default::default(), @@ -97,6 +97,11 @@ pub struct Handler { local_peer_id: PeerId, remote_peer_id: PeerId, remote_addr: Multiaddr, + pending_error: Option< + ProtocolsHandlerUpgrErr< + EitherError, + >, + >, /// Queue of events to return when polled. queued_events: VecDeque< @@ -176,33 +181,32 @@ impl ProtocolsHandler for Handler { ) { match (output, info) { ( - outbound_hop::Output::Reservation { expire, addrs }, + outbound_hop::Output::Reservation { + renewal_timeout, + addrs, + }, OutboundOpenInfo::Reserve { to_listener }, ) => { - self.queued_events - .push_back(ProtocolsHandlerEvent::Custom(Event::Reserved { - renewed: self.reservation.is_some(), - })); - let local_peer_id = self.local_peer_id; - let mut pending_msgs = VecDeque::new(); + let (renewed, mut pending_msgs) = match self.reservation.take() { + Some(Reservation::Accepted { pending_msgs, .. }) + | Some(Reservation::Renewal { pending_msgs, .. }) => (true, pending_msgs), + None => (false, VecDeque::new()), + }; + pending_msgs.push_back(transport::ToListenerMsg::Reservation { addrs: addrs .into_iter() - .map(|a| a.with(Protocol::P2p(local_peer_id.into()))) + .map(|a| a.with(Protocol::P2p(self.local_peer_id.into()))) .collect(), }); self.reservation = Some(Reservation::Accepted { - // TODO: Should timeout fire early to make sure reservation never expires? - renewal_timeout: expire.map(|e| { - Delay::new( - e.checked_duration_since(Instant::now()) - // TODO Handle - .unwrap(), - ) - }), + renewal_timeout, pending_msgs, to_listener, - }) + }); + + self.queued_events + .push_back(ProtocolsHandlerEvent::Custom(Event::Reserved { renewed })); } ( outbound_hop::Output::Circuit { @@ -284,6 +288,12 @@ impl ProtocolsHandler for Handler { Self::Error, >, > { + // Check for a pending (fatal) error. + if let Some(err) = self.pending_error.take() { + // The handler will not be polled again by the `Swarm`. + return Poll::Ready(ProtocolsHandlerEvent::Close(err)); + } + // Return queued events. if let Some(event) = self.queued_events.pop_front() { return Poll::Ready(event); diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index dfbabde622d..bab7b0cf18c 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -19,10 +19,11 @@ // DEALINGS IN THE SOFTWARE. use crate::v2::message_proto::{hop_message, HopMessage, Peer, Status}; -use asynchronous_codec::{Framed, FramedParts}; use crate::v2::protocol::{HOP_PROTOCOL_NAME, MAX_MESSAGE_SIZE}; -use bytes::{Bytes}; +use asynchronous_codec::{Framed, FramedParts}; +use bytes::Bytes; use futures::{future::BoxFuture, prelude::*}; +use futures_timer::Delay; use libp2p_core::{upgrade, Multiaddr, PeerId}; use libp2p_swarm::NegotiatedSubstream; use prost::Message; @@ -125,9 +126,11 @@ impl upgrade::OutboundUpgrade for Upgrade { .map_err(|_| UpgradeError::InvalidReservationAddrs)? }; - let expire = if let Some(expires) = reservation.expire { + let renewal_timeout = if let Some(expires) = reservation.expire { Some( unix_timestamp_to_instant(expires) + .and_then(|instant| instant.checked_duration_since(Instant::now())) + .map(|duration| Delay::new(duration)) .ok_or(UpgradeError::InvalidReservationExpiration)?, ) } else { @@ -136,7 +139,10 @@ impl upgrade::OutboundUpgrade for Upgrade { substream.close().await?; - Output::Reservation { expire, addrs } + Output::Reservation { + renewal_timeout, + addrs, + } } Upgrade::Connect { .. } => { let FramedParts { @@ -272,7 +278,7 @@ fn unix_timestamp_to_instant(secs: i64) -> Option { pub enum Output { Reservation { - expire: Option, + renewal_timeout: Option, addrs: Vec, }, Circuit { From 131863638770499ae4cd2694e0fd41a624f90821 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 18 May 2021 16:49:22 +0200 Subject: [PATCH 014/108] protocols/relay: Handle in and outbound failure --- protocols/relay/src/v2/client.rs | 52 ++++-- protocols/relay/src/v2/client/handler.rs | 193 +++++++++++++++++++++-- protocols/relay/tests/v2.rs | 8 +- 3 files changed, 220 insertions(+), 33 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 33cc27a08d1..5d19e099247 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -47,17 +47,25 @@ use std::task::{Context, Poll}; pub enum Event { /// An outbound reservation has been accepted. // TODO: Should this be renamed to ReservationAccepted? - Reserved { + ReservationReqAccepted { relay_peer_id: PeerId, /// Indicates whether the request replaces an existing reservation. - renewed: bool, + renewal: bool, + }, + ReservationReqFailed { + relay_peer_id: PeerId, + /// Indicates whether the request replaces an existing reservation. + renewal: bool, + }, + OutboundCircuitReqFailed { + relay_peer_id: PeerId, }, /// An inbound circuit request has been denied. - CircuitReqDenied { + InboundCircuitReqDenied { src_peer_id: PeerId, }, /// Denying an inbound circuit request failed. - CircuitReqDenyFailed { + InboundCircuitReqDenyFailed { src_peer_id: PeerId, error: std::io::Error, }, @@ -182,23 +190,41 @@ impl NetworkBehaviour for Client { handler_event: handler::Event, ) { match handler_event { - handler::Event::Reserved { renewed } => { + handler::Event::ReservationReqAccepted { renewal } => { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::ReservationReqAccepted { + relay_peer_id: event_source, + renewal, + }, + )) + } + handler::Event::ReservationReqFailed { renewal } => { self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent(Event::Reserved { - relay_peer_id: event_source, - renewed, - })) + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::ReservationReqFailed { + relay_peer_id: event_source, + renewal, + }, + )) } - handler::Event::CircuitReqDenied { src_peer_id } => { + handler::Event::OutboundCircuitReqFailed {} => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitReqDenied { src_peer_id }, + Event::OutboundCircuitReqFailed { + relay_peer_id: event_source, + }, )) } - handler::Event::CircuitReqDenyFailed { src_peer_id, error } => self + handler::Event::InboundCircuitReqDenied { src_peer_id } => self + .queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::InboundCircuitReqDenied { src_peer_id }, + )), + handler::Event::InboundCircuitReqDenyFailed { src_peer_id, error } => self .queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitReqDenyFailed { src_peer_id, error }, + Event::InboundCircuitReqDenyFailed { src_peer_id, error }, )), } } diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index d7484554852..cb960ebb4d6 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -49,14 +49,21 @@ pub enum In { } pub enum Event { - Reserved { + ReservationReqAccepted { /// Indicates whether the request replaces an existing reservation. - renewed: bool, + renewal: bool, }, + ReservationReqFailed { + /// Indicates whether the request replaces an existing reservation. + renewal: bool, + }, + OutboundCircuitReqFailed {}, /// An inbound circuit request has been denied. - CircuitReqDenied { src_peer_id: PeerId }, + InboundCircuitReqDenied { + src_peer_id: PeerId, + }, /// Denying an inbound circuit request failed. - CircuitReqDenyFailed { + InboundCircuitReqDenyFailed { src_peer_id: PeerId, error: std::io::Error, }, @@ -187,7 +194,7 @@ impl ProtocolsHandler for Handler { }, OutboundOpenInfo::Reserve { to_listener }, ) => { - let (renewed, mut pending_msgs) = match self.reservation.take() { + let (renewal, mut pending_msgs) = match self.reservation.take() { Some(Reservation::Accepted { pending_msgs, .. }) | Some(Reservation::Renewal { pending_msgs, .. }) => (true, pending_msgs), None => (false, VecDeque::new()), @@ -205,8 +212,9 @@ impl ProtocolsHandler for Handler { to_listener, }); - self.queued_events - .push_back(ProtocolsHandlerEvent::Custom(Event::Reserved { renewed })); + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::ReservationReqAccepted { renewal }, + )); } ( outbound_hop::Output::Circuit { @@ -256,18 +264,171 @@ impl ProtocolsHandler for Handler { fn inject_listen_upgrade_error( &mut self, _: Self::InboundOpenInfo, - _error: ProtocolsHandlerUpgrErr<::Error>, + error: ProtocolsHandlerUpgrErr<::Error>, ) { - todo!() + match error { + ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )) => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::ProtocolError(e), + )) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Select(upgrade::NegotiationError::ProtocolError(e)), + )); + } + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::A(error)), + )) + } + } } fn inject_dial_upgrade_error( &mut self, - _open_info: Self::OutboundOpenInfo, + open_info: Self::OutboundOpenInfo, error: ProtocolsHandlerUpgrErr<::Error>, ) { - // TODO: If this was a reservation request, set reservation to `None`. - panic!("{:?}", error) + match open_info { + // TODO: Inform listener or just drop it? + OutboundOpenInfo::Reserve { to_listener: _ } => { + let renewal = self.reservation.take().is_some(); + + match error { + ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )) => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::ProtocolError(e), + )) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Select( + upgrade::NegotiationError::ProtocolError(e), + ), + )); + } + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { + match error { + outbound_hop::UpgradeError::Decode(_) + | outbound_hop::UpgradeError::Io(_) + | outbound_hop::UpgradeError::ParseTypeField + | outbound_hop::UpgradeError::ParseStatusField + | outbound_hop::UpgradeError::MissingStatusField + | outbound_hop::UpgradeError::MissingReservationField + | outbound_hop::UpgradeError::NoAddressesinReservation + | outbound_hop::UpgradeError::InvalidReservationExpiration + | outbound_hop::UpgradeError::InvalidReservationAddrs + | outbound_hop::UpgradeError::UnexpectedTypeConnect + | outbound_hop::UpgradeError::UnexpectedTypeReserve => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )); + } + outbound_hop::UpgradeError::UnexpectedStatus(status) => { + match status { + Status::Ok => { + unreachable!( + "Status success was explicitly expected earlier." + ) + } + // With either status below there is either no reason to stay + // connected or it is a protocol violation. + // Thus terminate the connection. + Status::ConnectionFailed + | Status::NoReservation + | Status::PermissionDenied + | Status::UnexpectedMessage + | Status::MalformedMessage => { + self.pending_error = + Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )); + } + // The connection to the relay might still proof helpful CONNECT + // requests. Thus do not terminate the connection. + Status::ReservationRefused | Status::ResourceLimitExceeded => {} + } + } + } + } + } + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::ReservationReqFailed { renewal }, + )); + } + // TODO: Should we inform listenerupgrade via send_back? + OutboundOpenInfo::Connect { send_back: _ } => { + match error { + ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )) => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::ProtocolError(e), + )) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Select( + upgrade::NegotiationError::ProtocolError(e), + ), + )); + } + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { + match error { + outbound_hop::UpgradeError::Decode(_) + | outbound_hop::UpgradeError::Io(_) + | outbound_hop::UpgradeError::ParseTypeField + | outbound_hop::UpgradeError::ParseStatusField + | outbound_hop::UpgradeError::MissingStatusField + | outbound_hop::UpgradeError::MissingReservationField + | outbound_hop::UpgradeError::NoAddressesinReservation + | outbound_hop::UpgradeError::InvalidReservationExpiration + | outbound_hop::UpgradeError::InvalidReservationAddrs + | outbound_hop::UpgradeError::UnexpectedTypeConnect + | outbound_hop::UpgradeError::UnexpectedTypeReserve => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )); + } + outbound_hop::UpgradeError::UnexpectedStatus(status) => { + match status { + Status::Ok => { + unreachable!( + "Status success was explicitly expected earlier." + ) + } + // With either status below there is either no reason to stay + // connected or it is a protocol violation. + // Thus terminate the connection. + Status::ReservationRefused + | Status::UnexpectedMessage + | Status::MalformedMessage => { + self.pending_error = + Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )); + } + // While useless for reaching this particular destination, the + // connection to the relay might still proof helpful for other + // destinations. Thus do not terminate the connection. + Status::ResourceLimitExceeded + | Status::ConnectionFailed + | Status::NoReservation + | Status::PermissionDenied => {} + } + } + } + } + }; + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::OutboundCircuitReqFailed {}, + )); + } + } } // TODO: Why is this not a mut reference? If it were the case, we could do all keep alive handling in here. @@ -348,13 +509,13 @@ impl ProtocolsHandler for Handler { { match result { Ok(()) => { - return Poll::Ready(ProtocolsHandlerEvent::Custom(Event::CircuitReqDenied { - src_peer_id, - })) + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::InboundCircuitReqDenied { src_peer_id }, + )) } Err(error) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( - Event::CircuitReqDenyFailed { src_peer_id, error }, + Event::InboundCircuitReqDenyFailed { src_peer_id, error }, )) } } diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index b8f42c5df3e..0d27b25f722 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -303,14 +303,14 @@ async fn wait_for_reservation( client: &mut Swarm, client_addr: Multiaddr, relay_peer_id: PeerId, - renewal: bool, + is_renewal: bool, ) { loop { match client.next_event().await { - SwarmEvent::Behaviour(ClientEvent::Relay(client::Event::Reserved { + SwarmEvent::Behaviour(ClientEvent::Relay(client::Event::ReservationReqAccepted { relay_peer_id: peer_id, - renewed, - })) if relay_peer_id == peer_id && renewed == renewal => break, + renewal, + })) if relay_peer_id == peer_id && renewal == is_renewal => break, SwarmEvent::Behaviour(ClientEvent::Ping(_)) => {} SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => {} From 5df655e77b059eb4afe772fdb4ab9de3cee86f08 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 18 May 2021 19:53:53 +0200 Subject: [PATCH 015/108] protocols/relay: Implement client handler keep alive --- protocols/relay/src/v2/client/handler.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index cb960ebb4d6..536bc788840 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -36,6 +36,7 @@ use libp2p_swarm::{ }; use std::collections::VecDeque; use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; pub enum In { Reserve { @@ -92,6 +93,7 @@ impl IntoProtocolsHandler for Prototype { reservation: None, alive_lend_out_substreams: Default::default(), circuit_deny_futs: Default::default(), + keep_alive: KeepAlive::Yes, } } @@ -109,6 +111,8 @@ pub struct Handler { EitherError, >, >, + /// Until when to keep the connection alive. + keep_alive: KeepAlive, /// Queue of events to return when polled. queued_events: VecDeque< @@ -433,9 +437,7 @@ impl ProtocolsHandler for Handler { // TODO: Why is this not a mut reference? If it were the case, we could do all keep alive handling in here. fn connection_keep_alive(&self) -> KeepAlive { - // TODO - // TODO: cover lend out substreams. - KeepAlive::Yes + self.keep_alive } fn poll( @@ -521,6 +523,21 @@ impl ProtocolsHandler for Handler { } } + if self.reservation.is_none() + && self.alive_lend_out_substreams.is_empty() + && self.circuit_deny_futs.is_empty() + { + match self.keep_alive { + KeepAlive::Yes => { + self.keep_alive = KeepAlive::Until(Instant::now() + Duration::from_secs(10)); + } + KeepAlive::Until(_) => {} + KeepAlive::No => panic!("Handler never sets KeepAlive::No."), + } + } else { + self.keep_alive = KeepAlive::Yes; + } + Poll::Pending } } From 643ffb0f5ba51e664fddacb37f86a115f9519a78 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 19 May 2021 13:10:40 +0200 Subject: [PATCH 016/108] protocols/relay: Handle handler listener closed channel --- protocols/relay/src/v2/client/handler.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 536bc788840..0b653b82e12 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -34,6 +34,7 @@ use libp2p_swarm::{ IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, }; +use log::debug; use std::collections::VecDeque; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; @@ -472,10 +473,11 @@ impl ProtocolsHandler for Handler { if !pending_msgs.is_empty() { match to_listener.poll_ready(cx) { Poll::Ready(Ok(())) => { - to_listener + if let Err(e) = to_listener .start_send(pending_msgs.pop_front().expect("Called !is_empty().")) - // TODO: Handle - .unwrap(); + { + debug!("Failed to sent pending message to listener: {:?}", e); + } } Poll::Ready(Err(e)) => todo!("{:?}", e), Poll::Pending => {} @@ -506,6 +508,7 @@ impl ProtocolsHandler for Handler { } } + // Deny incoming circuit requests. while let Poll::Ready(Some((src_peer_id, result))) = self.circuit_deny_futs.poll_next_unpin(cx) { @@ -523,6 +526,7 @@ impl ProtocolsHandler for Handler { } } + // Update keep-alive handling. if self.reservation.is_none() && self.alive_lend_out_substreams.is_empty() && self.circuit_deny_futs.is_empty() From 570980d4e4d0a083e82113770d3c8cee93f145a2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 19 May 2021 15:39:44 +0200 Subject: [PATCH 017/108] protocols/relay: Handle handler to listener failure --- protocols/relay/src/v2/client/handler.rs | 60 ++++++++++++++---------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 0b653b82e12..faf0f8c7c4d 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -463,11 +463,38 @@ impl ProtocolsHandler for Handler { return Poll::Ready(event); } - // Maintain existing reservation. + // Check if reservation needs renewal. + match self.reservation.take() { + Some(Reservation::Accepted { + mut renewal_timeout, + pending_msgs, + to_listener, + }) => match renewal_timeout.as_mut().map(|t| t.poll_unpin(cx)) { + Some(Poll::Ready(())) => { + self.reservation = Some(Reservation::Renewal { pending_msgs }); + return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new( + outbound_hop::Upgrade::Reserve, + OutboundOpenInfo::Reserve { to_listener }, + ), + }); + } + Some(Poll::Pending) | None => { + self.reservation = Some(Reservation::Accepted { + renewal_timeout, + pending_msgs, + to_listener, + }); + } + }, + r => self.reservation = r, + } + + // Forward messages to transport listener. if let Some(Reservation::Accepted { - renewal_timeout, pending_msgs, to_listener, + .. }) = &mut self.reservation { if !pending_msgs.is_empty() { @@ -477,34 +504,15 @@ impl ProtocolsHandler for Handler { .start_send(pending_msgs.pop_front().expect("Called !is_empty().")) { debug!("Failed to sent pending message to listener: {:?}", e); + self.reservation.take(); } } - Poll::Ready(Err(e)) => todo!("{:?}", e), - Poll::Pending => {} - } - } - match renewal_timeout.as_mut().map(|t| t.poll_unpin(cx)) { - Some(Poll::Ready(())) => { - // TODO: Can we do better? - match self.reservation.take() { - Some(Reservation::Accepted { - to_listener, - pending_msgs, - .. - }) => { - self.reservation = Some(Reservation::Renewal { pending_msgs }); - return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new( - outbound_hop::Upgrade::Reserve, - OutboundOpenInfo::Reserve { to_listener }, - ), - }); - } - _ => unreachable!(), + Poll::Ready(Err(e)) => { + debug!("Channel to listener failed: {:?}", e); + self.reservation.take(); } + Poll::Pending => {} } - Some(Poll::Pending) => {} - None => {} } } From 037a398a39a739bad8808306c41bf29d36c8058d Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 19 May 2021 15:56:56 +0200 Subject: [PATCH 018/108] protocols/relay: Return all new listener addresses --- protocols/relay/src/v2/client/transport.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index da62f00500f..24610f77871 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -31,6 +31,7 @@ use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_core::transport::{ListenerEvent, TransportError}; use libp2p_core::{PeerId, Transport}; use pin_project::pin_project; +use std::collections::VecDeque; use std::pin::Pin; use std::task::{Context, Poll}; @@ -181,6 +182,7 @@ impl Transport for ClientTransport { ); Ok(RelayListener::Relayed { + queued_new_addresses: Default::default(), from_behaviour, msg_to_behaviour, }) @@ -308,8 +310,8 @@ fn parse_relayed_multiaddr( pub enum RelayListener { Inner(#[pin] ::Listener), Relayed { + queued_new_addresses: VecDeque, from_behaviour: mpsc::Receiver, - msg_to_behaviour: Option>>, }, } @@ -350,6 +352,7 @@ impl Stream for RelayListener { Poll::Pending => {} }, RelayListenerProj::Relayed { + queued_new_addresses, from_behaviour, msg_to_behaviour, } => { @@ -363,6 +366,10 @@ impl Stream for RelayListener { } } + if let Some(addr) = queued_new_addresses.pop_front() { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); + } + match from_behaviour.poll_next_unpin(cx) { Poll::Ready(Some(ToListenerMsg::IncomingRelayedConnection { stream, @@ -377,10 +384,14 @@ impl Stream for RelayListener { }))); } Poll::Ready(Some(ToListenerMsg::Reservation { addrs })) => { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress( - // TODO: What about the other addresses? - addrs.into_iter().next().unwrap() - )))); + let mut iter = addrs.into_iter(); + let first = iter.next(); + queued_new_addresses.extend(iter); + if let Some(addr) = first { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress( + addr, + )))); + } } Poll::Ready(None) => return Poll::Ready(None), Poll::Pending => {} From 806fd9354284017afc49cdca44fb8c288247382d Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 1 Jul 2021 15:04:24 +0200 Subject: [PATCH 019/108] protocols/relay/v2: Update to latest protobuf definition --- protocols/relay/src/v2/message.proto | 11 ++++------- protocols/relay/src/v2/protocol/inbound_hop.rs | 10 +++------- protocols/relay/src/v2/protocol/outbound_hop.rs | 8 ++------ protocols/relay/src/v2/protocol/outbound_stop.rs | 5 ++--- 4 files changed, 11 insertions(+), 23 deletions(-) diff --git a/protocols/relay/src/v2/message.proto b/protocols/relay/src/v2/message.proto index da6a29ca09c..b3b16511269 100644 --- a/protocols/relay/src/v2/message.proto +++ b/protocols/relay/src/v2/message.proto @@ -38,17 +38,14 @@ message Peer { } message Reservation { - // TODO: Should this be a timestamp instead? - optional int64 expire = 1; // Unix expiration time (UTC) + optional uint64 expire = 1; // Unix expiration time (UTC) repeated bytes addrs = 2; // relay addrs for reserving peer optional bytes voucher = 3; // reservation voucher } message Limit { - // TODO: Why use an int32 instead of a uint32? - optional int32 duration = 1; // seconds - // TODO: Why use an int64 instead of a uint64? - optional int64 data = 2; // bytes + optional uint32 duration = 1; // seconds + optional uint64 data = 2; // bytes } enum Status { @@ -60,4 +57,4 @@ enum Status { NO_RESERVATION = 204; MALFORMED_MESSAGE = 400; UNEXPECTED_MESSAGE = 401; -} +} \ No newline at end of file diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 0ff153759e5..6101bba4a71 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -174,19 +174,15 @@ impl ReservationReq { (SystemTime::now() + self.reservation_duration) .duration_since(UNIX_EPOCH) .unwrap() - .as_secs() - .try_into() - // TODO: Can we do better? Why represent time as an i64 in protobuf in the first place? - .expect("Time to fit i64."), + .as_secs(), ), // TODO: Does this need to be set? voucher: None, }), limit: Some(Limit { - // TODO: Handle the unwrap. Why use an i32 in protobuf in the first place? + // TODO: Handle unwrap duration: Some(self.max_circuit_duration.as_secs().try_into().unwrap()), - // TODO: Handle the unwrap. Why use an i64 instead of a u64? - data: Some(self.max_circuit_bytes.try_into().unwrap()), + data: Some(self.max_circuit_bytes), }), status: Some(Status::Ok.into()), }; diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index bab7b0cf18c..2417022f490 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -259,17 +259,13 @@ impl error::Error for UpgradeError { } } -// TODO: This should use a u64. -fn unix_timestamp_to_instant(secs: i64) -> Option { +fn unix_timestamp_to_instant(secs: u64) -> Option { Instant::now().checked_add(Duration::from_secs( secs.checked_sub( SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() - .as_secs() - .try_into() - // TODO: Can we do better? Why represent time as an i64 in protobuf in the first place? - .expect("Time to fit i64."), + .as_secs(), )? .try_into() .ok()?, diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs index c8a969be004..c5ac894042e 100644 --- a/protocols/relay/src/v2/protocol/outbound_stop.rs +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -60,10 +60,9 @@ impl upgrade::OutboundUpgrade for Upgrade { addrs: vec![], }), limit: Some(Limit { - // TODO: Handle the unwrap. Why use an i32 in protobuf in the first place? + // TODO: Handle unwrap? duration: Some(self.max_circuit_duration.as_secs().try_into().unwrap()), - // TODO: Handle the unwrap. Why use an i64 instead of a u64? - data: Some(self.max_circuit_bytes.try_into().unwrap()), + data: Some(self.max_circuit_bytes), }), status: None, }; From 1723e9227252a56454a48d1af62542f80b5a74c0 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 2 Jul 2021 21:23:22 +0200 Subject: [PATCH 020/108] Revert "misc/multistream-select: Ignore simultaneous open 'iamclient'" This reverts commit 125e3c3e5740fb9334a3ccc3e9598f2479fbd635. --- misc/multistream-select/src/protocol.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/misc/multistream-select/src/protocol.rs b/misc/multistream-select/src/protocol.rs index eeffc87c888..1d056de75ec 100644 --- a/misc/multistream-select/src/protocol.rs +++ b/misc/multistream-select/src/protocol.rs @@ -42,7 +42,6 @@ const MSG_MULTISTREAM_1_0: &[u8] = b"/multistream/1.0.0\n"; const MSG_PROTOCOL_NA: &[u8] = b"na\n"; /// The encoded form of a multistream-select 'ls' message. const MSG_LS: &[u8] = b"ls\n"; -const MSG_IAMCLIENT: &[u8] = b"iamclient\n"; /// The multistream-select header lines preceeding negotiation. /// @@ -172,13 +171,6 @@ impl Message { return Ok(Message::ListProtocols) } - // TODO: This is a hack for now. - // - // See https://github.com/libp2p/specs/pull/196#discussion_r615846605 for details. - if msg == MSG_IAMCLIENT { - return Ok(Message::Protocol(Protocol("iamclient".into()))) - } - // If it starts with a `/`, ends with a line feed without any // other line feeds in-between, it must be a protocol name. if msg.get(0) == Some(&b'/') && msg.last() == Some(&b'\n') && From ea53ad770a7d6988911556eb82cd8cdacd8c073a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 9 Jul 2021 19:22:34 +0200 Subject: [PATCH 021/108] protocols/relay/v2: Report back to transport --- protocols/relay/src/v2/client.rs | 2 +- protocols/relay/src/v2/client/handler.rs | 44 ++++++++++++------- protocols/relay/src/v2/client/transport.rs | 50 +++++++++++----------- protocols/relay/tests/v2.rs | 22 +++++++--- 4 files changed, 69 insertions(+), 49 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 5d19e099247..fa820935055 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -554,7 +554,7 @@ enum RqstPendingConnection { Circuit { dst_peer_id: PeerId, relay_addr: Multiaddr, - send_back: oneshot::Sender>, + send_back: oneshot::Sender>, }, } diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index faf0f8c7c4d..d42147e0aee 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -24,6 +24,7 @@ use crate::v2::protocol::{inbound_stop, outbound_hop}; use futures::channel::mpsc::Sender; use futures::channel::oneshot; use futures::future::{BoxFuture, FutureExt}; +use futures::sink::SinkExt; use futures::stream::{FuturesUnordered, StreamExt}; use futures_timer::Delay; use libp2p_core::either::EitherError; @@ -45,8 +46,7 @@ pub enum In { }, EstablishCircuit { dst_peer_id: PeerId, - send_back: - oneshot::Sender>, + send_back: oneshot::Sender>, }, } @@ -94,6 +94,7 @@ impl IntoProtocolsHandler for Prototype { reservation: None, alive_lend_out_substreams: Default::default(), circuit_deny_futs: Default::default(), + send_error_futs: Default::default(), keep_alive: KeepAlive::Yes, } } @@ -138,6 +139,8 @@ pub struct Handler { alive_lend_out_substreams: FuturesUnordered>, circuit_deny_futs: FuturesUnordered)>>, + + send_error_futs: FuturesUnordered>, } impl ProtocolsHandler for Handler { @@ -172,7 +175,7 @@ impl ProtocolsHandler for Handler { src_peer_id, relay_peer_id: self.remote_peer_id, relay_addr: self.remote_addr.clone(), - }) + }); } _ => { let src_peer_id = inbound_circuit.src_peer_id(); @@ -205,12 +208,14 @@ impl ProtocolsHandler for Handler { None => (false, VecDeque::new()), }; - pending_msgs.push_back(transport::ToListenerMsg::Reservation { - addrs: addrs - .into_iter() - .map(|a| a.with(Protocol::P2p(self.local_peer_id.into()))) - .collect(), - }); + pending_msgs.push_back(transport::ToListenerMsg::Reservation(Ok( + transport::Reservation { + addrs: addrs + .into_iter() + .map(|a| a.with(Protocol::P2p(self.local_peer_id.into()))) + .collect(), + }, + ))); self.reservation = Some(Reservation::Accepted { renewal_timeout, pending_msgs, @@ -297,8 +302,7 @@ impl ProtocolsHandler for Handler { error: ProtocolsHandlerUpgrErr<::Error>, ) { match open_info { - // TODO: Inform listener or just drop it? - OutboundOpenInfo::Reserve { to_listener: _ } => { + OutboundOpenInfo::Reserve { mut to_listener } => { let renewal = self.reservation.take().is_some(); match error { @@ -361,12 +365,18 @@ impl ProtocolsHandler for Handler { } } + self.send_error_futs.push( + async move { + let _ = to_listener.send(transport::ToListenerMsg::Reservation(Err(()))); + } + .boxed(), + ); + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( Event::ReservationReqFailed { renewal }, )); } - // TODO: Should we inform listenerupgrade via send_back? - OutboundOpenInfo::Connect { send_back: _ } => { + OutboundOpenInfo::Connect { send_back } => { match error { ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( @@ -429,6 +439,8 @@ impl ProtocolsHandler for Handler { } }; + let _ = send_back.send(Err(())); + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( Event::OutboundCircuitReqFailed {}, )); @@ -534,6 +546,9 @@ impl ProtocolsHandler for Handler { } } + // Send errors to transport. + while let Poll::Ready(Some(())) = self.send_error_futs.poll_next_unpin(cx) {} + // Update keep-alive handling. if self.reservation.is_none() && self.alive_lend_out_substreams.is_empty() @@ -571,7 +586,6 @@ pub enum OutboundOpenInfo { to_listener: Sender, }, Connect { - send_back: - oneshot::Sender>, + send_back: oneshot::Sender>, }, } diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 24610f77871..5df27e6e805 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -226,7 +226,7 @@ impl Transport for ClientTransport { send_back: tx, }) .await?; - let stream = rx.await??; + let stream = rx.await?.map_err(|()| RelayError::Connect)?; Ok(stream) } .boxed(), @@ -383,17 +383,22 @@ impl Stream for RelayListener { remote_addr: Protocol::P2p(src_peer_id.into()).into(), }))); } - Poll::Ready(Some(ToListenerMsg::Reservation { addrs })) => { + Poll::Ready(Some(ToListenerMsg::Reservation(Ok(Reservation { addrs })))) => { let mut iter = addrs.into_iter(); let first = iter.next(); queued_new_addresses.extend(iter); if let Some(addr) = first { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress( - addr, - )))); + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); } } - Poll::Ready(None) => return Poll::Ready(None), + Poll::Ready(Some(ToListenerMsg::Reservation(Err(())))) => { + return Poll::Ready(Some(Err(EitherError::B(RelayError::Reservation)))); + } + Poll::Ready(None) => { + // TODO: Assumption safe here? + panic!("Expect `from_behaviour` not to be dropped before listener."); + + }, Poll::Pending => {} } } @@ -435,7 +440,7 @@ impl Future for RelayedListenerUpgrade { } /// Error that occurred during relay connection setup. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug)] pub enum RelayError { MissingRelayPeerId, MissingRelayAddr, @@ -443,9 +448,10 @@ pub enum RelayError { InvalidHash, SendingMessageToBehaviour(mpsc::SendError), ResponseFromBehaviourCanceled, - DialingRelay, MultipleCircuitRelayProtocolsUnsupported, MalformedMultiaddr, + Reservation, + Connect, } impl From for TransportError> { @@ -466,14 +472,6 @@ impl From for RelayError { } } -impl From for RelayError { - fn from(error: OutgoingRelayReqError) -> Self { - match error { - OutgoingRelayReqError::DialingRelay => RelayError::DialingRelay, - } - } -} - impl std::fmt::Display for RelayError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -495,15 +493,18 @@ impl std::fmt::Display for RelayError { RelayError::ResponseFromBehaviourCanceled => { write!(f, "Response from behaviour was canceled") } - RelayError::DialingRelay => { - write!(f, "Dialing relay failed") - } RelayError::MultipleCircuitRelayProtocolsUnsupported => { write!(f, "Address contains multiple circuit relay protocols (`p2p-circuit`) which is not supported.") } RelayError::MalformedMultiaddr => { write!(f, "One of the provided multiaddresses is malformed.") } + RelayError::Reservation => { + write!(f, "Failed to get Reservation.") + } + RelayError::Connect => { + write!(f, "Failed to connect to destination.") + } } } } @@ -520,7 +521,7 @@ pub enum TransportToBehaviourMsg { relay_peer_id: PeerId, dst_addr: Option, dst_peer_id: PeerId, - send_back: oneshot::Sender>, + send_back: oneshot::Sender>, }, /// Listen for incoming relayed connections via relay node. ListenReq { @@ -531,9 +532,7 @@ pub enum TransportToBehaviourMsg { } pub enum ToListenerMsg { - Reservation { - addrs: Vec, - }, + Reservation(Result), IncomingRelayedConnection { stream: RelayedConnection, src_peer_id: PeerId, @@ -542,7 +541,6 @@ pub enum ToListenerMsg { }, } -#[derive(Debug, Eq, PartialEq)] -pub enum OutgoingRelayReqError { - DialingRelay, +pub struct Reservation { + pub(crate) addrs: Vec, } diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index c1c2563d600..7e084f6cbd7 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -305,24 +305,32 @@ async fn wait_for_reservation( relay_peer_id: PeerId, is_renewal: bool, ) { + let mut new_listen_addr = false; + let mut reservation_req_accepted = false; + loop { match client.select_next_some().await { SwarmEvent::Behaviour(ClientEvent::Relay(client::Event::ReservationReqAccepted { relay_peer_id: peer_id, renewal, - })) if relay_peer_id == peer_id && renewal == is_renewal => break, + })) if relay_peer_id == peer_id && renewal == is_renewal => { + reservation_req_accepted = true; + if new_listen_addr { + break; + } + } + SwarmEvent::NewListenAddr { address, .. } if address == client_addr => { + new_listen_addr = true; + if reservation_req_accepted { + break; + } + } SwarmEvent::Behaviour(ClientEvent::Ping(_)) => {} SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => {} e => panic!("{:?}", e), } } - - // Wait for `NewListenAddr` event. - match client.select_next_some().await { - SwarmEvent::NewListenAddr { address, .. } if address == client_addr => {} - e => panic!("{:?}", e), - } } async fn wait_for_dial(client: &mut Swarm, relay_peer_id: PeerId) -> bool { From 599aa0974d2a2b129ec16c943c94b660ffe3451a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 9 Jul 2021 19:27:01 +0200 Subject: [PATCH 022/108] protocols/relay/v2: Disconnect when stop protocol not supported --- protocols/relay/src/v2/relay/handler.rs | 61 ++++++++++++------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 11776e6e81e..d68089ff4db 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -18,10 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::v2::relay::CircuitId; use crate::v2::copy_future::CopyFuture; use crate::v2::message_proto::Status; use crate::v2::protocol::{inbound_hop, outbound_stop}; +use crate::v2::relay::CircuitId; use bytes::Bytes; use futures::channel::oneshot::{self, Canceled}; use futures::future::{BoxFuture, FutureExt, TryFutureExt}; @@ -417,11 +417,16 @@ impl ProtocolsHandler for Handler { ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => { Status::ConnectionFailed } - // TODO: This should not happen, as the remote has previously done a reservation. - // Doing a reservation but not supporting the stop protocol seems odd. ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::Failed, - )) => Status::ConnectionFailed, + )) => { + // The remote has previously done a reservation. Doing a reservation but not + // supporting the stop protocol is pointless, thus disconnecting. + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Select(upgrade::NegotiationError::Failed), + )); + Status::ConnectionFailed + } ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::ProtocolError(e), )) => { @@ -525,22 +530,18 @@ impl ProtocolsHandler for Handler { { match result { Ok(()) => { - return Poll::Ready(ProtocolsHandlerEvent::Custom( - Event::CircuitClosed { - circuit_id, - dst_peer_id, - error: None, - }, - )) + return Poll::Ready(ProtocolsHandlerEvent::Custom(Event::CircuitClosed { + circuit_id, + dst_peer_id, + error: None, + })) } Err(e) => { - return Poll::Ready(ProtocolsHandlerEvent::Custom( - Event::CircuitClosed { - circuit_id, - dst_peer_id, - error: Some(e), - }, - )) + return Poll::Ready(ProtocolsHandlerEvent::Custom(Event::CircuitClosed { + circuit_id, + dst_peer_id, + error: Some(e), + })) } } } @@ -620,12 +621,10 @@ impl ProtocolsHandler for Handler { self.circuits.push(circuit); - return Poll::Ready(ProtocolsHandlerEvent::Custom( - Event::CircuitReqAccepted { - circuit_id, - dst_peer_id, - }, - )); + return Poll::Ready(ProtocolsHandlerEvent::Custom(Event::CircuitReqAccepted { + circuit_id, + dst_peer_id, + })); } Err((circuit_id, dst_peer_id, error)) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( @@ -644,12 +643,10 @@ impl ProtocolsHandler for Handler { { match result { Ok(()) => { - return Poll::Ready(ProtocolsHandlerEvent::Custom( - Event::CircuitReqDenied { - circuit_id, - dst_peer_id, - }, - )); + return Poll::Ready(ProtocolsHandlerEvent::Custom(Event::CircuitReqDenied { + circuit_id, + dst_peer_id, + })); } Err(error) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( @@ -673,9 +670,7 @@ impl ProtocolsHandler for Handler { .map(|fut| fut.poll_unpin(cx)) { self.active_reservation = None; - return Poll::Ready(ProtocolsHandlerEvent::Custom( - Event::ReservationTimedOut {}, - )); + return Poll::Ready(ProtocolsHandlerEvent::Custom(Event::ReservationTimedOut {})); } if self.reservation_accept_futures.is_empty() From 0b8d54c95c188914eafccc7bc1049e5248f3db1f Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 9 Jul 2021 19:37:42 +0200 Subject: [PATCH 023/108] protocols/relay/v2: Document max_duration not exceed u32::MAX --- protocols/relay/src/v2/client.rs | 4 ---- protocols/relay/src/v2/protocol/outbound_stop.rs | 9 ++++++--- protocols/relay/src/v2/relay.rs | 5 +++++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index fa820935055..31a780463c5 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -46,7 +46,6 @@ use std::task::{Context, Poll}; #[derive(Debug)] pub enum Event { /// An outbound reservation has been accepted. - // TODO: Should this be renamed to ReservationAccepted? ReservationReqAccepted { relay_peer_id: PeerId, /// Indicates whether the request replaces an existing reservation. @@ -320,9 +319,6 @@ impl NetworkBehaviour for Client { } } - // TODO: Should we return NetworkBehaviourAction::ReportObservedAddr when an outbound - // reservation is acknowledged with the addresses of the relay? - Poll::Pending } } diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs index c5ac894042e..686546a2bfe 100644 --- a/protocols/relay/src/v2/protocol/outbound_stop.rs +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -60,8 +60,12 @@ impl upgrade::OutboundUpgrade for Upgrade { addrs: vec![], }), limit: Some(Limit { - // TODO: Handle unwrap? - duration: Some(self.max_circuit_duration.as_secs().try_into().unwrap()), + duration: Some( + self.max_circuit_duration + .as_secs() + .try_into() + .expect("`max_circuit_duration` not to exceed `u32::MAX`."), + ), data: Some(self.max_circuit_bytes), }), status: None, @@ -69,7 +73,6 @@ impl upgrade::OutboundUpgrade for Upgrade { let mut encoded_msg = Vec::new(); msg.encode(&mut encoded_msg) - // TODO: Double check. Safe to panic here? .expect("all the mandatory fields are always filled; QED"); let mut codec = UviBytes::default(); diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 15cf492e6cb..1a9e59d898a 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -32,6 +32,11 @@ use std::ops::Add; use std::task::{Context, Poll}; use std::time::Duration; +/// Configuration for the [`Relay`] [`NetworkBehaviour`]. +/// +/// # Panics +/// +/// [`Config::max_circuit_duration`] may not exceed [`u32::MAX`]. // TODO: Expose this as RelayConfig? pub struct Config { // TODO: Should we use u32? From 3082efb682f251cf3e42fb6580ffa16483837430 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 9 Jul 2021 19:58:56 +0200 Subject: [PATCH 024/108] protocols/relay/v2: Don't append p2p-circuit as relay --- protocols/relay/src/v2/client/handler.rs | 5 ++++- protocols/relay/src/v2/protocol/inbound_hop.rs | 11 ++++++----- .../relay/src/v2/protocol/inbound_stop.rs | 1 - .../relay/src/v2/protocol/outbound_hop.rs | 1 - protocols/relay/src/v2/relay.rs | 18 +++++------------- 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index d42147e0aee..820742a2d1c 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -212,7 +212,10 @@ impl ProtocolsHandler for Handler { transport::Reservation { addrs: addrs .into_iter() - .map(|a| a.with(Protocol::P2p(self.local_peer_id.into()))) + .map(|a| { + a.with(Protocol::P2pCircuit) + .with(Protocol::P2p(self.local_peer_id.into())) + }) .collect(), }, ))); diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 6101bba4a71..66b94d3fef9 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -176,12 +176,15 @@ impl ReservationReq { .unwrap() .as_secs(), ), - // TODO: Does this need to be set? voucher: None, }), limit: Some(Limit { - // TODO: Handle unwrap - duration: Some(self.max_circuit_duration.as_secs().try_into().unwrap()), + duration: Some( + self.max_circuit_duration + .as_secs() + .try_into() + .expect("`max_circuit_duration` not to exceed `u32::MAX`."), + ), data: Some(self.max_circuit_bytes), }), status: Some(Status::Ok.into()), @@ -205,7 +208,6 @@ impl ReservationReq { async fn send(mut self, msg: HopMessage) -> Result<(), std::io::Error> { let mut msg_bytes = BytesMut::new(); msg.encode(&mut msg_bytes) - // TODO: Sure panicing is safe here? .expect("all the mandatory fields are always filled; QED"); self.substream.send(msg_bytes.freeze()).await?; self.substream.flush().await?; @@ -265,7 +267,6 @@ impl CircuitReq { async fn send(&mut self, msg: HopMessage) -> Result<(), std::io::Error> { let mut msg_bytes = BytesMut::new(); msg.encode(&mut msg_bytes) - // TODO: Sure panicing is safe here? .expect("all the mandatory fields are always filled; QED"); self.substream.send(msg_bytes.freeze()).await?; self.substream.flush().await?; diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 3bf8c00866d..318602ceea8 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -191,7 +191,6 @@ impl Circuit { async fn send(&mut self, msg: StopMessage) -> Result<(), std::io::Error> { let mut msg_bytes = BytesMut::new(); msg.encode(&mut msg_bytes) - // TODO: Sure panicing is safe here? .expect("all the mandatory fields are always filled; QED"); self.substream.send(msg_bytes.freeze()).await?; self.substream.flush().await?; diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index 2417022f490..734d4380fd2 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -75,7 +75,6 @@ impl upgrade::OutboundUpgrade for Upgrade { let mut encoded_msg = Vec::new(); msg.encode(&mut encoded_msg) - // TODO: Double check. Safe to panic here? .expect("all the mandatory fields are always filled; QED"); let mut codec = UviBytes::default(); diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 1a9e59d898a..8541ea7ea83 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -37,16 +37,13 @@ use std::time::Duration; /// # Panics /// /// [`Config::max_circuit_duration`] may not exceed [`u32::MAX`]. -// TODO: Expose this as RelayConfig? pub struct Config { - // TODO: Should we use u32? pub max_reservations: usize, - pub _max_reservations_per_ip: u32, - // TODO: Good idea? - pub _max_reservations_per_asn: u32, + // TODO: Implement rate limiting. + // pub _max_reservations_per_ip: u32, + // pub _max_reservations_per_asn: u32, pub reservation_duration: Duration, - // TODO: Should we use u32? pub max_circuits: usize, pub max_circuit_duration: Duration, pub max_circuit_bytes: u64, @@ -56,9 +53,8 @@ impl Default for Config { fn default() -> Self { Config { max_reservations: 128, - _max_reservations_per_ip: 4, - // TODO: Good idea? - _max_reservations_per_asn: 32, + // _max_reservations_per_ip: 4, + // _max_reservations_per_asn: 32, reservation_duration: Duration::from_secs(60 * 60), // TODO: Shouldn't we have a limit per IP and ASN as well? @@ -485,12 +481,8 @@ impl NetworkBehaviour for Relay { .map(|a| { a.addr .with(Protocol::P2p((*poll_parameters.local_peer_id()).into())) - // TODO: Is it really required to add the p2p circuit protocol at the end? Why? - .with(Protocol::P2pCircuit) }) .collect(); - // TODO remove - assert!(!addrs.is_empty()) } return Poll::Ready(event); From 5f073c65185cb56e1cd979325f8657003a92e9b0 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 11 Jul 2021 12:31:28 +0200 Subject: [PATCH 025/108] protocols/relay/v2: Implement rate limiter --- protocols/relay/Cargo.toml | 7 +- protocols/relay/src/v2/relay.rs | 29 ++- protocols/relay/src/v2/relay/rate_limiter.rs | 256 +++++++++++++++++++ 3 files changed, 283 insertions(+), 9 deletions(-) create mode 100644 protocols/relay/src/v2/relay/rate_limiter.rs diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index f1b75447158..43a293aa920 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -19,7 +19,7 @@ libp2p-swarm = { version = "0.30", path = "../../swarm" } log = "0.4" pin-project = "1" prost = "0.8" -rand = "0.7" +rand = "0.8.4" smallvec = "1.6.1" static_assertions = "1" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } @@ -31,10 +31,11 @@ prost-build = "0.8" [dev-dependencies] env_logger = "0.8.3" -structopt = "0.3.21" libp2p = { path = "../.." } +libp2p-identify = { path = "../identify" } libp2p-kad = { path = "../kad" } libp2p-ping = { path = "../ping" } -libp2p-identify = { path = "../identify" } libp2p-plaintext = { path = "../../transports/plaintext" } libp2p-yamux = { path = "../../muxers/yamux" } +quickcheck = "1" +structopt = "0.3.21" diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 8541ea7ea83..035d2c719a9 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -21,6 +21,9 @@ //! [`NetworkBehaviour`] to act as a circuit relay v2 **relay**. mod handler; +mod rate_limiter; + +pub use rate_limiter::Config as RateLimiterConfig; use crate::v2::message_proto; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; @@ -30,7 +33,8 @@ use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, Poll use std::collections::{HashMap, HashSet, VecDeque}; use std::ops::Add; use std::task::{Context, Poll}; -use std::time::Duration; +use std::time::{Duration, Instant}; +use std::num::NonZeroU32; /// Configuration for the [`Relay`] [`NetworkBehaviour`]. /// @@ -39,11 +43,12 @@ use std::time::Duration; /// [`Config::max_circuit_duration`] may not exceed [`u32::MAX`]. pub struct Config { pub max_reservations: usize, - // TODO: Implement rate limiting. - // pub _max_reservations_per_ip: u32, - // pub _max_reservations_per_asn: u32, pub reservation_duration: Duration, + pub max_reservations_per_peer: RateLimiterConfig, + // pub max_reservations_per_ip: u32, + // pub max_reservations_per_asn: u32, + pub max_circuits: usize, pub max_circuit_duration: Duration, pub max_circuit_bytes: u64, @@ -53,10 +58,16 @@ impl Default for Config { fn default() -> Self { Config { max_reservations: 128, - // _max_reservations_per_ip: 4, - // _max_reservations_per_asn: 32, reservation_duration: Duration::from_secs(60 * 60), + max_reservations_per_peer: RateLimiterConfig { + // TODO: Made up values. Reconsider these. + limit: NonZeroU32::new(128).expect("128 > 0"), + interval: Duration::from_secs(60), + }, + // max_reservations_per_ip: 4, + // max_reservations_per_asn: 32, + // TODO: Shouldn't we have a limit per IP and ASN as well? max_circuits: 16, max_circuit_duration: Duration::from_secs(2 * 60), @@ -128,6 +139,10 @@ pub struct Relay { reservations: HashMap>, circuits: CircuitsTracker, + peer_rate_limiter: rate_limiter::RateLimiter, + // ip_rate_limiter: rate_limiter::RateLimiter, + // asn_rate_limiter: rate_limiter::RateLimiter, + /// Queue of actions to return when polled. queued_actions: VecDeque>, } @@ -135,6 +150,7 @@ pub struct Relay { impl Relay { pub fn new(local_peer_id: PeerId, config: Config) -> Self { Self { + peer_rate_limiter: rate_limiter::RateLimiter::new(config.max_reservations_per_peer), config, local_peer_id, reservations: Default::default(), @@ -211,6 +227,7 @@ impl NetworkBehaviour for Relay { .map(|(_, cs)| cs.len()) .sum::() < self.config.max_reservations + && self.peer_rate_limiter.try_next(event_source, Instant::now()) { self.reservations .entry(event_source) diff --git a/protocols/relay/src/v2/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs new file mode 100644 index 00000000000..06a7610dd07 --- /dev/null +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -0,0 +1,256 @@ +// 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. + +// TODO: Rate limit per (in that order): +// - PeerId +// - IP +// - ASN + +use std::collections::{HashMap, VecDeque}; +use std::convert::TryInto; +use std::hash::Hash; +use std::num::NonZeroU32; +use std::time::{Duration, Instant}; + +/// Rate limiter using the [Token Bucket] algorithm. +/// +/// [Token Bucket]: https://en.wikipedia.org/wiki/Token_bucket +pub(crate) struct RateLimiter { + limit: u32, + interval: Duration, + + refill_schedule: VecDeque<(Instant, Id)>, + buckets: HashMap, +} + +/// Configuration for a [`RateLimiter`]. +#[derive(Clone, Copy)] +pub struct Config { + /// The maximum number of tokens in the bucket at any point in time. + pub limit: NonZeroU32, + /// The interval at which a single token is added to the bucket. + pub interval: Duration, +} + +impl RateLimiter { + pub(crate) fn new(config: Config) -> Self { + assert!(!config.interval.is_zero()); + + Self { + limit: config.limit.into(), + interval: config.interval, + refill_schedule: Default::default(), + buckets: Default::default(), + } + } + + pub(crate) fn try_next(&mut self, id: Id, now: Instant) -> bool { + self.refill(now); + + match self.buckets.get_mut(&id) { + // If the bucet exists, try to take a token. + Some(balance) => match balance.checked_sub(1) { + Some(a) => { + *balance = a; + true + } + None => false, + }, + // If the bucket is missing, act like the bucket has `limit` number of tokens. Take one + // token and track the new bucket balance. + None => { + self.buckets.insert(id.clone(), self.limit - 1); + self.refill_schedule.push_back((now, id)); + true + } + } + } + + fn refill(&mut self, now: Instant) { + loop { + match self.refill_schedule.get(0) { + // Only continue if (a) there is a bucket and (b) the bucket has not already been + // refilled recently. + Some((last_refill, _)) if now.duration_since(*last_refill) >= self.interval => {} + // Otherwise stop refilling. + _ => return, + }; + + let (last_refill, id) = self + .refill_schedule + .pop_front() + .expect("Queue not to be empty."); + + // Get the current balance of the bucket. + let balance = self + .buckets + .get(&id) + .expect("Entry can only be removed via refill."); + + // Calculate the new balance. + let duration_since = now.duration_since(last_refill); + let new_tokens = duration_since + .as_micros() + .checked_div(self.interval.as_micros()) + .and_then(|i| i.try_into().ok()) + .unwrap_or(u32::MAX); + let new_balance = balance.checked_add(new_tokens).unwrap_or(u32::MAX); + + // If the new balance is below the limit, update the bucket. + if new_balance < self.limit { + self.buckets + .insert(id.clone(), new_balance) + .expect("To override value."); + self.refill_schedule.push_back((now, id)); + } else { + // If the balance is above the limit, the bucket can be removed, given that a + // non-existing bucket is seen as a bucket with `limit` tokens. + self.buckets.remove(&id); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::{QuickCheck, TestResult}; + use std::num::NonZeroU32; + + #[test] + fn first() { + let id = 1; + let mut l = RateLimiter::new(Config { + limit: NonZeroU32::new(10).unwrap(), + interval: Duration::from_secs(1), + }); + assert!(l.try_next(id, Instant::now())); + } + + #[test] + fn limits() { + let id = 1; + let now = Instant::now(); + let mut l = RateLimiter::new(Config { + limit: NonZeroU32::new(10).unwrap(), + interval: Duration::from_secs(1), + }); + for _ in 0..10 { + assert!(l.try_next(id, now)); + } + + assert!(!l.try_next(id, now)); + } + + #[test] + fn refills() { + let id = 1; + let now = Instant::now(); + let mut l = RateLimiter::new(Config { + limit: NonZeroU32::new(10).unwrap(), + interval: Duration::from_secs(1), + }); + + for _ in 0..10 { + assert!(l.try_next(id, now)); + } + assert!(!l.try_next(id, now)); + + let now = now + Duration::from_secs(1); + assert!(l.try_next(id, now)); + assert!(!l.try_next(id, now)); + + let now = now + Duration::from_secs(10); + for _ in 0..10 { + assert!(l.try_next(id, now)); + } + } + + #[test] + fn move_at_half_interval_steps() { + let id = 1; + let now = Instant::now(); + let mut l = RateLimiter::new(Config { + limit: NonZeroU32::new(1).unwrap(), + interval: Duration::from_secs(2), + }); + + assert!(l.try_next(id, now)); + assert!(!l.try_next(id, now)); + + let now = now + Duration::from_secs(1); + assert!(!l.try_next(id, now)); + + let now = now + Duration::from_secs(1); + assert!(l.try_next(id, now)); + } + + #[test] + fn garbage_collects() { + let now = Instant::now(); + let mut l = RateLimiter::new(Config { + limit: NonZeroU32::new(1).unwrap(), + interval: Duration::from_secs(1), + }); + + assert!(l.try_next(1, now)); + + let now = now + Duration::from_secs(1); + assert!(l.try_next(2, now)); + + assert_eq!(l.buckets.len(), 1); + assert_eq!(l.refill_schedule.len(), 1); + } + + #[test] + fn quick_check() { + fn prop( + limit: NonZeroU32, + interval: Duration, + events: Vec<(u32, Duration)>, + ) -> TestResult { + if interval.is_zero() { + return TestResult::discard(); + } + + let mut now = Instant::now(); + let mut l = RateLimiter::new(Config { + limit: limit.try_into().unwrap(), + interval, + }); + + for (id, d) in events { + now = now + d; + l.try_next(id, now); + } + + // TODO: This might panic. + now = now + interval * limit.into(); + assert!(l.try_next(1, now)); + + assert_eq!(l.buckets.len(), 1); + assert_eq!(l.refill_schedule.len(), 1); + + TestResult::passed() + } + + QuickCheck::new().quickcheck(prop as fn(_, _, _) -> _) + } +} From fdacc1bf5bc2d4b826bce57c2e209b613e3d2667 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 21 Jul 2021 16:08:44 +0200 Subject: [PATCH 026/108] protocols/relay/v2: Document caveats on rate limiter with high volume --- protocols/relay/src/v2/relay/rate_limiter.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/protocols/relay/src/v2/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs index 06a7610dd07..0bfac5b0fe3 100644 --- a/protocols/relay/src/v2/relay/rate_limiter.rs +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -84,12 +84,15 @@ impl RateLimiter { } fn refill(&mut self, now: Instant) { + // Note when used with a high number of buckets: This loop refills all the to-be-refilled + // buckets at once, thus potentially delaying the parent call to `try_next`. loop { match self.refill_schedule.get(0) { // Only continue if (a) there is a bucket and (b) the bucket has not already been // refilled recently. Some((last_refill, _)) if now.duration_since(*last_refill) >= self.interval => {} - // Otherwise stop refilling. + // Otherwise stop refilling. Items in `refill_schedule` are sorted, thus, if the + // first ain't ready, none of them are. _ => return, }; @@ -108,6 +111,7 @@ impl RateLimiter { let duration_since = now.duration_since(last_refill); let new_tokens = duration_since .as_micros() + // Note that the use of `as_micros` limits the number of tokens to 10^6 per second. .checked_div(self.interval.as_micros()) .and_then(|i| i.try_into().ok()) .unwrap_or(u32::MAX); @@ -121,7 +125,7 @@ impl RateLimiter { self.refill_schedule.push_back((now, id)); } else { // If the balance is above the limit, the bucket can be removed, given that a - // non-existing bucket is seen as a bucket with `limit` tokens. + // non-existing bucket is equivalent to a bucket with `limit` tokens. self.buckets.remove(&id); } } From 93f0979f272f245edd0703f54b0ef5f28dfa7918 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 21 Jul 2021 16:19:12 +0200 Subject: [PATCH 027/108] protocols/relay: Prevent possible false positive in quickcheck --- protocols/relay/src/v2/relay/rate_limiter.rs | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/protocols/relay/src/v2/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs index 0bfac5b0fe3..50ae0fb67e6 100644 --- a/protocols/relay/src/v2/relay/rate_limiter.rs +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -225,11 +225,7 @@ mod tests { #[test] fn quick_check() { - fn prop( - limit: NonZeroU32, - interval: Duration, - events: Vec<(u32, Duration)>, - ) -> TestResult { + fn prop(limit: NonZeroU32, interval: Duration, events: Vec<(u32, Duration)>) -> TestResult { if interval.is_zero() { return TestResult::discard(); } @@ -241,12 +237,22 @@ mod tests { }); for (id, d) in events { - now = now + d; + now = if let Some(now) = now.checked_add(d) { + now + } else { + return TestResult::discard(); + }; l.try_next(id, now); } - // TODO: This might panic. - now = now + interval * limit.into(); + now = if let Some(now) = interval + .checked_mul(limit.into()) + .and_then(|full_interval| now.checked_add(full_interval)) + { + now + } else { + return TestResult::discard(); + }; assert!(l.try_next(1, now)); assert_eq!(l.buckets.len(), 1); From 334ffc6f264825a7bf393a4e1318c101cc8ca058 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 21 Jul 2021 16:22:48 +0200 Subject: [PATCH 028/108] protocols/relay: Reword Prost error message --- protocols/relay/src/v2/protocol/inbound_hop.rs | 4 ++-- protocols/relay/src/v2/protocol/inbound_stop.rs | 2 +- protocols/relay/src/v2/protocol/outbound_hop.rs | 2 +- protocols/relay/src/v2/protocol/outbound_stop.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 66b94d3fef9..a3374eba5d5 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -208,7 +208,7 @@ impl ReservationReq { async fn send(mut self, msg: HopMessage) -> Result<(), std::io::Error> { let mut msg_bytes = BytesMut::new(); msg.encode(&mut msg_bytes) - .expect("all the mandatory fields are always filled; QED"); + .expect("BytesMut to have sufficient capacity."); self.substream.send(msg_bytes.freeze()).await?; self.substream.flush().await?; self.substream.close().await?; @@ -267,7 +267,7 @@ impl CircuitReq { async fn send(&mut self, msg: HopMessage) -> Result<(), std::io::Error> { let mut msg_bytes = BytesMut::new(); msg.encode(&mut msg_bytes) - .expect("all the mandatory fields are always filled; QED"); + .expect("BytesMut to have sufficient capacity."); self.substream.send(msg_bytes.freeze()).await?; self.substream.flush().await?; diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 318602ceea8..c57af9a0cbd 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -191,7 +191,7 @@ impl Circuit { async fn send(&mut self, msg: StopMessage) -> Result<(), std::io::Error> { let mut msg_bytes = BytesMut::new(); msg.encode(&mut msg_bytes) - .expect("all the mandatory fields are always filled; QED"); + .expect("BytesMut to have sufficient capacity."); self.substream.send(msg_bytes.freeze()).await?; self.substream.flush().await?; diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index 734d4380fd2..e8cd044c31c 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -75,7 +75,7 @@ impl upgrade::OutboundUpgrade for Upgrade { let mut encoded_msg = Vec::new(); msg.encode(&mut encoded_msg) - .expect("all the mandatory fields are always filled; QED"); + .expect("Vec to have sufficient capacity."); let mut codec = UviBytes::default(); codec.set_max_len(MAX_MESSAGE_SIZE); diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs index 686546a2bfe..78406c9bf63 100644 --- a/protocols/relay/src/v2/protocol/outbound_stop.rs +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -73,7 +73,7 @@ impl upgrade::OutboundUpgrade for Upgrade { let mut encoded_msg = Vec::new(); msg.encode(&mut encoded_msg) - .expect("all the mandatory fields are always filled; QED"); + .expect("Vec to have sufficient capacity."); let mut codec = UviBytes::default(); codec.set_max_len(MAX_MESSAGE_SIZE); From 8803bd0aff4b26fcd4ece98582855731418d4ee8 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 21 Jul 2021 19:14:06 +0200 Subject: [PATCH 029/108] protocols/relay: Allow users to specify generic rate limiters --- protocols/relay/src/v2/client/transport.rs | 2 +- protocols/relay/src/v2/relay.rs | 84 +++++++++++++++----- protocols/relay/src/v2/relay/handler.rs | 7 +- protocols/relay/src/v2/relay/rate_limiter.rs | 4 +- 4 files changed, 75 insertions(+), 22 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 5df27e6e805..f46bdfa0f43 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -396,7 +396,7 @@ impl Stream for RelayListener { } Poll::Ready(None) => { // TODO: Assumption safe here? - panic!("Expect `from_behaviour` not to be dropped before listener."); + panic!("Expect sender of `from_behaviour` not to be dropped before listener."); }, Poll::Pending => {} diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 035d2c719a9..cdaf02ac457 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -23,7 +23,7 @@ mod handler; mod rate_limiter; -pub use rate_limiter::Config as RateLimiterConfig; +pub use rate_limiter::{Config as GenericRateLimiterConfig, RateLimiter as GenericRateLimiter}; use crate::v2::message_proto; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; @@ -31,10 +31,11 @@ use libp2p_core::multiaddr::Protocol; use libp2p_core::{Multiaddr, PeerId}; use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; use std::collections::{HashMap, HashSet, VecDeque}; +use std::net::IpAddr; +use std::num::NonZeroU32; use std::ops::Add; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; -use std::num::NonZeroU32; /// Configuration for the [`Relay`] [`NetworkBehaviour`]. /// @@ -45,9 +46,8 @@ pub struct Config { pub max_reservations: usize, pub reservation_duration: Duration, - pub max_reservations_per_peer: RateLimiterConfig, - // pub max_reservations_per_ip: u32, - // pub max_reservations_per_asn: u32, + // TODO: Mutable state in a config. Good idea? + pub reservation_rate_limiters: Vec>, pub max_circuits: usize, pub max_circuit_duration: Duration, @@ -56,17 +56,26 @@ pub struct Config { impl Default for Config { fn default() -> Self { + let reservation_rate_limiters = { + vec![ + new_per_peer(GenericRateLimiterConfig { + // TODO: Made up values. Reconsider these. + limit: NonZeroU32::new(128).expect("128 > 0"), + interval: Duration::from_secs(60), + }), + new_per_ip(GenericRateLimiterConfig { + // TODO: Made up values. Reconsider these. + limit: NonZeroU32::new(128).expect("128 > 0"), + interval: Duration::from_secs(60), + }), + ] + }; + Config { max_reservations: 128, reservation_duration: Duration::from_secs(60 * 60), - max_reservations_per_peer: RateLimiterConfig { - // TODO: Made up values. Reconsider these. - limit: NonZeroU32::new(128).expect("128 > 0"), - interval: Duration::from_secs(60), - }, - // max_reservations_per_ip: 4, - // max_reservations_per_asn: 32, + reservation_rate_limiters, // TODO: Shouldn't we have a limit per IP and ASN as well? max_circuits: 16, @@ -139,10 +148,6 @@ pub struct Relay { reservations: HashMap>, circuits: CircuitsTracker, - peer_rate_limiter: rate_limiter::RateLimiter, - // ip_rate_limiter: rate_limiter::RateLimiter, - // asn_rate_limiter: rate_limiter::RateLimiter, - /// Queue of actions to return when polled. queued_actions: VecDeque>, } @@ -150,7 +155,6 @@ pub struct Relay { impl Relay { pub fn new(local_peer_id: PeerId, config: Config) -> Self { Self { - peer_rate_limiter: rate_limiter::RateLimiter::new(config.max_reservations_per_peer), config, local_peer_id, reservations: Default::default(), @@ -218,16 +222,24 @@ impl NetworkBehaviour for Relay { event: handler::Event, ) { match event { + // TODO: Make sure this is not a relayed connection already. handler::Event::ReservationReqReceived { inbound_reservation_req, + remote_addr, } => { + let now = Instant::now(); + let event = if self .reservations .iter() .map(|(_, cs)| cs.len()) .sum::() < self.config.max_reservations - && self.peer_rate_limiter.try_next(event_source, Instant::now()) + && self + .config + .reservation_rate_limiters + .iter_mut() + .all(|limiter| limiter.try_next(event_source, &remote_addr, now)) { self.reservations .entry(event_source) @@ -238,6 +250,7 @@ impl NetworkBehaviour for Relay { addrs: vec![], } } else { + println!("===== else "); handler::In::DenyReservationReq { inbound_reservation_req, status: message_proto::Status::ResourceLimitExceeded, @@ -306,6 +319,7 @@ impl NetworkBehaviour for Relay { )); } handler::Event::CircuitReqReceived(inbound_circuit_req) => { + // TODO: Make sure this is not a relayed connection already. if self.circuits.len() >= self.config.max_circuits { self.queued_actions .push_back(NetworkBehaviourAction::NotifyHandler { @@ -591,3 +605,37 @@ impl Add for CircuitId { CircuitId(self.0 + rhs) } } + +fn multiaddr_to_ip(addr: &Multiaddr) -> Option { + addr.iter().find_map(|p| match p { + Protocol::Ip4(addr) => Some(addr.into()), + Protocol::Ip6(addr) => Some(addr.into()), + _ => None, + }) +} + +// TODO: Should this and the below be moved to its own module, or rate_limiter.rs? +pub trait RateLimiter: Send { + fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool; + +} + +fn new_per_peer(config: GenericRateLimiterConfig) -> Box { + let mut limiter = GenericRateLimiter::new(config); + Box::new(move |peer_id, _addr: &Multiaddr, now| limiter.try_next(peer_id, now)) +} + +fn new_per_ip(config: GenericRateLimiterConfig) -> Box { + let mut limiter = GenericRateLimiter::new(config); + Box::new(move |_peer_id, addr: &Multiaddr, now| { + multiaddr_to_ip(addr) + .map(|a| limiter.try_next(a, now)) + .unwrap_or(true) + }) +} + +impl bool + Send> RateLimiter for T { + fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool { + self(peer, addr, now) + } +} diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index d68089ff4db..52e2b73f6b9 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -82,6 +82,7 @@ pub enum Event { /// An inbound reservation request has been received. ReservationReqReceived { inbound_reservation_req: inbound_hop::ReservationReq, + remote_addr: Multiaddr, }, /// An inbound reservation request has been accepted. ReservationReqAccepted { @@ -154,8 +155,9 @@ pub struct Prototype { impl IntoProtocolsHandler for Prototype { type Handler = Handler; - fn into_handler(self, _remote_peer_id: &PeerId, _endpoint: &ConnectedPoint) -> Self::Handler { + fn into_handler(self, _remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { Handler { + remote_addr: endpoint.get_remote_address().clone(), config: self.config, queued_events: Default::default(), pending_error: Default::default(), @@ -182,6 +184,8 @@ impl IntoProtocolsHandler for Prototype { /// [`ProtocolsHandler`] that manages substreams for a relay on a single /// connection with a peer. pub struct Handler { + remote_addr: Multiaddr, + /// Static [`Handler`] [`Config`]. config: Config, @@ -263,6 +267,7 @@ impl ProtocolsHandler for Handler { self.queued_events.push_back(ProtocolsHandlerEvent::Custom( Event::ReservationReqReceived { inbound_reservation_req, + remote_addr: self.remote_addr.clone(), }, )); } diff --git a/protocols/relay/src/v2/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs index 50ae0fb67e6..1e2b982e2f8 100644 --- a/protocols/relay/src/v2/relay/rate_limiter.rs +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -32,7 +32,7 @@ use std::time::{Duration, Instant}; /// Rate limiter using the [Token Bucket] algorithm. /// /// [Token Bucket]: https://en.wikipedia.org/wiki/Token_bucket -pub(crate) struct RateLimiter { +pub struct RateLimiter { limit: u32, interval: Duration, @@ -65,7 +65,7 @@ impl RateLimiter { self.refill(now); match self.buckets.get_mut(&id) { - // If the bucet exists, try to take a token. + // If the bucket exists, try to take a token. Some(balance) => match balance.checked_sub(1) { Some(a) => { *balance = a; From c51f9e2dff1d1f9205a44f7c38bfb336be142612 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 13:30:33 +0200 Subject: [PATCH 030/108] protocols/relay: Move rate limiting logic into module --- protocols/relay/src/v2/relay/rate_limiter.rs | 442 ++++++++++--------- 1 file changed, 245 insertions(+), 197 deletions(-) diff --git a/protocols/relay/src/v2/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs index 1e2b982e2f8..c4c0634e03d 100644 --- a/protocols/relay/src/v2/relay/rate_limiter.rs +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -18,249 +18,297 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -// TODO: Rate limit per (in that order): -// - PeerId -// - IP -// - ASN - -use std::collections::{HashMap, VecDeque}; -use std::convert::TryInto; -use std::hash::Hash; -use std::num::NonZeroU32; -use std::time::{Duration, Instant}; - -/// Rate limiter using the [Token Bucket] algorithm. +pub use generic::{ + RateLimiter as GenericRateLimiter, RateLimiterConfig as GenericRateLimiterConfig, +}; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use libp2p_core::PeerId; +use std::net::IpAddr; +use std::time::Instant; + +/// Allows rate limiting access to some resource based on the [`PeerId`] and +/// [`Multiaddr`] of a remote peer. /// -/// [Token Bucket]: https://en.wikipedia.org/wiki/Token_bucket -pub struct RateLimiter { - limit: u32, - interval: Duration, +/// See [`new_per_peer`] and [`new_per_ip`] for precast implementations. Use +/// [`GenericRateLimiter`] to build your own, e.g. based on the atonomous system +/// number of a peers IP address. +pub trait RateLimiter: Send { + fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool; +} - refill_schedule: VecDeque<(Instant, Id)>, - buckets: HashMap, +pub fn new_per_peer(config: GenericRateLimiterConfig) -> Box { + let mut limiter = GenericRateLimiter::new(config); + Box::new(move |peer_id, _addr: &Multiaddr, now| limiter.try_next(peer_id, now)) } -/// Configuration for a [`RateLimiter`]. -#[derive(Clone, Copy)] -pub struct Config { - /// The maximum number of tokens in the bucket at any point in time. - pub limit: NonZeroU32, - /// The interval at which a single token is added to the bucket. - pub interval: Duration, +pub fn new_per_ip(config: GenericRateLimiterConfig) -> Box { + let mut limiter = GenericRateLimiter::new(config); + Box::new(move |_peer_id, addr: &Multiaddr, now| { + multiaddr_to_ip(addr) + .map(|a| limiter.try_next(a, now)) + .unwrap_or(true) + }) } -impl RateLimiter { - pub(crate) fn new(config: Config) -> Self { - assert!(!config.interval.is_zero()); +impl bool + Send> RateLimiter for T { + fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool { + self(peer, addr, now) + } +} - Self { - limit: config.limit.into(), - interval: config.interval, - refill_schedule: Default::default(), - buckets: Default::default(), - } +fn multiaddr_to_ip(addr: &Multiaddr) -> Option { + addr.iter().find_map(|p| match p { + Protocol::Ip4(addr) => Some(addr.into()), + Protocol::Ip6(addr) => Some(addr.into()), + _ => None, + }) +} + +mod generic { + use std::collections::{HashMap, VecDeque}; + use std::convert::TryInto; + use std::hash::Hash; + use std::num::NonZeroU32; + use std::time::{Duration, Instant}; + + /// Rate limiter using the [Token Bucket] algorithm. + /// + /// [Token Bucket]: https://en.wikipedia.org/wiki/Token_bucket + pub struct RateLimiter { + limit: u32, + interval: Duration, + + refill_schedule: VecDeque<(Instant, Id)>, + buckets: HashMap, + } + + /// Configuration for a [`RateLimiter`]. + #[derive(Clone, Copy)] + pub struct RateLimiterConfig { + /// The maximum number of tokens in the bucket at any point in time. + pub limit: NonZeroU32, + /// The interval at which a single token is added to the bucket. + pub interval: Duration, } - pub(crate) fn try_next(&mut self, id: Id, now: Instant) -> bool { - self.refill(now); + impl RateLimiter { + pub(crate) fn new(config: RateLimiterConfig) -> Self { + assert!(!config.interval.is_zero()); - match self.buckets.get_mut(&id) { - // If the bucket exists, try to take a token. - Some(balance) => match balance.checked_sub(1) { - Some(a) => { - *balance = a; + Self { + limit: config.limit.into(), + interval: config.interval, + refill_schedule: Default::default(), + buckets: Default::default(), + } + } + + pub(crate) fn try_next(&mut self, id: Id, now: Instant) -> bool { + self.refill(now); + + match self.buckets.get_mut(&id) { + // If the bucket exists, try to take a token. + Some(balance) => match balance.checked_sub(1) { + Some(a) => { + *balance = a; + true + } + None => false, + }, + // If the bucket is missing, act like the bucket has `limit` number of tokens. Take one + // token and track the new bucket balance. + None => { + self.buckets.insert(id.clone(), self.limit - 1); + self.refill_schedule.push_back((now, id)); true } - None => false, - }, - // If the bucket is missing, act like the bucket has `limit` number of tokens. Take one - // token and track the new bucket balance. - None => { - self.buckets.insert(id.clone(), self.limit - 1); - self.refill_schedule.push_back((now, id)); - true } } - } - fn refill(&mut self, now: Instant) { - // Note when used with a high number of buckets: This loop refills all the to-be-refilled - // buckets at once, thus potentially delaying the parent call to `try_next`. - loop { - match self.refill_schedule.get(0) { - // Only continue if (a) there is a bucket and (b) the bucket has not already been - // refilled recently. - Some((last_refill, _)) if now.duration_since(*last_refill) >= self.interval => {} - // Otherwise stop refilling. Items in `refill_schedule` are sorted, thus, if the - // first ain't ready, none of them are. - _ => return, - }; - - let (last_refill, id) = self - .refill_schedule - .pop_front() - .expect("Queue not to be empty."); - - // Get the current balance of the bucket. - let balance = self - .buckets - .get(&id) - .expect("Entry can only be removed via refill."); - - // Calculate the new balance. - let duration_since = now.duration_since(last_refill); - let new_tokens = duration_since - .as_micros() - // Note that the use of `as_micros` limits the number of tokens to 10^6 per second. - .checked_div(self.interval.as_micros()) - .and_then(|i| i.try_into().ok()) - .unwrap_or(u32::MAX); - let new_balance = balance.checked_add(new_tokens).unwrap_or(u32::MAX); - - // If the new balance is below the limit, update the bucket. - if new_balance < self.limit { - self.buckets - .insert(id.clone(), new_balance) - .expect("To override value."); - self.refill_schedule.push_back((now, id)); - } else { - // If the balance is above the limit, the bucket can be removed, given that a - // non-existing bucket is equivalent to a bucket with `limit` tokens. - self.buckets.remove(&id); + fn refill(&mut self, now: Instant) { + // Note when used with a high number of buckets: This loop refills all the to-be-refilled + // buckets at once, thus potentially delaying the parent call to `try_next`. + loop { + match self.refill_schedule.get(0) { + // Only continue if (a) there is a bucket and (b) the bucket has not already been + // refilled recently. + Some((last_refill, _)) if now.duration_since(*last_refill) >= self.interval => { + } + // Otherwise stop refilling. Items in `refill_schedule` are sorted, thus, if the + // first ain't ready, none of them are. + _ => return, + }; + + let (last_refill, id) = self + .refill_schedule + .pop_front() + .expect("Queue not to be empty."); + + // Get the current balance of the bucket. + let balance = self + .buckets + .get(&id) + .expect("Entry can only be removed via refill."); + + // Calculate the new balance. + let duration_since = now.duration_since(last_refill); + let new_tokens = duration_since + .as_micros() + // Note that the use of `as_micros` limits the number of tokens to 10^6 per second. + .checked_div(self.interval.as_micros()) + .and_then(|i| i.try_into().ok()) + .unwrap_or(u32::MAX); + let new_balance = balance.checked_add(new_tokens).unwrap_or(u32::MAX); + + // If the new balance is below the limit, update the bucket. + if new_balance < self.limit { + self.buckets + .insert(id.clone(), new_balance) + .expect("To override value."); + self.refill_schedule.push_back((now, id)); + } else { + // If the balance is above the limit, the bucket can be removed, given that a + // non-existing bucket is equivalent to a bucket with `limit` tokens. + self.buckets.remove(&id); + } } } } -} -#[cfg(test)] -mod tests { - use super::*; - use quickcheck::{QuickCheck, TestResult}; - use std::num::NonZeroU32; + #[cfg(test)] + mod tests { + use super::*; + use quickcheck::{QuickCheck, TestResult}; + use std::num::NonZeroU32; + + #[test] + fn first() { + let id = 1; + let mut l = RateLimiter::new(RateLimiterConfig { + limit: NonZeroU32::new(10).unwrap(), + interval: Duration::from_secs(1), + }); + assert!(l.try_next(id, Instant::now())); + } - #[test] - fn first() { - let id = 1; - let mut l = RateLimiter::new(Config { - limit: NonZeroU32::new(10).unwrap(), - interval: Duration::from_secs(1), - }); - assert!(l.try_next(id, Instant::now())); - } + #[test] + fn limits() { + let id = 1; + let now = Instant::now(); + let mut l = RateLimiter::new(RateLimiterConfig { + limit: NonZeroU32::new(10).unwrap(), + interval: Duration::from_secs(1), + }); + for _ in 0..10 { + assert!(l.try_next(id, now)); + } - #[test] - fn limits() { - let id = 1; - let now = Instant::now(); - let mut l = RateLimiter::new(Config { - limit: NonZeroU32::new(10).unwrap(), - interval: Duration::from_secs(1), - }); - for _ in 0..10 { - assert!(l.try_next(id, now)); + assert!(!l.try_next(id, now)); } - assert!(!l.try_next(id, now)); - } + #[test] + fn refills() { + let id = 1; + let now = Instant::now(); + let mut l = RateLimiter::new(RateLimiterConfig { + limit: NonZeroU32::new(10).unwrap(), + interval: Duration::from_secs(1), + }); - #[test] - fn refills() { - let id = 1; - let now = Instant::now(); - let mut l = RateLimiter::new(Config { - limit: NonZeroU32::new(10).unwrap(), - interval: Duration::from_secs(1), - }); + for _ in 0..10 { + assert!(l.try_next(id, now)); + } + assert!(!l.try_next(id, now)); - for _ in 0..10 { + let now = now + Duration::from_secs(1); assert!(l.try_next(id, now)); - } - assert!(!l.try_next(id, now)); + assert!(!l.try_next(id, now)); - let now = now + Duration::from_secs(1); - assert!(l.try_next(id, now)); - assert!(!l.try_next(id, now)); - - let now = now + Duration::from_secs(10); - for _ in 0..10 { - assert!(l.try_next(id, now)); + let now = now + Duration::from_secs(10); + for _ in 0..10 { + assert!(l.try_next(id, now)); + } } - } - #[test] - fn move_at_half_interval_steps() { - let id = 1; - let now = Instant::now(); - let mut l = RateLimiter::new(Config { - limit: NonZeroU32::new(1).unwrap(), - interval: Duration::from_secs(2), - }); + #[test] + fn move_at_half_interval_steps() { + let id = 1; + let now = Instant::now(); + let mut l = RateLimiter::new(RateLimiterConfig { + limit: NonZeroU32::new(1).unwrap(), + interval: Duration::from_secs(2), + }); - assert!(l.try_next(id, now)); - assert!(!l.try_next(id, now)); + assert!(l.try_next(id, now)); + assert!(!l.try_next(id, now)); - let now = now + Duration::from_secs(1); - assert!(!l.try_next(id, now)); + let now = now + Duration::from_secs(1); + assert!(!l.try_next(id, now)); - let now = now + Duration::from_secs(1); - assert!(l.try_next(id, now)); - } + let now = now + Duration::from_secs(1); + assert!(l.try_next(id, now)); + } - #[test] - fn garbage_collects() { - let now = Instant::now(); - let mut l = RateLimiter::new(Config { - limit: NonZeroU32::new(1).unwrap(), - interval: Duration::from_secs(1), - }); + #[test] + fn garbage_collects() { + let now = Instant::now(); + let mut l = RateLimiter::new(RateLimiterConfig { + limit: NonZeroU32::new(1).unwrap(), + interval: Duration::from_secs(1), + }); - assert!(l.try_next(1, now)); + assert!(l.try_next(1, now)); - let now = now + Duration::from_secs(1); - assert!(l.try_next(2, now)); + let now = now + Duration::from_secs(1); + assert!(l.try_next(2, now)); - assert_eq!(l.buckets.len(), 1); - assert_eq!(l.refill_schedule.len(), 1); - } + assert_eq!(l.buckets.len(), 1); + assert_eq!(l.refill_schedule.len(), 1); + } - #[test] - fn quick_check() { - fn prop(limit: NonZeroU32, interval: Duration, events: Vec<(u32, Duration)>) -> TestResult { - if interval.is_zero() { - return TestResult::discard(); - } + #[test] + fn quick_check() { + fn prop( + limit: NonZeroU32, + interval: Duration, + events: Vec<(u32, Duration)>, + ) -> TestResult { + if interval.is_zero() { + return TestResult::discard(); + } - let mut now = Instant::now(); - let mut l = RateLimiter::new(Config { - limit: limit.try_into().unwrap(), - interval, - }); + let mut now = Instant::now(); + let mut l = RateLimiter::new(RateLimiterConfig { + limit: limit.try_into().unwrap(), + interval, + }); + + for (id, d) in events { + now = if let Some(now) = now.checked_add(d) { + now + } else { + return TestResult::discard(); + }; + l.try_next(id, now); + } - for (id, d) in events { - now = if let Some(now) = now.checked_add(d) { + now = if let Some(now) = interval + .checked_mul(limit.into()) + .and_then(|full_interval| now.checked_add(full_interval)) + { now } else { return TestResult::discard(); }; - l.try_next(id, now); - } + assert!(l.try_next(1, now)); - now = if let Some(now) = interval - .checked_mul(limit.into()) - .and_then(|full_interval| now.checked_add(full_interval)) - { - now - } else { - return TestResult::discard(); - }; - assert!(l.try_next(1, now)); + assert_eq!(l.buckets.len(), 1); + assert_eq!(l.refill_schedule.len(), 1); - assert_eq!(l.buckets.len(), 1); - assert_eq!(l.refill_schedule.len(), 1); + TestResult::passed() + } - TestResult::passed() + QuickCheck::new().quickcheck(prop as fn(_, _, _) -> _) } - - QuickCheck::new().quickcheck(prop as fn(_, _, _) -> _) } } From 8777d7063f5286e788e515be71ea208e8aca01c4 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 13:30:52 +0200 Subject: [PATCH 031/108] protocols/relay: Prevent reservation and connection over relayed conn --- protocols/relay/src/v2/relay.rs | 184 +++++++++++------------- protocols/relay/src/v2/relay/handler.rs | 12 +- 2 files changed, 93 insertions(+), 103 deletions(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index cdaf02ac457..563b11b6847 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -21,9 +21,7 @@ //! [`NetworkBehaviour`] to act as a circuit relay v2 **relay**. mod handler; -mod rate_limiter; - -pub use rate_limiter::{Config as GenericRateLimiterConfig, RateLimiter as GenericRateLimiter}; +pub mod rate_limiter; use crate::v2::message_proto; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; @@ -31,7 +29,6 @@ use libp2p_core::multiaddr::Protocol; use libp2p_core::{Multiaddr, PeerId}; use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; use std::collections::{HashMap, HashSet, VecDeque}; -use std::net::IpAddr; use std::num::NonZeroU32; use std::ops::Add; use std::task::{Context, Poll}; @@ -47,7 +44,7 @@ pub struct Config { pub reservation_duration: Duration, // TODO: Mutable state in a config. Good idea? - pub reservation_rate_limiters: Vec>, + pub reservation_rate_limiters: Vec>, pub max_circuits: usize, pub max_circuit_duration: Duration, @@ -56,20 +53,18 @@ pub struct Config { impl Default for Config { fn default() -> Self { - let reservation_rate_limiters = { - vec![ - new_per_peer(GenericRateLimiterConfig { - // TODO: Made up values. Reconsider these. - limit: NonZeroU32::new(128).expect("128 > 0"), - interval: Duration::from_secs(60), - }), - new_per_ip(GenericRateLimiterConfig { - // TODO: Made up values. Reconsider these. - limit: NonZeroU32::new(128).expect("128 > 0"), - interval: Duration::from_secs(60), - }), - ] - }; + let reservation_rate_limiters = vec![ + rate_limiter::new_per_peer(rate_limiter::GenericRateLimiterConfig { + // TODO: Made up values. Reconsider these. + limit: NonZeroU32::new(128).expect("128 > 0"), + interval: Duration::from_secs(60), + }), + rate_limiter::new_per_ip(rate_limiter::GenericRateLimiterConfig { + // TODO: Made up values. Reconsider these. + limit: NonZeroU32::new(128).expect("128 > 0"), + interval: Duration::from_secs(60), + }), + ]; Config { max_reservations: 128, @@ -229,18 +224,31 @@ impl NetworkBehaviour for Relay { } => { let now = Instant::now(); - let event = if self + let handler_event = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { + // Deny reservation requests over relayed connections. + handler::In::DenyReservationReq { + inbound_reservation_req, + status: message_proto::Status::PermissionDenied, + } + } else if self .reservations .iter() .map(|(_, cs)| cs.len()) .sum::() - < self.config.max_reservations - && self + >= self.config.max_reservations + || !self .config .reservation_rate_limiters .iter_mut() .all(|limiter| limiter.try_next(event_source, &remote_addr, now)) { + // Deny reservation exceeding limits. + handler::In::DenyReservationReq { + inbound_reservation_req, + status: message_proto::Status::ResourceLimitExceeded, + } + } else { + // Accept reservation. self.reservations .entry(event_source) .or_default() @@ -249,20 +257,14 @@ impl NetworkBehaviour for Relay { inbound_reservation_req, addrs: vec![], } - } else { - println!("===== else "); - handler::In::DenyReservationReq { - inbound_reservation_req, - status: message_proto::Status::ResourceLimitExceeded, - } }; self.queued_actions .push_back(NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, - event, - }) + event: handler_event, + }); } handler::Event::ReservationReqAccepted { renewed } => { // Ensure local eventual consistent reservation state matches handler (source of @@ -318,19 +320,34 @@ impl NetworkBehaviour for Relay { }, )); } - handler::Event::CircuitReqReceived(inbound_circuit_req) => { - // TODO: Make sure this is not a relayed connection already. - if self.circuits.len() >= self.config.max_circuits { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - handler: NotifyHandler::One(connection), - peer_id: event_source, - event: handler::In::DenyCircuitReq { - circuit_id: None, - inbound_circuit_req, - status: message_proto::Status::ResourceLimitExceeded, - }, - }); + handler::Event::CircuitReqReceived { + inbound_circuit_req, + remote_addr, + } => { + let action = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { + // Deny connection requests over relayed connections. + // + // An attacker could otherwise build recursive or cyclic connections. + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: handler::In::DenyCircuitReq { + circuit_id: None, + inbound_circuit_req, + status: message_proto::Status::PermissionDenied, + }, + } + } else if self.circuits.len() >= self.config.max_circuits { + // Deny connection exceeding limits. + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: handler::In::DenyCircuitReq { + circuit_id: None, + inbound_circuit_req, + status: message_proto::Status::ResourceLimitExceeded, + }, + } } else if let Some(dst_conn) = self .reservations .get(&inbound_circuit_req.dst()) @@ -340,6 +357,7 @@ impl NetworkBehaviour for Relay { // TODO: Restrict the amount of circuits between two peers and one single // peer. + // Accept connection request if reservation present. let circuit_id = self.circuits.insert(Circuit { status: CircuitStatus::Accepting, src_peer_id: event_source, @@ -348,30 +366,30 @@ impl NetworkBehaviour for Relay { dst_connection_id: *dst_conn, }); - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - handler: NotifyHandler::One(*dst_conn), - peer_id: event_source, - event: handler::In::NegotiateOutboundConnect { - circuit_id, - inbound_circuit_req, - relay_peer_id: self.local_peer_id, - src_peer_id: event_source, - src_connection_id: connection, - }, - }); + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(*dst_conn), + peer_id: event_source, + event: handler::In::NegotiateOutboundConnect { + circuit_id, + inbound_circuit_req, + relay_peer_id: self.local_peer_id, + src_peer_id: event_source, + src_connection_id: connection, + }, + } } else { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - handler: NotifyHandler::One(connection), - peer_id: event_source, - event: handler::In::DenyCircuitReq { - circuit_id: None, - inbound_circuit_req, - status: message_proto::Status::NoReservation, - }, - }); - } + // Deny connection request if no reservation present. + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: handler::In::DenyCircuitReq { + circuit_id: None, + inbound_circuit_req, + status: message_proto::Status::NoReservation, + }, + } + }; + self.queued_actions.push_back(action); } handler::Event::CircuitReqDenied { circuit_id, @@ -605,37 +623,3 @@ impl Add for CircuitId { CircuitId(self.0 + rhs) } } - -fn multiaddr_to_ip(addr: &Multiaddr) -> Option { - addr.iter().find_map(|p| match p { - Protocol::Ip4(addr) => Some(addr.into()), - Protocol::Ip6(addr) => Some(addr.into()), - _ => None, - }) -} - -// TODO: Should this and the below be moved to its own module, or rate_limiter.rs? -pub trait RateLimiter: Send { - fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool; - -} - -fn new_per_peer(config: GenericRateLimiterConfig) -> Box { - let mut limiter = GenericRateLimiter::new(config); - Box::new(move |peer_id, _addr: &Multiaddr, now| limiter.try_next(peer_id, now)) -} - -fn new_per_ip(config: GenericRateLimiterConfig) -> Box { - let mut limiter = GenericRateLimiter::new(config); - Box::new(move |_peer_id, addr: &Multiaddr, now| { - multiaddr_to_ip(addr) - .map(|a| limiter.try_next(a, now)) - .unwrap_or(true) - }) -} - -impl bool + Send> RateLimiter for T { - fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool { - self(peer, addr, now) - } -} diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 52e2b73f6b9..bd7dc8918c3 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -98,7 +98,10 @@ pub enum Event { /// An inbound reservation has timed out. ReservationTimedOut {}, /// An inbound circuit request has been received. - CircuitReqReceived(inbound_hop::CircuitReq), + CircuitReqReceived { + inbound_circuit_req: inbound_hop::CircuitReq, + remote_addr: Multiaddr, + }, /// An inbound circuit request has been denied. CircuitReqDenied { circuit_id: Option, @@ -271,9 +274,12 @@ impl ProtocolsHandler for Handler { }, )); } - inbound_hop::Req::Connect(circuit_req) => { + inbound_hop::Req::Connect(inbound_circuit_req) => { self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - Event::CircuitReqReceived(circuit_req), + Event::CircuitReqReceived { + inbound_circuit_req, + remote_addr: self.remote_addr.clone(), + }, )); } } From 4e5f99d677a0a28654cec595ed051c5cb8d3b70f Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 18:07:32 +0200 Subject: [PATCH 032/108] protocols/relay: Add circuit src rate limiting --- protocols/relay/src/v2/client/transport.rs | 1 - protocols/relay/src/v2/relay.rs | 33 ++++++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index f46bdfa0f43..59116d1b881 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -395,7 +395,6 @@ impl Stream for RelayListener { return Poll::Ready(Some(Err(EitherError::B(RelayError::Reservation)))); } Poll::Ready(None) => { - // TODO: Assumption safe here? panic!("Expect sender of `from_behaviour` not to be dropped before listener."); }, diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 563b11b6847..8ebf6124f29 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -42,13 +42,14 @@ use std::time::{Duration, Instant}; pub struct Config { pub max_reservations: usize, pub reservation_duration: Duration, - // TODO: Mutable state in a config. Good idea? pub reservation_rate_limiters: Vec>, pub max_circuits: usize, pub max_circuit_duration: Duration, pub max_circuit_bytes: u64, + // TODO: Mutable state in a config. Good idea? + pub circuit_src_rate_limiters: Vec>, } impl Default for Config { @@ -66,16 +67,28 @@ impl Default for Config { }), ]; + let circuit_src_rate_limiters = vec![ + rate_limiter::new_per_peer(rate_limiter::GenericRateLimiterConfig { + // TODO: Made up values. Reconsider these. + limit: NonZeroU32::new(128).expect("128 > 0"), + interval: Duration::from_secs(60), + }), + rate_limiter::new_per_ip(rate_limiter::GenericRateLimiterConfig { + // TODO: Made up values. Reconsider these. + limit: NonZeroU32::new(128).expect("128 > 0"), + interval: Duration::from_secs(60), + }), + ]; + Config { max_reservations: 128, reservation_duration: Duration::from_secs(60 * 60), - reservation_rate_limiters, - // TODO: Shouldn't we have a limit per IP and ASN as well? max_circuits: 16, max_circuit_duration: Duration::from_secs(2 * 60), max_circuit_bytes: 1 << 17, // 128 kibibyte + circuit_src_rate_limiters, } } } @@ -217,7 +230,6 @@ impl NetworkBehaviour for Relay { event: handler::Event, ) { match event { - // TODO: Make sure this is not a relayed connection already. handler::Event::ReservationReqReceived { inbound_reservation_req, remote_addr, @@ -324,6 +336,8 @@ impl NetworkBehaviour for Relay { inbound_circuit_req, remote_addr, } => { + let now = Instant::now(); + let action = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { // Deny connection requests over relayed connections. // @@ -337,7 +351,13 @@ impl NetworkBehaviour for Relay { status: message_proto::Status::PermissionDenied, }, } - } else if self.circuits.len() >= self.config.max_circuits { + } else if self.circuits.len() >= self.config.max_circuits + || !self + .config + .circuit_src_rate_limiters + .iter_mut() + .all(|limiter| limiter.try_next(event_source, &remote_addr, now)) + { // Deny connection exceeding limits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), @@ -354,9 +374,6 @@ impl NetworkBehaviour for Relay { .map(|cs| cs.iter().next()) .flatten() { - // TODO: Restrict the amount of circuits between two peers and one single - // peer. - // Accept connection request if reservation present. let circuit_id = self.circuits.insert(Circuit { status: CircuitStatus::Accepting, From d55a5b997b38cd60c0c5f81354d25a0b85246429 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 18:22:10 +0200 Subject: [PATCH 033/108] protocols/relay/v2: Simplify example --- protocols/relay/examples/relay_v2.rs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/protocols/relay/examples/relay_v2.rs b/protocols/relay/examples/relay_v2.rs index d1ab14b2633..f414c1e754e 100644 --- a/protocols/relay/examples/relay_v2.rs +++ b/protocols/relay/examples/relay_v2.rs @@ -31,7 +31,6 @@ use libp2p::tcp::TcpConfig; use libp2p::Transport; use libp2p::{identity, NetworkBehaviour, PeerId}; use std::error::Error; -use std::task::{Context, Poll}; fn main() -> Result<(), Box> { env_logger::init(); @@ -99,26 +98,17 @@ fn main() -> Result<(), Box> { // Listen on all interfaces and whatever port the OS assigns swarm.listen_on("/ip4/0.0.0.0/tcp/4001".parse()?)?; - let mut listening = false; - block_on(futures::future::poll_fn(move |cx: &mut Context<'_>| { + block_on(async { loop { - match swarm.poll_next_unpin(cx) { - Poll::Ready(Some(SwarmEvent::Behaviour(Event::Relay(event)))) => { + match swarm.next().await.expect("Infinite Stream.") { + SwarmEvent::Behaviour(Event::Relay(event)) => { println!("{:?}", event) } - Poll::Ready(Some(_)) => {} - Poll::Ready(None) => return Poll::Ready(Ok(())), - Poll::Pending => { - if !listening { - for addr in Swarm::listeners(&swarm) { - println!("Listening on {:?}", addr); - listening = true; - } - } - break; + SwarmEvent::NewListenAddr { address, .. } => { + println!("Listening on {:?}", address); } + _ => {} } } - Poll::Pending - })) + }) } From e5f63bc63d137e0326ef1bfa0cdef94f5ad6b76a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 18:23:19 +0200 Subject: [PATCH 034/108] protocols/relay: Add myself to authors --- protocols/relay/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 43a293aa920..605d8711d77 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-relay" edition = "2018" description = "Communications relaying for libp2p" version = "0.3.0" -authors = ["Parity Technologies "] +authors = ["Parity Technologies ", "Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] From f8d00477548cb7356b083d290307ea19b0d2d844 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 19:10:09 +0200 Subject: [PATCH 035/108] protocols/relay: Use thiserror --- protocols/relay/Cargo.toml | 1 + protocols/relay/src/v2/client/transport.rs | 66 +++---------- .../relay/src/v2/protocol/inbound_hop.rs | 69 +++---------- .../relay/src/v2/protocol/inbound_stop.rs | 69 +++---------- .../relay/src/v2/protocol/outbound_hop.rs | 99 ++++--------------- .../relay/src/v2/protocol/outbound_stop.rs | 74 +++----------- 6 files changed, 82 insertions(+), 296 deletions(-) diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 605d8711d77..b00ef78394f 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -22,6 +22,7 @@ prost = "0.8" rand = "0.8.4" smallvec = "1.6.1" static_assertions = "1" +thiserror = "1.0" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1" wasm-timer = "0.2" diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 59116d1b881..6cc0c819dc6 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -34,6 +34,7 @@ use pin_project::pin_project; use std::collections::VecDeque; use std::pin::Pin; use std::task::{Context, Poll}; +use thiserror::Error; /// A [`Transport`] wrapping another [`Transport`] enabling client relay capabilities. /// @@ -439,17 +440,27 @@ impl Future for RelayedListenerUpgrade { } /// Error that occurred during relay connection setup. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum RelayError { + #[error("Missing relay peer id.")] MissingRelayPeerId, + #[error("Missing relay address.")] MissingRelayAddr, + #[error("Missing destination peer id.")] MissingDstPeerId, + #[error("Invalid peer id hash.")] InvalidHash, - SendingMessageToBehaviour(mpsc::SendError), - ResponseFromBehaviourCanceled, + #[error("Failed to send message to relay behaviour: {0:?}")] + SendingMessageToBehaviour(#[from] mpsc::SendError), + #[error("Response from behaviour was canceled")] + ResponseFromBehaviourCanceled(#[from] oneshot::Canceled), + #[error("Address contains multiple circuit relay protocols (`p2p-circuit`) which is not supported.")] MultipleCircuitRelayProtocolsUnsupported, + #[error("One of the provided multiaddresses is malformed.")] MalformedMultiaddr, + #[error("Failed to get Reservation.")] Reservation, + #[error("Failed to connect to destination.")] Connect, } @@ -459,56 +470,7 @@ impl From for TransportError> { } } -impl From for RelayError { - fn from(error: mpsc::SendError) -> Self { - RelayError::SendingMessageToBehaviour(error) - } -} - -impl From for RelayError { - fn from(_: oneshot::Canceled) -> Self { - RelayError::ResponseFromBehaviourCanceled - } -} - -impl std::fmt::Display for RelayError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RelayError::MissingRelayPeerId => { - write!(f, "Missing relay peer id.") - } - RelayError::MissingRelayAddr => { - write!(f, "Missing relay address.") - } - RelayError::MissingDstPeerId => { - write!(f, "Missing destination peer id.") - } - RelayError::InvalidHash => { - write!(f, "Invalid peer id hash.") - } - RelayError::SendingMessageToBehaviour(e) => { - write!(f, "Failed to send message to relay behaviour: {:?}", e) - } - RelayError::ResponseFromBehaviourCanceled => { - write!(f, "Response from behaviour was canceled") - } - RelayError::MultipleCircuitRelayProtocolsUnsupported => { - write!(f, "Address contains multiple circuit relay protocols (`p2p-circuit`) which is not supported.") - } - RelayError::MalformedMultiaddr => { - write!(f, "One of the provided multiaddresses is malformed.") - } - RelayError::Reservation => { - write!(f, "Failed to get Reservation.") - } - RelayError::Connect => { - write!(f, "Failed to connect to destination.") - } - } - } -} -impl std::error::Error for RelayError {} /// Message from the [`ClientTransport`] to the [`Relay`](crate::v2::Relay) /// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index a3374eba5d5..b4a595a2cde 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -28,8 +28,9 @@ use libp2p_swarm::NegotiatedSubstream; use prost::Message; use std::convert::TryInto; use std::io::Cursor; +use std::iter; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use std::{error, fmt, iter}; +use thiserror::Error; use unsigned_varint::codec::UviBytes; pub struct Upgrade { @@ -91,66 +92,26 @@ impl upgrade::InboundUpgrade for Upgrade { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum UpgradeError { - Decode(prost::DecodeError), - Io(std::io::Error), + #[error("Failed to decode message: {0}.")] + Decode( + #[from] + #[source] + prost::DecodeError, + ), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("Failed to parse response type field.")] ParseTypeField, + #[error("Failed to parse peer id.")] ParsePeerId, + #[error("Expected 'peer' field to be set.")] MissingPeer, + #[error("Unexpected message type 'status'")] UnexpectedTypeStatus, } -impl From for UpgradeError { - fn from(e: std::io::Error) -> Self { - UpgradeError::Io(e) - } -} - -impl From for UpgradeError { - fn from(e: prost::DecodeError) -> Self { - UpgradeError::Decode(e) - } -} - -impl fmt::Display for UpgradeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UpgradeError::Decode(e) => { - write!(f, "Failed to decode response: {}.", e) - } - UpgradeError::Io(e) => { - write!(f, "Io error {}", e) - } - UpgradeError::ParseTypeField => { - write!(f, "Failed to parse response type field.") - } - UpgradeError::ParsePeerId => { - write!(f, "Failed to parse peer id.") - } - UpgradeError::MissingPeer => { - write!(f, "Expected 'peer' field to be set.") - } - UpgradeError::UnexpectedTypeStatus => { - write!(f, "Unexpected message type 'status'") - } - } - } -} - -impl error::Error for UpgradeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - UpgradeError::Decode(e) => Some(e), - UpgradeError::Io(e) => Some(e), - UpgradeError::ParseTypeField => None, - UpgradeError::ParsePeerId => None, - UpgradeError::MissingPeer => None, - UpgradeError::UnexpectedTypeStatus => None, - } - } -} - pub enum Req { Reserve(ReservationReq), Connect(CircuitReq), diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index c57af9a0cbd..476fb603e18 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -27,7 +27,8 @@ use libp2p_core::{upgrade, PeerId}; use libp2p_swarm::NegotiatedSubstream; use prost::Message; use std::io::Cursor; -use std::{error, fmt, iter}; +use std::iter; +use thiserror::Error; use unsigned_varint::codec::UviBytes; pub struct Upgrade {} @@ -83,66 +84,26 @@ impl upgrade::InboundUpgrade for Upgrade { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum UpgradeError { - Decode(prost::DecodeError), - Io(std::io::Error), + #[error("Failed to decode message: {0}.")] + Decode( + #[from] + #[source] + prost::DecodeError, + ), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("Failed to parse response type field.")] ParseTypeField, + #[error("Failed to parse peer id.")] ParsePeerId, + #[error("Expected 'peer' field to be set.")] MissingPeer, + #[error("Unexpected message type 'status'")] UnexpectedTypeStatus, } -impl From for UpgradeError { - fn from(e: std::io::Error) -> Self { - UpgradeError::Io(e) - } -} - -impl From for UpgradeError { - fn from(e: prost::DecodeError) -> Self { - UpgradeError::Decode(e) - } -} - -impl fmt::Display for UpgradeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UpgradeError::Decode(e) => { - write!(f, "Failed to decode response: {}.", e) - } - UpgradeError::Io(e) => { - write!(f, "Io error {}", e) - } - UpgradeError::ParseTypeField => { - write!(f, "Failed to parse response type field.") - } - UpgradeError::ParsePeerId => { - write!(f, "Failed to parse peer id.") - } - UpgradeError::MissingPeer => { - write!(f, "Expected 'peer' field to be set.") - } - UpgradeError::UnexpectedTypeStatus => { - write!(f, "Unexpected message type 'status'") - } - } - } -} - -impl error::Error for UpgradeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - UpgradeError::Decode(e) => Some(e), - UpgradeError::Io(e) => Some(e), - UpgradeError::ParseTypeField => None, - UpgradeError::ParsePeerId => None, - UpgradeError::MissingPeer => None, - UpgradeError::UnexpectedTypeStatus => None, - } - } -} - pub struct Circuit { substream: Framed, src_peer_id: PeerId, diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index e8cd044c31c..be5f03cba17 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -30,7 +30,8 @@ use prost::Message; use std::convert::{TryFrom, TryInto}; use std::io::Cursor; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use std::{error, fmt, iter}; +use std::{iter}; +use thiserror::Error; use unsigned_varint::codec::UviBytes; pub enum Upgrade { @@ -168,96 +169,38 @@ impl upgrade::OutboundUpgrade for Upgrade { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum UpgradeError { - Decode(prost::DecodeError), - Io(std::io::Error), + #[error("Failed to decode message: {0}.")] + Decode( + #[from] + #[source] + prost::DecodeError, + ), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("Expected 'status' field to be set.")] MissingStatusField, + #[error("Expected 'reservation' field to be set.")] MissingReservationField, + #[error("Expected at least one address in reservation.")] NoAddressesinReservation, + #[error("Invalid expiration timestamp in reservation.")] InvalidReservationExpiration, + #[error("Invalid addresses in reservation.")] InvalidReservationAddrs, + #[error("Failed to parse response type field.")] ParseTypeField, + #[error("Unexpected message type 'connect'")] UnexpectedTypeConnect, + #[error("Unexpected message type 'reserve'")] UnexpectedTypeReserve, + #[error("Failed to parse response type field.")] ParseStatusField, + #[error("Unexpected message status '{0:?}'")] UnexpectedStatus(Status), } -impl From for UpgradeError { - fn from(e: std::io::Error) -> Self { - UpgradeError::Io(e) - } -} - -impl From for UpgradeError { - fn from(e: prost::DecodeError) -> Self { - UpgradeError::Decode(e) - } -} - -impl fmt::Display for UpgradeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UpgradeError::Decode(e) => { - write!(f, "Failed to decode response: {}.", e) - } - UpgradeError::Io(e) => { - write!(f, "Io error {}", e) - } - UpgradeError::MissingStatusField => { - write!(f, "Expected 'status' field to be set.") - } - UpgradeError::MissingReservationField => { - write!(f, "Expected 'reservation' field to be set.") - } - UpgradeError::NoAddressesinReservation => { - write!(f, "Expected at least one address in reservation.") - } - UpgradeError::InvalidReservationExpiration => { - write!(f, "Invalid expiration timestamp in reservation.") - } - UpgradeError::InvalidReservationAddrs => { - write!(f, "Invalid addresses in reservation.") - } - UpgradeError::ParseTypeField => { - write!(f, "Failed to parse response type field.") - } - UpgradeError::UnexpectedTypeConnect => { - write!(f, "Unexpected message type 'connect'") - } - UpgradeError::UnexpectedTypeReserve => { - write!(f, "Unexpected message type 'reserve'") - } - UpgradeError::ParseStatusField => { - write!(f, "Failed to parse response type field.") - } - UpgradeError::UnexpectedStatus(status) => { - write!(f, "Unexpected message status '{:?}'", status) - } - } - } -} - -impl error::Error for UpgradeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - UpgradeError::Decode(e) => Some(e), - UpgradeError::Io(e) => Some(e), - UpgradeError::MissingStatusField => None, - UpgradeError::MissingReservationField => None, - UpgradeError::NoAddressesinReservation => None, - UpgradeError::InvalidReservationExpiration => None, - UpgradeError::InvalidReservationAddrs => None, - UpgradeError::ParseTypeField => None, - UpgradeError::UnexpectedTypeConnect => None, - UpgradeError::UnexpectedTypeReserve => None, - UpgradeError::ParseStatusField => None, - UpgradeError::UnexpectedStatus(_) => None, - } - } -} - fn unix_timestamp_to_instant(secs: u64) -> Option { Instant::now().checked_add(Duration::from_secs( secs.checked_sub( diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs index 78406c9bf63..2c158eac37b 100644 --- a/protocols/relay/src/v2/protocol/outbound_stop.rs +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -28,8 +28,9 @@ use libp2p_swarm::NegotiatedSubstream; use prost::Message; use std::convert::TryInto; use std::io::Cursor; +use std::iter; use std::time::Duration; -use std::{error, fmt, iter}; +use thiserror::Error; use unsigned_varint::codec::UviBytes; pub struct Upgrade { @@ -124,67 +125,24 @@ impl upgrade::OutboundUpgrade for Upgrade { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum UpgradeError { - Decode(prost::DecodeError), - Io(std::io::Error), + #[error("Failed to decode message: {0}.")] + Decode( + #[from] + #[source] + prost::DecodeError, + ), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("Expected 'status' field to be set.")] MissingStatusField, + #[error("Failed to parse response type field.")] ParseTypeField, + #[error("Unexpected message type 'connect'")] UnexpectedTypeConnect, + #[error("Failed to parse response type field.")] ParseStatusField, + #[error("Unexpected message status '{0:?}'")] UnexpectedStatus(Status), } - -impl From for UpgradeError { - fn from(e: std::io::Error) -> Self { - UpgradeError::Io(e) - } -} - -impl From for UpgradeError { - fn from(e: prost::DecodeError) -> Self { - UpgradeError::Decode(e) - } -} - -impl fmt::Display for UpgradeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UpgradeError::Decode(e) => { - write!(f, "Failed to decode response: {}.", e) - } - UpgradeError::Io(e) => { - write!(f, "Io error {}", e) - } - UpgradeError::MissingStatusField => { - write!(f, "Expected 'status' field to be set.") - } - UpgradeError::ParseTypeField => { - write!(f, "Failed to parse response type field.") - } - UpgradeError::UnexpectedTypeConnect => { - write!(f, "Unexpected message type 'connect'") - } - UpgradeError::ParseStatusField => { - write!(f, "Failed to parse response type field.") - } - UpgradeError::UnexpectedStatus(status) => { - write!(f, "Unexpected message status '{:?}'", status) - } - } - } -} - -impl error::Error for UpgradeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - UpgradeError::Decode(e) => Some(e), - UpgradeError::Io(e) => Some(e), - UpgradeError::MissingStatusField => None, - UpgradeError::ParseTypeField => None, - UpgradeError::UnexpectedTypeConnect => None, - UpgradeError::ParseStatusField => None, - UpgradeError::UnexpectedStatus(_) => None, - } - } -} From 55dd0a2bca79a03fdb72ba7a14241b49fff8eb0e Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 19:32:44 +0200 Subject: [PATCH 036/108] protocols/relay/v2: Set rate limits --- protocols/relay/src/v2/client/handler.rs | 1 - protocols/relay/src/v2/relay.rs | 36 ++++++++++++------------ protocols/relay/src/v2/relay/handler.rs | 1 - 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 820742a2d1c..8935bca5ee5 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -451,7 +451,6 @@ impl ProtocolsHandler for Handler { } } - // TODO: Why is this not a mut reference? If it were the case, we could do all keep alive handling in here. fn connection_keep_alive(&self) -> KeepAlive { self.keep_alive } diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 8ebf6124f29..46b7485c618 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -55,28 +55,28 @@ pub struct Config { impl Default for Config { fn default() -> Self { let reservation_rate_limiters = vec![ + // For each peer ID one reservation every 2 minutes with up to 30 reservations per hour. rate_limiter::new_per_peer(rate_limiter::GenericRateLimiterConfig { - // TODO: Made up values. Reconsider these. - limit: NonZeroU32::new(128).expect("128 > 0"), - interval: Duration::from_secs(60), + limit: NonZeroU32::new(30).expect("30 > 0"), + interval: Duration::from_secs(60 * 2), }), + // For each IP address one reservation every minute with up to 60 reservations per hour. rate_limiter::new_per_ip(rate_limiter::GenericRateLimiterConfig { - // TODO: Made up values. Reconsider these. - limit: NonZeroU32::new(128).expect("128 > 0"), - interval: Duration::from_secs(60), + limit: NonZeroU32::new(60).expect("60 > 0"), + interval: Duration::from_secs(60 * 1), }), ]; let circuit_src_rate_limiters = vec![ + // For each source peer ID one circuit every 2 minute with up to 30 circuits per hour. rate_limiter::new_per_peer(rate_limiter::GenericRateLimiterConfig { - // TODO: Made up values. Reconsider these. - limit: NonZeroU32::new(128).expect("128 > 0"), - interval: Duration::from_secs(60), + limit: NonZeroU32::new(30).expect("30 > 0"), + interval: Duration::from_secs(60 * 2), }), + // For each source IP address one circuit every minute with up to 60 circuits per hour. rate_limiter::new_per_ip(rate_limiter::GenericRateLimiterConfig { - // TODO: Made up values. Reconsider these. - limit: NonZeroU32::new(128).expect("128 > 0"), - interval: Duration::from_secs(60), + limit: NonZeroU32::new(60).expect("60 > 0"), + interval: Duration::from_secs(60 * 1), }), ]; @@ -237,7 +237,7 @@ impl NetworkBehaviour for Relay { let now = Instant::now(); let handler_event = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { - // Deny reservation requests over relayed connections. + // Deny reservation requests over relayed circuits. handler::In::DenyReservationReq { inbound_reservation_req, status: message_proto::Status::PermissionDenied, @@ -339,9 +339,9 @@ impl NetworkBehaviour for Relay { let now = Instant::now(); let action = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { - // Deny connection requests over relayed connections. + // Deny circuit requests over relayed circuit. // - // An attacker could otherwise build recursive or cyclic connections. + // An attacker could otherwise build recursive or cyclic circuits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, @@ -358,7 +358,7 @@ impl NetworkBehaviour for Relay { .iter_mut() .all(|limiter| limiter.try_next(event_source, &remote_addr, now)) { - // Deny connection exceeding limits. + // Deny circuit exceeding limits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, @@ -374,7 +374,7 @@ impl NetworkBehaviour for Relay { .map(|cs| cs.iter().next()) .flatten() { - // Accept connection request if reservation present. + // Accept circuit request if reservation present. let circuit_id = self.circuits.insert(Circuit { status: CircuitStatus::Accepting, src_peer_id: event_source, @@ -395,7 +395,7 @@ impl NetworkBehaviour for Relay { }, } } else { - // Deny connection request if no reservation present. + // Deny circuit request if no reservation present. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index bd7dc8918c3..cb7fa87a7c6 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -509,7 +509,6 @@ impl ProtocolsHandler for Handler { )); } - // TODO: Why is this not a mut reference? If it were the case, we could do all keep alive handling in here. fn connection_keep_alive(&self) -> KeepAlive { self.keep_alive } From 126f97151df06277d4b26f34d80f50ae61ab873b Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 19:50:23 +0200 Subject: [PATCH 037/108] protocols/relay: Use wasm_timer::Instant --- protocols/relay/src/v2/client/handler.rs | 3 ++- protocols/relay/src/v2/protocol/outbound_hop.rs | 5 +++-- protocols/relay/src/v2/relay.rs | 3 ++- protocols/relay/src/v2/relay/handler.rs | 3 ++- protocols/relay/src/v2/relay/rate_limiter.rs | 5 +++-- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 8935bca5ee5..7e7b02fe7d4 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -38,7 +38,8 @@ use libp2p_swarm::{ use log::debug; use std::collections::VecDeque; use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; +use std::time::Duration; +use wasm_timer::Instant; pub enum In { Reserve { diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index be5f03cba17..a69e161d5d8 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -29,10 +29,11 @@ use libp2p_swarm::NegotiatedSubstream; use prost::Message; use std::convert::{TryFrom, TryInto}; use std::io::Cursor; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use std::{iter}; +use std::iter; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use thiserror::Error; use unsigned_varint::codec::UviBytes; +use wasm_timer::Instant; pub enum Upgrade { Reserve, diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 46b7485c618..ce446883b23 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -32,7 +32,8 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::num::NonZeroU32; use std::ops::Add; use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; +use std::time::Duration; +use wasm_timer::Instant; /// Configuration for the [`Relay`] [`NetworkBehaviour`]. /// diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index cb7fa87a7c6..2964bcc68df 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -38,7 +38,8 @@ use libp2p_swarm::{ }; use std::collections::VecDeque; use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; +use std::time::Duration; +use wasm_timer::Instant; pub struct Config { pub reservation_duration: Duration, diff --git a/protocols/relay/src/v2/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs index c4c0634e03d..398a90a83d3 100644 --- a/protocols/relay/src/v2/relay/rate_limiter.rs +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -24,7 +24,7 @@ pub use generic::{ use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_core::PeerId; use std::net::IpAddr; -use std::time::Instant; +use wasm_timer::Instant; /// Allows rate limiting access to some resource based on the [`PeerId`] and /// [`Multiaddr`] of a remote peer. @@ -69,7 +69,8 @@ mod generic { use std::convert::TryInto; use std::hash::Hash; use std::num::NonZeroU32; - use std::time::{Duration, Instant}; + use std::time::Duration; + use wasm_timer::Instant; /// Rate limiter using the [Token Bucket] algorithm. /// From 75f5616d509393be8c4c959a6f79528e83b7f4ab Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 20:17:25 +0200 Subject: [PATCH 038/108] protocols/relay/v2: Apply clippy suggestions --- protocols/relay/src/v2/client.rs | 6 +++--- protocols/relay/src/v2/client/handler.rs | 2 +- protocols/relay/src/v2/client/transport.rs | 4 ++-- protocols/relay/src/v2/protocol/inbound_hop.rs | 4 ++-- protocols/relay/src/v2/protocol/inbound_stop.rs | 2 +- protocols/relay/src/v2/protocol/outbound_hop.rs | 10 ++++------ protocols/relay/src/v2/protocol/outbound_stop.rs | 2 +- protocols/relay/src/v2/relay.rs | 10 +++++----- protocols/relay/src/v2/relay/handler.rs | 10 +++++----- 9 files changed, 24 insertions(+), 26 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 31a780463c5..31ac1b3ac11 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -375,17 +375,17 @@ impl RelayedConnection { read_buffer, drop_notifier, }; - return Poll::Ready(Ok(())); + Poll::Ready(Ok(())) } Poll::Ready(Err(e)) => { - return Poll::Ready(Err(e)); + Poll::Ready(Err(e)) } Poll::Pending => { **self = RelayedConnection::InboundAccepting { accept, drop_notifier, }; - return Poll::Pending; + Poll::Pending } } } diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 7e7b02fe7d4..67f5beec985 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -532,7 +532,7 @@ impl ProtocolsHandler for Handler { } // Deny incoming circuit requests. - while let Poll::Ready(Some((src_peer_id, result))) = + if let Poll::Ready(Some((src_peer_id, result))) = self.circuit_deny_futs.poll_next_unpin(cx) { match result { diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 6cc0c819dc6..60ad31626af 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -168,7 +168,7 @@ impl Transport for ClientTransport { }; let (to_listener, from_behaviour) = mpsc::channel(0); - let mut to_behaviour = self.to_behaviour.clone(); + let mut to_behaviour = self.to_behaviour; let msg_to_behaviour = Some( async move { to_behaviour @@ -213,7 +213,7 @@ impl Transport for ClientTransport { let relay_addr = relay_addr.ok_or(RelayError::MissingRelayAddr)?; let dst_peer_id = dst_peer_id.ok_or(RelayError::MissingDstPeerId)?; - let mut to_behaviour = self.to_behaviour.clone(); + let mut to_behaviour = self.to_behaviour; Ok(EitherFuture::Second( async move { let (tx, rx) = oneshot::channel(); diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index b4a595a2cde..0da45c603ab 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -62,7 +62,7 @@ impl upgrade::InboundUpgrade for Upgrade { let msg: bytes::BytesMut = substream .next() .await - .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; let HopMessage { r#type, @@ -83,7 +83,7 @@ impl upgrade::InboundUpgrade for Upgrade { hop_message::Type::Connect => { let dst = PeerId::from_bytes(&peer.ok_or(UpgradeError::MissingPeer)?.id) .map_err(|_| UpgradeError::ParsePeerId)?; - Ok(Req::Connect(CircuitReq { substream, dst })) + Ok(Req::Connect(CircuitReq { dst, substream })) } hop_message::Type::Status => Err(UpgradeError::UnexpectedTypeStatus), } diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 476fb603e18..4911c60f536 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -56,7 +56,7 @@ impl upgrade::InboundUpgrade for Upgrade { let msg: bytes::BytesMut = substream .next() .await - .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; let StopMessage { r#type, diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index a69e161d5d8..eeb90881fa9 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -27,7 +27,7 @@ use futures_timer::Delay; use libp2p_core::{upgrade, Multiaddr, PeerId}; use libp2p_swarm::NegotiatedSubstream; use prost::Message; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::io::Cursor; use std::iter; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -88,7 +88,7 @@ impl upgrade::OutboundUpgrade for Upgrade { let msg: bytes::BytesMut = substream .next() .await - .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; let HopMessage { r#type, @@ -131,7 +131,7 @@ impl upgrade::OutboundUpgrade for Upgrade { Some( unix_timestamp_to_instant(expires) .and_then(|instant| instant.checked_duration_since(Instant::now())) - .map(|duration| Delay::new(duration)) + .map(Delay::new) .ok_or(UpgradeError::InvalidReservationExpiration)?, ) } else { @@ -209,9 +209,7 @@ fn unix_timestamp_to_instant(secs: u64) -> Option { .duration_since(UNIX_EPOCH) .unwrap() .as_secs(), - )? - .try_into() - .ok()?, + )?, )) } diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs index 2c158eac37b..b3de1e98383 100644 --- a/protocols/relay/src/v2/protocol/outbound_stop.rs +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -85,7 +85,7 @@ impl upgrade::OutboundUpgrade for Upgrade { let msg: bytes::BytesMut = substream .next() .await - .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, ""))??; let StopMessage { r#type, diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index ce446883b23..5c97bfd7912 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -64,7 +64,7 @@ impl Default for Config { // For each IP address one reservation every minute with up to 60 reservations per hour. rate_limiter::new_per_ip(rate_limiter::GenericRateLimiterConfig { limit: NonZeroU32::new(60).expect("60 > 0"), - interval: Duration::from_secs(60 * 1), + interval: Duration::from_secs(60), }), ]; @@ -77,7 +77,7 @@ impl Default for Config { // For each source IP address one circuit every minute with up to 60 circuits per hour. rate_limiter::new_per_ip(rate_limiter::GenericRateLimiterConfig { limit: NonZeroU32::new(60).expect("60 > 0"), - interval: Duration::from_secs(60 * 1), + interval: Duration::from_secs(60), }), ]; @@ -580,9 +580,9 @@ impl CircuitsTracker { } fn accepted(&mut self, circuit_id: CircuitId) { - self.circuits - .get_mut(&circuit_id) - .map(|c| c.status = CircuitStatus::Accepted); + if let Some(c) = self.circuits.get_mut(&circuit_id) { + c.status = CircuitStatus::Accepted; + }; } fn remove(&mut self, circuit_id: CircuitId) -> Option { diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 2964bcc68df..b8d16d0a7df 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -536,7 +536,7 @@ impl ProtocolsHandler for Handler { return Poll::Ready(event); } - while let Poll::Ready(Some((circuit_id, dst_peer_id, result))) = + if let Poll::Ready(Some((circuit_id, dst_peer_id, result))) = self.circuits.poll_next_unpin(cx) { match result { @@ -557,7 +557,7 @@ impl ProtocolsHandler for Handler { } } - while let Poll::Ready(Some(result)) = self.reservation_accept_futures.poll_next_unpin(cx) { + if let Poll::Ready(Some(result)) = self.reservation_accept_futures.poll_next_unpin(cx) { match result { Ok(()) => { let renewed = self @@ -576,7 +576,7 @@ impl ProtocolsHandler for Handler { } } - while let Poll::Ready(Some(result)) = self.reservation_deny_futures.poll_next_unpin(cx) { + if let Poll::Ready(Some(result)) = self.reservation_deny_futures.poll_next_unpin(cx) { match result { Ok(()) => { return Poll::Ready(ProtocolsHandlerEvent::Custom( @@ -591,7 +591,7 @@ impl ProtocolsHandler for Handler { } } - while let Poll::Ready(Some(result)) = self.circuit_accept_futures.poll_next_unpin(cx) { + if let Poll::Ready(Some(result)) = self.circuit_accept_futures.poll_next_unpin(cx) { match result { Ok(parts) => { let CircuitParts { @@ -649,7 +649,7 @@ impl ProtocolsHandler for Handler { } } - while let Poll::Ready(Some((circuit_id, dst_peer_id, result))) = + if let Poll::Ready(Some((circuit_id, dst_peer_id, result))) = self.circuit_deny_futures.poll_next_unpin(cx) { match result { From 6dc961a4811aaf3625e4e92f1f6cd610650a9846 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 22 Jul 2021 20:20:23 +0200 Subject: [PATCH 039/108] protocols/relay: Fix intra doc link --- protocols/relay/src/v2/client/transport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 60ad31626af..ede6e3ccd2c 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -472,7 +472,7 @@ impl From for TransportError> { -/// Message from the [`ClientTransport`] to the [`Relay`](crate::v2::Relay) +/// Message from the [`ClientTransport`] to the [`Relay`](crate::v2::relay::Relay) /// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). pub enum TransportToBehaviourMsg { /// Dial destination node via relay node. From 31f842435441e9e2c4aa78bbc29b8a5b669b4854 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 28 Jul 2021 18:12:29 +0200 Subject: [PATCH 040/108] protocols/relay: Fix clippy warnings --- protocols/relay/src/v2/client/transport.rs | 1 + protocols/relay/src/v2/relay/handler.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index ede6e3ccd2c..86975b6e24d 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -492,6 +492,7 @@ pub enum TransportToBehaviourMsg { }, } +#[allow(clippy::large_enum_variant)] pub enum ToListenerMsg { Reservation(Result), IncomingRelayedConnection { diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index b8d16d0a7df..463da0699fe 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -79,6 +79,7 @@ pub enum In { } /// The events produced by the [`Handler`]. +#[allow(clippy::large_enum_variant)] pub enum Event { /// An inbound reservation request has been received. ReservationReqReceived { From 2c79813db730a24736ee9c057a11c5f39aa1f45b Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 7 Oct 2021 11:40:27 +0200 Subject: [PATCH 041/108] misc/metrics: Add basic instrumentation for libp2p-relay --- Cargo.toml | 2 +- misc/metrics/Cargo.toml | 2 + misc/metrics/src/lib.rs | 6 +++ misc/metrics/src/relay.rs | 106 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 misc/metrics/src/relay.rs diff --git a/Cargo.toml b/Cargo.toml index 05b5ccc8860..019e23b952d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ noise = ["libp2p-noise"] ping = ["libp2p-ping", "libp2p-metrics/ping"] plaintext = ["libp2p-plaintext"] pnet = ["libp2p-pnet"] -relay = ["libp2p-relay"] +relay = ["libp2p-relay", "libp2p-metrics/relay"] request-response = ["libp2p-request-response"] rendezvous = ["libp2p-rendezvous"] tcp-async-io = ["libp2p-tcp", "libp2p-tcp/async-io"] diff --git a/misc/metrics/Cargo.toml b/misc/metrics/Cargo.toml index f34c33d9d94..50c4abb7359 100644 --- a/misc/metrics/Cargo.toml +++ b/misc/metrics/Cargo.toml @@ -13,12 +13,14 @@ categories = ["network-programming", "asynchronous"] identify = ["libp2p-identify"] kad = ["libp2p-kad"] ping = ["libp2p-ping"] +relay = ["libp2p-relay"] [dependencies] libp2p-core= { version = "0.30.0", path = "../../core" } libp2p-identify = { version = "0.31.0", path = "../../protocols/identify", optional = true } libp2p-kad = { version = "0.32.0", path = "../../protocols/kad", optional = true } libp2p-ping = { version = "0.31.0", path = "../../protocols/ping", optional = true } +libp2p-relay = { version = "0.4.0", path = "../../protocols/relay", optional = true } libp2p-swarm = { version = "0.31.0", path = "../../swarm" } open-metrics-client = "0.12.0" diff --git a/misc/metrics/src/lib.rs b/misc/metrics/src/lib.rs index 4582904ca49..cb7900757c1 100644 --- a/misc/metrics/src/lib.rs +++ b/misc/metrics/src/lib.rs @@ -31,6 +31,8 @@ mod identify; mod kad; #[cfg(feature = "ping")] mod ping; +#[cfg(feature = "relay")] +mod relay; mod swarm; use open_metrics_client::registry::Registry; @@ -43,6 +45,8 @@ pub struct Metrics { kad: kad::Metrics, #[cfg(feature = "ping")] ping: ping::Metrics, + #[cfg(feature = "relay")] + relay: relay::Metrics, swarm: swarm::Metrics, } @@ -64,6 +68,8 @@ impl Metrics { kad: kad::Metrics::new(sub_registry), #[cfg(feature = "ping")] ping: ping::Metrics::new(sub_registry), + #[cfg(feature = "relay")] + relay: relay::Metrics::new(sub_registry), swarm: swarm::Metrics::new(sub_registry), } } diff --git a/misc/metrics/src/relay.rs b/misc/metrics/src/relay.rs new file mode 100644 index 00000000000..9bf85c46c56 --- /dev/null +++ b/misc/metrics/src/relay.rs @@ -0,0 +1,106 @@ +// 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 open_metrics_client::encoding::text::Encode; +use open_metrics_client::metrics::counter::Counter; +use open_metrics_client::metrics::family::Family; +use open_metrics_client::registry::Registry; + +pub struct Metrics { + events: Family, +} + +impl Metrics { + pub fn new(registry: &mut Registry) -> Self { + let sub_registry = registry.sub_registry_with_prefix("relay"); + + let events = Family::default(); + sub_registry.register( + "events", + "Events emitted by the relay NetworkBehaviour", + Box::new(events.clone()), + ); + + Self { events } + } +} + +#[derive(Clone, Hash, PartialEq, Eq, Encode)] +struct EventLabels { + event: EventType, +} + +#[derive(Clone, Hash, PartialEq, Eq, Encode)] +enum EventType { + ReservationReqAccepted, + ReservationReqAcceptFailed, + ReservationReqDenied, + ReservationReqDenyFailed, + ReservationTimedOut, + CircuitReqDenied, + CircuitReqDenyFailed, + CircuitReqAccepted, + CircuitReqAcceptFailed, + CircuitClosed, +} + +impl From<&libp2p_relay::v2::relay::Event> for EventType { + fn from(event: &libp2p_relay::v2::relay::Event) -> Self { + match event { + libp2p_relay::v2::relay::Event::ReservationReqAccepted { .. } => { + EventType::ReservationReqAccepted + } + libp2p_relay::v2::relay::Event::ReservationReqAcceptFailed { .. } => { + EventType::ReservationReqAcceptFailed + } + libp2p_relay::v2::relay::Event::ReservationReqDenied { .. } => { + EventType::ReservationReqDenied + } + libp2p_relay::v2::relay::Event::ReservationReqDenyFailed { .. } => { + EventType::ReservationReqDenyFailed + } + libp2p_relay::v2::relay::Event::ReservationTimedOut { .. } => { + EventType::ReservationTimedOut + } + libp2p_relay::v2::relay::Event::CircuitReqDenied { .. } => EventType::CircuitReqDenied, + libp2p_relay::v2::relay::Event::CircuitReqDenyFailed { .. } => { + EventType::CircuitReqDenyFailed + } + libp2p_relay::v2::relay::Event::CircuitReqAccepted { .. } => { + EventType::CircuitReqAccepted + } + libp2p_relay::v2::relay::Event::CircuitReqAcceptFailed { .. } => { + EventType::CircuitReqAcceptFailed + } + libp2p_relay::v2::relay::Event::CircuitClosed { .. } => EventType::CircuitClosed, + } + } +} + +impl super::Recorder for super::Metrics { + fn record(&self, event: &libp2p_relay::v2::relay::Event) { + self.relay + .events + .get_or_create(&EventLabels { + event: event.into(), + }) + .inc(); + } +} From d51fa727df138cc5f74b71a64b5d951416ed4c05 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 19 Nov 2021 20:15:31 +0100 Subject: [PATCH 042/108] protocols/relay: Return NetworkBehaviourAction::NotifyHandler right away --- protocols/relay/src/v2/client.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 94c3e22016d..ce6ae00c33e 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -293,15 +293,14 @@ impl NetworkBehaviour for Client { .and_then(|cs| cs.get(0)) { Some(connection_id) => { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: relay_peer_id, - handler: NotifyHandler::One(*connection_id), - event: handler::In::EstablishCircuit { - send_back, - dst_peer_id, - }, - }); + return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id: relay_peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::EstablishCircuit { + send_back, + dst_peer_id, + }, + }); } None => { self.rqsts_pending_connection From c74233550fc417f037c472ffaf268322cfa81ce1 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 20 Nov 2021 12:08:51 +0100 Subject: [PATCH 043/108] protocols/relay: Run rust fmt --- protocols/relay/src/v1/handler.rs | 7 ++----- protocols/relay/src/v1/protocol.rs | 1 - protocols/relay/src/v1/protocol/incoming_dst_req.rs | 6 ++---- protocols/relay/src/v1/protocol/listen.rs | 2 +- protocols/relay/src/v1/protocol/outgoing_dst_req.rs | 2 +- protocols/relay/src/v1/transport.rs | 2 +- protocols/relay/src/v2/client/transport.rs | 13 +++++++------ 7 files changed, 14 insertions(+), 19 deletions(-) diff --git a/protocols/relay/src/v1/handler.rs b/protocols/relay/src/v1/handler.rs index dc7c05a8b1c..4da3ed93f14 100644 --- a/protocols/relay/src/v1/handler.rs +++ b/protocols/relay/src/v1/handler.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::v1::message_proto::circuit_relay; -use crate::v1::{protocol, RequestId, Connection}; +use crate::v1::{protocol, Connection, RequestId}; use futures::channel::oneshot::{self, Canceled}; use futures::future::BoxFuture; use futures::prelude::*; @@ -92,10 +92,7 @@ pub struct RelayHandler { accept_dst_futures: FuturesUnordered< BoxFuture< 'static, - Result< - (PeerId, Connection, oneshot::Receiver<()>), - protocol::IncomingDstReqError, - >, + Result<(PeerId, Connection, oneshot::Receiver<()>), protocol::IncomingDstReqError>, >, >, /// Futures that copy from a source to a destination. diff --git a/protocols/relay/src/v1/protocol.rs b/protocols/relay/src/v1/protocol.rs index 9847bd1a0d4..274ad39d3ac 100644 --- a/protocols/relay/src/v1/protocol.rs +++ b/protocols/relay/src/v1/protocol.rs @@ -48,7 +48,6 @@ const MAX_ACCEPTED_MESSAGE_LEN: usize = 10 * 1024; const PROTOCOL_NAME: &[u8; 27] = b"/libp2p/circuit/relay/0.1.0"; - /// Representation of a `CircuitRelay_Peer` protobuf message with refined field types. /// /// Can be parsed from a `CircuitRelay_Peer` using the `TryFrom` trait. diff --git a/protocols/relay/src/v1/protocol/incoming_dst_req.rs b/protocols/relay/src/v1/protocol/incoming_dst_req.rs index 3b2e67479c6..21ed1cb83a8 100644 --- a/protocols/relay/src/v1/protocol/incoming_dst_req.rs +++ b/protocols/relay/src/v1/protocol/incoming_dst_req.rs @@ -70,10 +70,8 @@ impl IncomingDstReq { /// stream then points to the source (as retreived with `src_id()` and `src_addrs()`). pub fn accept( self, - ) -> BoxFuture< - 'static, - Result<(PeerId, Connection, oneshot::Receiver<()>), IncomingDstReqError>, - > { + ) -> BoxFuture<'static, Result<(PeerId, Connection, oneshot::Receiver<()>), IncomingDstReqError>> + { let IncomingDstReq { mut stream, src } = self; let msg = CircuitRelay { r#type: Some(circuit_relay::Type::Status.into()), diff --git a/protocols/relay/src/v1/protocol/listen.rs b/protocols/relay/src/v1/protocol/listen.rs index ac89aa6124f..87d77458050 100644 --- a/protocols/relay/src/v1/protocol/listen.rs +++ b/protocols/relay/src/v1/protocol/listen.rs @@ -21,8 +21,8 @@ use crate::v1::message_proto::{circuit_relay, CircuitRelay}; use crate::v1::protocol::incoming_dst_req::IncomingDstReq; use crate::v1::protocol::incoming_relay_req::IncomingRelayReq; -use crate::v1::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; use crate::v1::protocol::{Peer, PeerParseError}; +use crate::v1::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; use asynchronous_codec::Framed; use futures::channel::oneshot; use futures::{future::BoxFuture, prelude::*}; diff --git a/protocols/relay/src/v1/protocol/outgoing_dst_req.rs b/protocols/relay/src/v1/protocol/outgoing_dst_req.rs index fcad7cf9bbc..60871b17a93 100644 --- a/protocols/relay/src/v1/protocol/outgoing_dst_req.rs +++ b/protocols/relay/src/v1/protocol/outgoing_dst_req.rs @@ -19,8 +19,8 @@ // DEALINGS IN THE SOFTWARE. use crate::v1::message_proto::{circuit_relay, CircuitRelay}; -use crate::v1::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; use crate::v1::protocol::Peer; +use crate::v1::protocol::{MAX_ACCEPTED_MESSAGE_LEN, PROTOCOL_NAME}; use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::future::BoxFuture; diff --git a/protocols/relay/src/v1/transport.rs b/protocols/relay/src/v1/transport.rs index 11c97c563a2..811541793e8 100644 --- a/protocols/relay/src/v1/transport.rs +++ b/protocols/relay/src/v1/transport.rs @@ -18,8 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::v1::{RequestId, Connection}; use crate::v1::behaviour::BehaviourToListenerMsg; +use crate::v1::{Connection, RequestId}; use futures::channel::mpsc; use futures::channel::oneshot; use futures::future::{BoxFuture, Future, FutureExt}; diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 86975b6e24d..8cc6ce1dbfb 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -396,9 +396,10 @@ impl Stream for RelayListener { return Poll::Ready(Some(Err(EitherError::B(RelayError::Reservation)))); } Poll::Ready(None) => { - panic!("Expect sender of `from_behaviour` not to be dropped before listener."); - - }, + panic!( + "Expect sender of `from_behaviour` not to be dropped before listener." + ); + } Poll::Pending => {} } } @@ -454,7 +455,9 @@ pub enum RelayError { SendingMessageToBehaviour(#[from] mpsc::SendError), #[error("Response from behaviour was canceled")] ResponseFromBehaviourCanceled(#[from] oneshot::Canceled), - #[error("Address contains multiple circuit relay protocols (`p2p-circuit`) which is not supported.")] + #[error( + "Address contains multiple circuit relay protocols (`p2p-circuit`) which is not supported." + )] MultipleCircuitRelayProtocolsUnsupported, #[error("One of the provided multiaddresses is malformed.")] MalformedMultiaddr, @@ -470,8 +473,6 @@ impl From for TransportError> { } } - - /// Message from the [`ClientTransport`] to the [`Relay`](crate::v2::relay::Relay) /// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). pub enum TransportToBehaviourMsg { From 69b2fc1edb31a22af986eef3c816464541b97929 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 20 Nov 2021 12:24:16 +0100 Subject: [PATCH 044/108] protocols/relay/src/v2/relay: Accept mutable config --- protocols/relay/src/v2/relay.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index c4e62109975..378c5cce1a4 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -43,13 +43,11 @@ use std::time::Duration; pub struct Config { pub max_reservations: usize, pub reservation_duration: Duration, - // TODO: Mutable state in a config. Good idea? pub reservation_rate_limiters: Vec>, pub max_circuits: usize, pub max_circuit_duration: Duration, pub max_circuit_bytes: u64, - // TODO: Mutable state in a config. Good idea? pub circuit_src_rate_limiters: Vec>, } From 580f6688fd675c698ca918d44808915fa9bda914 Mon Sep 17 00:00:00 2001 From: ronzigelman Date: Thu, 4 Nov 2021 15:32:30 +0200 Subject: [PATCH 045/108] protocols/relay/examples: Structure command line args - Allow deterministic peer identity. - Choose between ipv6 and ipv4. --- protocols/relay/examples/relay_v2.rs | 112 ++++++++++++++++++--------- 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/protocols/relay/examples/relay_v2.rs b/protocols/relay/examples/relay_v2.rs index f414c1e754e..8c4d7fabdb9 100644 --- a/protocols/relay/examples/relay_v2.rs +++ b/protocols/relay/examples/relay_v2.rs @@ -23,19 +23,26 @@ use futures::executor::block_on; use futures::stream::StreamExt; use libp2p::core::upgrade; use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; -use libp2p::noise; +use libp2p::multiaddr::Protocol; use libp2p::ping::{Ping, PingConfig, PingEvent}; use libp2p::relay::v2::relay::{self, Relay}; use libp2p::swarm::{Swarm, SwarmEvent}; use libp2p::tcp::TcpConfig; use libp2p::Transport; use libp2p::{identity, NetworkBehaviour, PeerId}; +use libp2p::{noise, Multiaddr}; use std::error::Error; +use std::net::{Ipv4Addr, Ipv6Addr}; +use structopt::StructOpt; fn main() -> Result<(), Box> { env_logger::init(); - let local_key = identity::Keypair::generate_ed25519(); + let opt = Opt::from_args(); + println!("opt: {:?}", opt); + + // Create a static known PeerId based on given secret + let local_key: identity::Keypair = generate_ed25519(opt.secret_key_seed); let local_peer_id = PeerId::from(local_key.public()); println!("Local peer id: {:?}", local_peer_id); @@ -51,39 +58,6 @@ fn main() -> Result<(), Box> { .multiplex(libp2p_yamux::YamuxConfig::default()) .boxed(); - #[derive(NetworkBehaviour)] - #[behaviour(out_event = "Event", event_process = false)] - struct Behaviour { - relay: Relay, - ping: Ping, - identify: Identify, - } - - #[derive(Debug)] - enum Event { - Ping(PingEvent), - Identify(IdentifyEvent), - Relay(relay::Event), - } - - impl From for Event { - fn from(e: PingEvent) -> Self { - Event::Ping(e) - } - } - - impl From for Event { - fn from(e: IdentifyEvent) -> Self { - Event::Identify(e) - } - } - - impl From for Event { - fn from(e: relay::Event) -> Self { - Event::Relay(e) - } - } - let behaviour = Behaviour { relay: Relay::new(local_peer_id, Default::default()), ping: Ping::new(PingConfig::new()), @@ -95,8 +69,14 @@ fn main() -> Result<(), Box> { let mut swarm = Swarm::new(transport, behaviour, local_peer_id); - // Listen on all interfaces and whatever port the OS assigns - swarm.listen_on("/ip4/0.0.0.0/tcp/4001".parse()?)?; + // Listen on all interfaces + let listen_addr = Multiaddr::empty() + .with(match opt.use_ipv6 { + Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED), + _ => Protocol::from(Ipv4Addr::UNSPECIFIED), + }) + .with(Protocol::Tcp(opt.port)); + swarm.listen_on(listen_addr)?; block_on(async { loop { @@ -112,3 +92,61 @@ fn main() -> Result<(), Box> { } }) } + +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "Event", event_process = false)] +struct Behaviour { + relay: Relay, + ping: Ping, + identify: Identify, +} + +#[derive(Debug)] +enum Event { + Ping(PingEvent), + Identify(IdentifyEvent), + Relay(relay::Event), +} + +impl From for Event { + fn from(e: PingEvent) -> Self { + Event::Ping(e) + } +} + +impl From for Event { + fn from(e: IdentifyEvent) -> Self { + Event::Identify(e) + } +} + +impl From for Event { + fn from(e: relay::Event) -> Self { + Event::Relay(e) + } +} + +fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair { + let mut bytes = [0u8; 32]; + bytes[0] = secret_key_seed; + + let secret_key = identity::ed25519::SecretKey::from_bytes(&mut bytes) + .expect("this returns `Err` only if the length is wrong; the length is correct; qed"); + identity::Keypair::Ed25519(secret_key.into()) +} + +#[derive(Debug, StructOpt)] +#[structopt(name = "libp2p relay")] +struct Opt { + /// Determine if the relay listen on ipv6 or ipv4 loopback address. the default is ipv4 + #[structopt(long)] + use_ipv6: Option, + + /// Fixed value to generate deterministic peer id + #[structopt(long)] + secret_key_seed: u8, + + /// The port used to listen on all interfaces + #[structopt(long)] + port: u16, +} From 7c9f3bc932e88f1cc185161393f3bff81244d7f0 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 7 Dec 2021 15:57:25 +0100 Subject: [PATCH 046/108] protocols/relay/src/v2/client: Return NetworkBehaviourAction on ListenReq --- protocols/relay/src/v2/client.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index ce6ae00c33e..56263b539e4 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -257,12 +257,11 @@ impl NetworkBehaviour for Client { .and_then(|cs| cs.get(0)) { Some(connection_id) => { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: relay_peer_id, - handler: NotifyHandler::One(*connection_id), - event: handler::In::Reserve { to_listener }, - }); + return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id: relay_peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::Reserve { to_listener }, + }); } None => { self.rqsts_pending_connection From ad794222e11609f62e887861c8605066e069e716 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Sun, 5 Dec 2021 22:07:45 -0800 Subject: [PATCH 047/108] Refactor suggestion --- protocols/relay/src/v2/client.rs | 167 ++++++++----------------------- 1 file changed, 43 insertions(+), 124 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 56263b539e4..9767284e333 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -40,6 +40,7 @@ use libp2p_swarm::{ }; use std::collections::{HashMap, VecDeque}; use std::io::{Error, IoSlice}; +use std::ops::DerefMut; use std::pin::Pin; use std::task::{Context, Poll}; @@ -335,15 +336,13 @@ impl NetworkBehaviour for Client { /// A [`NegotiatedSubstream`] acting as a [`RelayedConnection`]. pub enum RelayedConnection { InboundAccepting { - accept: BoxFuture<'static, Result<(NegotiatedSubstream, Bytes), std::io::Error>>, - drop_notifier: oneshot::Sender<()>, + accept: BoxFuture<'static, Result>, }, Operational { read_buffer: Bytes, substream: NegotiatedSubstream, drop_notifier: oneshot::Sender<()>, }, - Poisoned, } impl Unpin for RelayedConnection {} @@ -354,8 +353,15 @@ impl RelayedConnection { drop_notifier: oneshot::Sender<()>, ) -> Self { RelayedConnection::InboundAccepting { - accept: circuit.accept().boxed(), - drop_notifier, + accept: async { + let (substream, read_buffer) = circuit.accept().await?; + Ok(RelayedConnection::Operational { + read_buffer, + substream, + drop_notifier, + }) + } + .boxed(), } } @@ -370,32 +376,6 @@ impl RelayedConnection { drop_notifier, } } - - fn accept_inbound( - self: &mut Pin<&mut Self>, - cx: &mut Context, - mut accept: BoxFuture<'static, Result<(NegotiatedSubstream, Bytes), std::io::Error>>, - drop_notifier: oneshot::Sender<()>, - ) -> Poll> { - match accept.poll_unpin(cx) { - Poll::Ready(Ok((substream, read_buffer))) => { - **self = RelayedConnection::Operational { - substream, - read_buffer, - drop_notifier, - }; - Poll::Ready(Ok(())) - } - Poll::Ready(Err(e)) => Poll::Ready(Err(e)), - Poll::Pending => { - **self = RelayedConnection::InboundAccepting { - accept, - drop_notifier, - }; - Poll::Pending - } - } - } } impl AsyncWrite for RelayedConnection { @@ -405,73 +385,37 @@ impl AsyncWrite for RelayedConnection { buf: &[u8], ) -> Poll> { loop { - match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { - RelayedConnection::InboundAccepting { - accept, - drop_notifier, - } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - RelayedConnection::Operational { - mut substream, - read_buffer, - drop_notifier, - } => { - let result = Pin::new(&mut substream).poll_write(cx, buf); - *self = RelayedConnection::Operational { - substream, - read_buffer, - drop_notifier, - }; - return result; + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { substream, .. } => { + return Pin::new(substream).poll_write(cx, buf); } - RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { loop { - match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { - RelayedConnection::InboundAccepting { - accept, - drop_notifier, - } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - RelayedConnection::Operational { - mut substream, - read_buffer, - drop_notifier, - } => { - let result = Pin::new(&mut substream).poll_flush(cx); - *self = RelayedConnection::Operational { - substream, - read_buffer, - drop_notifier, - }; - return result; + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { substream, .. } => { + return Pin::new(substream).poll_flush(cx); } - RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { loop { - match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { - RelayedConnection::InboundAccepting { - accept, - drop_notifier, - } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - RelayedConnection::Operational { - mut substream, - read_buffer, - drop_notifier, - } => { - let result = Pin::new(&mut substream).poll_close(cx); - *self = RelayedConnection::Operational { - substream, - read_buffer, - drop_notifier, - }; - return result; + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { substream, .. } => { + return Pin::new(substream).poll_close(cx); } - RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } @@ -482,25 +426,13 @@ impl AsyncWrite for RelayedConnection { bufs: &[IoSlice], ) -> Poll> { loop { - match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { - RelayedConnection::InboundAccepting { - accept, - drop_notifier, - } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, - RelayedConnection::Operational { - mut substream, - read_buffer, - drop_notifier, - } => { - let result = Pin::new(&mut substream).poll_write_vectored(cx, bufs); - *self = RelayedConnection::Operational { - substream, - read_buffer, - drop_notifier, - }; - return result; + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { substream, .. } => { + return Pin::new(substream).poll_write_vectored(cx, bufs); } - RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } @@ -513,37 +445,24 @@ impl AsyncRead for RelayedConnection { buf: &mut [u8], ) -> Poll> { loop { - match std::mem::replace(&mut *self, RelayedConnection::Poisoned) { - RelayedConnection::InboundAccepting { - accept, - drop_notifier, - } => ready!(self.accept_inbound(cx, accept, drop_notifier))?, + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } RelayedConnection::Operational { - mut substream, - mut read_buffer, - drop_notifier, + read_buffer, + substream, + .. } => { if !read_buffer.is_empty() { let n = std::cmp::min(read_buffer.len(), buf.len()); let data = read_buffer.split_to(n); buf[0..n].copy_from_slice(&data[..]); - *self = RelayedConnection::Operational { - substream, - read_buffer, - drop_notifier, - }; return Poll::Ready(Ok(n)); } - let result = Pin::new(&mut substream).poll_read(cx, buf); - *self = RelayedConnection::Operational { - substream, - read_buffer, - drop_notifier, - }; - return result; + return Pin::new(substream).poll_read(cx, buf); } - RelayedConnection::Poisoned => unreachable!("RelayedConnection is poisoned."), } } } From 517075d68fb5ce2aebec764578f9d8b7a5e6172c Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 7 Dec 2021 16:27:30 +0100 Subject: [PATCH 048/108] protocols/relay/src/v2/client/handler: Check status of lend out substreams --- protocols/relay/src/v2/client/handler.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 013e813d2e5..9777f868243 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -568,6 +568,16 @@ impl ProtocolsHandler for Handler { // Send errors to transport. while let Poll::Ready(Some(())) = self.send_error_futs.poll_next_unpin(cx) {} + // Check status of lend out substreams. + loop { + match self.alive_lend_out_substreams.poll_next_unpin(cx) { + Poll::Ready(Some(Err(oneshot::Canceled))) => {} + // TODO: Use void. + Poll::Ready(Some(Ok(()))) => unreachable!("Nothing is ever send via oneshot."), + Poll::Ready(None) | Poll::Pending => break, + } + } + // Update keep-alive handling. if self.reservation.is_none() && self.alive_lend_out_substreams.is_empty() From da7f5e4678dcef4906bd79d097dc51e00a795961 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 7 Dec 2021 16:30:38 +0100 Subject: [PATCH 049/108] protocols/relay/src/v2/client: Use void::Void for drop_notifer --- protocols/relay/src/v2/client.rs | 6 +++--- protocols/relay/src/v2/client/handler.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 9767284e333..70016754ae8 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -341,7 +341,7 @@ pub enum RelayedConnection { Operational { read_buffer: Bytes, substream: NegotiatedSubstream, - drop_notifier: oneshot::Sender<()>, + drop_notifier: oneshot::Sender, }, } @@ -350,7 +350,7 @@ impl Unpin for RelayedConnection {} impl RelayedConnection { pub(crate) fn new_inbound( circuit: inbound_stop::Circuit, - drop_notifier: oneshot::Sender<()>, + drop_notifier: oneshot::Sender, ) -> Self { RelayedConnection::InboundAccepting { accept: async { @@ -368,7 +368,7 @@ impl RelayedConnection { pub(crate) fn new_outbound( substream: NegotiatedSubstream, read_buffer: Bytes, - drop_notifier: oneshot::Sender<()>, + drop_notifier: oneshot::Sender, ) -> Self { RelayedConnection::Operational { substream, diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 9777f868243..38d17b3d8ee 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -154,7 +154,7 @@ pub struct Handler { /// Once all substreams are dropped and this handler has no other work, /// [`KeepAlive::Until`] can be set, allowing the connection to be closed /// eventually. - alive_lend_out_substreams: FuturesUnordered>, + alive_lend_out_substreams: FuturesUnordered>, circuit_deny_futs: FuturesUnordered)>>, @@ -573,7 +573,7 @@ impl ProtocolsHandler for Handler { match self.alive_lend_out_substreams.poll_next_unpin(cx) { Poll::Ready(Some(Err(oneshot::Canceled))) => {} // TODO: Use void. - Poll::Ready(Some(Ok(()))) => unreachable!("Nothing is ever send via oneshot."), + Poll::Ready(Some(Ok(v))) => void::unreachable(v), Poll::Ready(None) | Poll::Pending => break, } } From 45163dfe7e792bcc0d3cab3b8bde6a41a4b1e6f7 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 7 Dec 2021 16:37:33 +0100 Subject: [PATCH 050/108] protocols/relay/src/v2/client/handler: Log dropped oneshot Sender to transport --- protocols/relay/src/v2/client/handler.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 38d17b3d8ee..4a97cdc796b 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -255,12 +255,18 @@ impl ProtocolsHandler for Handler { OutboundOpenInfo::Connect { send_back }, ) => { let (tx, rx) = oneshot::channel(); - self.alive_lend_out_substreams.push(rx); - let _ = send_back.send(Ok(super::RelayedConnection::new_outbound( + match send_back.send(Ok(super::RelayedConnection::new_outbound( substream, read_buffer, tx, - ))); + ))) { + Ok(()) => self.alive_lend_out_substreams.push(rx), + Err(_) => debug!( + "Oneshot to `RelayedDial` future dropped. \ + Dropping established relayed connection to {:?}.", + self.remote_peer_id, + ), + } } _ => unreachable!(), } From 1d2039214208935278d797e855c753fefc60648a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 7 Dec 2021 16:49:44 +0100 Subject: [PATCH 051/108] protocols/relay/src/v2/client/handler: Await send call to transport listener --- protocols/relay/src/v2/client/handler.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 4a97cdc796b..8358a54d5fb 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -394,7 +394,10 @@ impl ProtocolsHandler for Handler { self.send_error_futs.push( async move { - let _ = to_listener.send(transport::ToListenerMsg::Reservation(Err(()))); + let _ = to_listener + .send(transport::ToListenerMsg::Reservation(Err(()))) + .boxed() + .await; } .boxed(), ); From 395f43ad4cef45a84d5285be5640927671f1177a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 7 Dec 2021 18:41:24 +0100 Subject: [PATCH 052/108] protocols/relay/src/v2/client/handler: Remove unnecessary boxing --- protocols/relay/src/v2/client/handler.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 8358a54d5fb..ac49d15e35e 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -396,7 +396,6 @@ impl ProtocolsHandler for Handler { async move { let _ = to_listener .send(transport::ToListenerMsg::Reservation(Err(()))) - .boxed() .await; } .boxed(), From c0c088055fadc2a69ffc4923c608e17f864a997e Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 9 Dec 2021 14:55:20 +0100 Subject: [PATCH 053/108] protocols/relay/src/v2/client: Extend unreacheable! text --- protocols/relay/src/v2/client.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 70016754ae8..152b787abf4 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -323,7 +323,9 @@ impl NetworkBehaviour for Client { } Poll::Ready(None) => unreachable!( "`Relay` `NetworkBehaviour` polled after channel from \ - `RelayTransport` has been closed.", + `RelayTransport` has been closed. Unreachable under \ + the assumption that the `Client` is never polled after \ + `ClientTransport` is dropped.", ), Poll::Pending => break, } From 746c75ee40b525e91896c436cdf183fada3a1ec1 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 9 Dec 2021 15:16:19 +0100 Subject: [PATCH 054/108] protocols/relay/src/v2/: Document pending_error and send_error_futs --- protocols/relay/src/v2/client/handler.rs | 26 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index ac49d15e35e..da4fc82c5de 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -126,6 +126,7 @@ pub struct Handler { local_peer_id: PeerId, remote_peer_id: PeerId, remote_addr: Multiaddr, + /// A pending fatal error that results in the connection being closed. pending_error: Option< ProtocolsHandlerUpgrErr< EitherError, @@ -158,6 +159,10 @@ pub struct Handler { circuit_deny_futs: FuturesUnordered)>>, + /// Futures that try to send errors to the transport. + /// + /// We may drop errors if this handler ends up in a terminal state (by returning + /// [`ProtocolsHandlerEvent::Close`]). send_error_futs: FuturesUnordered>, } @@ -392,14 +397,19 @@ impl ProtocolsHandler for Handler { } } - self.send_error_futs.push( - async move { - let _ = to_listener - .send(transport::ToListenerMsg::Reservation(Err(()))) - .await; - } - .boxed(), - ); + if self.pending_error.is_none() { + self.send_error_futs.push( + async move { + let _ = to_listener + .send(transport::ToListenerMsg::Reservation(Err(()))) + .await; + } + .boxed(), + ); + } else { + // Fatal error occured, thus handler is closing as quickly as possible. + // Transport is notified through dropping `to_listener`. + } self.queued_events.push_back(ProtocolsHandlerEvent::Custom( Event::ReservationReqFailed { renewal }, From 08d6637732d0e792e69f0f86e26c12ca5bc3b8e7 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 9 Dec 2021 20:38:27 +0100 Subject: [PATCH 055/108] protocols/relay/src/v2: Use OrTransport --- protocols/relay/src/v2/client.rs | 11 +- protocols/relay/src/v2/client/transport.rs | 321 +++++++-------------- protocols/relay/tests/v2.rs | 19 +- 3 files changed, 122 insertions(+), 229 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 152b787abf4..674b35c6798 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -21,7 +21,7 @@ //! [`NetworkBehaviour`] to act as a circuit relay v2 **client**. mod handler; -mod transport; +pub mod transport; use crate::v2::protocol::inbound_stop; use bytes::Bytes; @@ -32,7 +32,7 @@ use futures::io::{AsyncRead, AsyncWrite}; use futures::ready; use futures::stream::StreamExt; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; -use libp2p_core::{Multiaddr, PeerId, Transport}; +use libp2p_core::{Multiaddr, PeerId}; use libp2p_swarm::dial_opts::DialOpts; use libp2p_swarm::{ DialError, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, @@ -84,11 +84,10 @@ pub struct Client { } impl Client { - pub fn new_transport_and_behaviour( + pub fn new_transport_and_behaviour( local_peer_id: PeerId, - transport: T, - ) -> (transport::ClientTransport, Self) { - let (transport, from_transport) = transport::ClientTransport::new(transport); + ) -> (transport::ClientTransport, Self) { + let (transport, from_transport) = transport::ClientTransport::new(); let behaviour = Client { local_peer_id, from_transport, diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 8cc6ce1dbfb..32176861edc 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -23,48 +23,36 @@ use crate::v2::client::RelayedConnection; use crate::v2::RequestId; use futures::channel::mpsc; use futures::channel::oneshot; -use futures::future::{BoxFuture, Future, FutureExt}; +use futures::future::{ready, BoxFuture, Future, FutureExt, Ready}; use futures::sink::SinkExt; use futures::stream::{Stream, StreamExt}; -use libp2p_core::either::{EitherError, EitherFuture, EitherOutput}; use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_core::transport::{ListenerEvent, TransportError}; use libp2p_core::{PeerId, Transport}; -use pin_project::pin_project; use std::collections::VecDeque; use std::pin::Pin; use std::task::{Context, Poll}; use thiserror::Error; -/// A [`Transport`] wrapping another [`Transport`] enabling client relay capabilities. +/// A [`Transport`] enabling client relay capabilities. /// -/// Allows the local node to: -/// -/// 1. Use inner wrapped transport as before. +/// Needs to be combined with another protocol, e.g. via +/// [`OrTransport`](libp2p_core::transport::choice::OrTransport). /// -/// ``` -/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; -/// # use libp2p_core::transport::memory::MemoryTransport; -/// # use libp2p_relay::v2::client; -/// # let inner_transport = MemoryTransport::default(); -/// # let (transport, behaviour) = client::Client::new_transport_and_behaviour( -/// # PeerId::random(), -/// # inner_transport, -/// # ); -/// transport.dial(Multiaddr::empty().with(Protocol::Memory(42))); -/// ``` +/// Allows the local node to: /// -/// 2. Establish relayed connections by dialing `/p2p-circuit` addresses. +/// 1. Establish relayed connections by dialing `/p2p-circuit` addresses. /// /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; /// # use libp2p_core::transport::memory::MemoryTransport; +/// # use libp2p_core::transport::choice::OrTransport; /// # use libp2p_relay::v2::client; -/// # let inner_transport = MemoryTransport::default(); -/// # let (transport, behaviour) = client::Client::new_transport_and_behaviour( -/// # PeerId::random(), -/// # inner_transport, -/// # ); +/// let actual_transport = MemoryTransport::default(); +/// let (relay_transport, behaviour) = client::Client::new_transport_and_behaviour( +/// PeerId::random(), +/// ); +/// let transport = OrTransport::new(relay_transport, actual_transport); /// let dst_addr_via_relay = Multiaddr::empty() /// .with(Protocol::Memory(40)) // Relay address. /// .with(Protocol::P2p(PeerId::random().into())) // Relay peer id. @@ -79,12 +67,13 @@ use thiserror::Error; /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; /// # use libp2p_core::transport::memory::MemoryTransport; +/// # use libp2p_core::transport::choice::OrTransport; /// # use libp2p_relay::v2::client; -/// # let inner_transport = MemoryTransport::default(); -/// # let (transport, behaviour) = client::Client::new_transport_and_behaviour( -/// # PeerId::random(), -/// # inner_transport, -/// # ); +/// let actual_transport = MemoryTransport::default(); +/// let (relay_transport, behaviour) = client::Client::new_transport_and_behaviour( +/// PeerId::random(), +/// ); +/// let transport = OrTransport::new(relay_transport, actual_transport); /// let relay_addr = Multiaddr::empty() /// .with(Protocol::Memory(40)) // Relay address. /// .with(Protocol::P2p(PeerId::random().into())) // Relay peer id. @@ -92,62 +81,46 @@ use thiserror::Error; /// transport.listen_on(relay_addr).unwrap(); /// ``` #[derive(Clone)] -pub struct ClientTransport { +pub struct ClientTransport { to_behaviour: mpsc::Sender, - - inner_transport: T, } -impl ClientTransport { +impl ClientTransport { /// Create a new [`ClientTransport`] by wrapping an existing [`Transport`] in a /// [`ClientTransport`]. /// - ///``` + /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; /// # use libp2p_core::transport::memory::MemoryTransport; + /// # use libp2p_core::transport::choice::OrTransport; /// # use libp2p_relay::v2::client; - /// let inner_transport = MemoryTransport::default(); - /// let (transport, behaviour) = client::Client::new_transport_and_behaviour( - /// PeerId::random(), - /// inner_transport, + /// let actual_transport = MemoryTransport::default(); + /// let (relay_transport, behaviour) = client::Client::new_transport_and_behaviour( + /// PeerId::random(), /// ); - ///``` - pub(crate) fn new(t: T) -> (Self, mpsc::Receiver) { + /// + /// // To reduce unnecessary connection attempts, put `relay_transport` first. + /// let transport = OrTransport::new(relay_transport, actual_transport); + /// ``` + pub(crate) fn new() -> (Self, mpsc::Receiver) { let (to_behaviour, from_transport) = mpsc::channel(0); - let transport = ClientTransport { - to_behaviour, - - inner_transport: t, - }; - - (transport, from_transport) + (ClientTransport { to_behaviour }, from_transport) } } -impl Transport for ClientTransport { - type Output = EitherOutput<::Output, RelayedConnection>; - type Error = EitherError<::Error, RelayError>; - type Listener = RelayListener; - type ListenerUpgrade = RelayedListenerUpgrade; - type Dial = EitherFuture<::Dial, RelayedDial>; +impl Transport for ClientTransport { + type Output = RelayedConnection; + type Error = RelayError; + type Listener = RelayListener; + type ListenerUpgrade = Ready>; + type Dial = RelayedDial; fn listen_on(self, addr: Multiaddr) -> Result> { match parse_relayed_multiaddr(addr)? { - // Address does not contain circuit relay protocol. Use inner transport. - Err(addr) => { - let inner_listener = match self.inner_transport.listen_on(addr) { - Ok(listener) => listener, - Err(TransportError::MultiaddrNotSupported(addr)) => { - return Err(TransportError::MultiaddrNotSupported(addr)) - } - Err(TransportError::Other(err)) => { - return Err(TransportError::Other(EitherError::A(err))) - } - }; - Ok(RelayListener::Inner(inner_listener)) - } - // Address does contain circuit relay protocol. Use relayed listener. + // Address does not contain circuit relay protocol. + Err(addr) => return Err(TransportError::MultiaddrNotSupported(addr)), + // Address does contain circuit relay protocol. Ok(relayed_addr) => { let (relay_peer_id, relay_addr) = match relayed_addr { RelayedMultiaddr { @@ -182,7 +155,7 @@ impl Transport for ClientTransport { .boxed(), ); - Ok(RelayListener::Relayed { + Ok(RelayListener { queued_new_addresses: Default::default(), from_behaviour, msg_to_behaviour, @@ -193,14 +166,8 @@ impl Transport for ClientTransport { fn dial(self, addr: Multiaddr) -> Result> { match parse_relayed_multiaddr(addr)? { - // Address does not contain circuit relay protocol. Use inner transport. - Err(addr) => match self.inner_transport.dial(addr) { - Ok(dialer) => Ok(EitherFuture::First(dialer)), - Err(TransportError::MultiaddrNotSupported(addr)) => { - Err(TransportError::MultiaddrNotSupported(addr)) - } - Err(TransportError::Other(err)) => Err(TransportError::Other(EitherError::A(err))), - }, + // Address does not contain circuit relay protocol. + Err(addr) => return Err(TransportError::MultiaddrNotSupported(addr)), // Address does contain circuit relay protocol. Dial destination via relay. Ok(RelayedMultiaddr { relay_peer_id, @@ -214,30 +181,28 @@ impl Transport for ClientTransport { let dst_peer_id = dst_peer_id.ok_or(RelayError::MissingDstPeerId)?; let mut to_behaviour = self.to_behaviour; - Ok(EitherFuture::Second( - async move { - let (tx, rx) = oneshot::channel(); - to_behaviour - .send(TransportToBehaviourMsg::DialReq { - request_id: RequestId::new(), - relay_addr, - relay_peer_id, - dst_addr, - dst_peer_id, - send_back: tx, - }) - .await?; - let stream = rx.await?.map_err(|()| RelayError::Connect)?; - Ok(stream) - } - .boxed(), - )) + Ok(async move { + let (tx, rx) = oneshot::channel(); + to_behaviour + .send(TransportToBehaviourMsg::DialReq { + request_id: RequestId::new(), + relay_addr, + relay_peer_id, + dst_addr, + dst_peer_id, + send_back: tx, + }) + .await?; + let stream = rx.await?.map_err(|()| RelayError::Connect)?; + Ok(stream) + } + .boxed()) } } } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner_transport.address_translation(server, observed) + fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { + None } } @@ -307,102 +272,59 @@ fn parse_relayed_multiaddr( Ok(Ok(relayed_multiaddr)) } -#[pin_project(project = RelayListenerProj)] -pub enum RelayListener { - Inner(#[pin] ::Listener), - Relayed { - queued_new_addresses: VecDeque, - from_behaviour: mpsc::Receiver, - msg_to_behaviour: Option>>, - }, +pub struct RelayListener { + queued_new_addresses: VecDeque, + from_behaviour: mpsc::Receiver, + msg_to_behaviour: Option>>, } -impl Stream for RelayListener { - type Item = Result< - ListenerEvent, EitherError<::Error, RelayError>>, - EitherError<::Error, RelayError>, - >; +impl Unpin for RelayListener {} - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); +impl Stream for RelayListener { + type Item = + Result>, RelayError>, RelayError>; - match this { - RelayListenerProj::Inner(listener) => match listener.poll_next(cx) { - Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(EitherError::A(e)))), - Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - upgrade, - local_addr, - remote_addr, - }))) => { - return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - upgrade: RelayedListenerUpgrade::Inner(upgrade), - local_addr, - remote_addr, - }))) - } - Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))) => { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))) - } - Poll::Ready(Some(Ok(ListenerEvent::AddressExpired(addr)))) => { - return Poll::Ready(Some(Ok(ListenerEvent::AddressExpired(addr)))) - } - Poll::Ready(Some(Ok(ListenerEvent::Error(err)))) => { - return Poll::Ready(Some(Ok(ListenerEvent::Error(EitherError::A(err))))) - } - Poll::Ready(None) => return Poll::Ready(None), + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if let Some(msg) = &mut self.msg_to_behaviour { + match Future::poll(msg.as_mut(), cx) { + Poll::Ready(Ok(())) => self.msg_to_behaviour = None, + Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))), Poll::Pending => {} - }, - RelayListenerProj::Relayed { - queued_new_addresses, - from_behaviour, - msg_to_behaviour, - } => { - if let Some(msg) = msg_to_behaviour { - match Future::poll(msg.as_mut(), cx) { - Poll::Ready(Ok(())) => *msg_to_behaviour = None, - Poll::Ready(Err(e)) => { - return Poll::Ready(Some(Err(EitherError::B(e.into())))) - } - Poll::Pending => {} - } - } + } + } - if let Some(addr) = queued_new_addresses.pop_front() { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); - } + if let Some(addr) = self.queued_new_addresses.pop_front() { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); + } - match from_behaviour.poll_next_unpin(cx) { - Poll::Ready(Some(ToListenerMsg::IncomingRelayedConnection { - stream, - src_peer_id, - relay_addr, - relay_peer_id: _, - })) => { - return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - upgrade: RelayedListenerUpgrade::Relayed(Some(stream)), - local_addr: relay_addr.with(Protocol::P2pCircuit), - remote_addr: Protocol::P2p(src_peer_id.into()).into(), - }))); - } - Poll::Ready(Some(ToListenerMsg::Reservation(Ok(Reservation { addrs })))) => { - let mut iter = addrs.into_iter(); - let first = iter.next(); - queued_new_addresses.extend(iter); - if let Some(addr) = first { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); - } - } - Poll::Ready(Some(ToListenerMsg::Reservation(Err(())))) => { - return Poll::Ready(Some(Err(EitherError::B(RelayError::Reservation)))); - } - Poll::Ready(None) => { - panic!( - "Expect sender of `from_behaviour` not to be dropped before listener." - ); - } - Poll::Pending => {} + match self.from_behaviour.poll_next_unpin(cx) { + Poll::Ready(Some(ToListenerMsg::IncomingRelayedConnection { + stream, + src_peer_id, + relay_addr, + relay_peer_id: _, + })) => { + return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + upgrade: ready(Ok(stream)), + local_addr: relay_addr.with(Protocol::P2pCircuit), + remote_addr: Protocol::P2p(src_peer_id.into()).into(), + }))); + } + Poll::Ready(Some(ToListenerMsg::Reservation(Ok(Reservation { addrs })))) => { + let mut iter = addrs.into_iter(); + let first = iter.next(); + self.queued_new_addresses.extend(iter); + if let Some(addr) = first { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); } } + Poll::Ready(Some(ToListenerMsg::Reservation(Err(())))) => { + return Poll::Ready(Some(Err(RelayError::Reservation))); + } + Poll::Ready(None) => { + panic!("Expect sender of `from_behaviour` not to be dropped before listener."); + } + Poll::Pending => {} } Poll::Pending @@ -411,35 +333,6 @@ impl Stream for RelayListener { pub type RelayedDial = BoxFuture<'static, Result>; -#[pin_project(project = RelayedListenerUpgradeProj)] -pub enum RelayedListenerUpgrade { - Inner(#[pin] ::ListenerUpgrade), - Relayed(Option), -} - -impl Future for RelayedListenerUpgrade { - type Output = Result< - EitherOutput<::Output, RelayedConnection>, - EitherError<::Error, RelayError>, - >; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.project() { - RelayedListenerUpgradeProj::Inner(upgrade) => match upgrade.poll(cx) { - Poll::Ready(Ok(out)) => return Poll::Ready(Ok(EitherOutput::First(out))), - Poll::Ready(Err(err)) => return Poll::Ready(Err(EitherError::A(err))), - Poll::Pending => {} - }, - RelayedListenerUpgradeProj::Relayed(substream) => { - return Poll::Ready(Ok(EitherOutput::Second( - substream.take().expect("Future polled after completion."), - ))) - } - } - - Poll::Pending - } -} - /// Error that occurred during relay connection setup. #[derive(Debug, Error)] pub enum RelayError { @@ -467,9 +360,9 @@ pub enum RelayError { Connect, } -impl From for TransportError> { +impl From for TransportError { fn from(error: RelayError) -> Self { - TransportError::Other(EitherError::B(error)) + TransportError::Other(error) } } diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index 0bd130e308d..879dc8e22f2 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -25,6 +25,7 @@ use futures::stream::StreamExt; use futures::task::Spawn; use libp2p::core::multiaddr::{Multiaddr, Protocol}; use libp2p::core::muxing::StreamMuxerBox; +use libp2p::core::transport::choice::OrTransport; use libp2p::core::transport::{Boxed, MemoryTransport, Transport}; use libp2p::core::PublicKey; use libp2p::core::{identity, upgrade, PeerId}; @@ -190,7 +191,7 @@ fn build_relay() -> Swarm { let local_public_key = local_key.public(); let local_peer_id = local_public_key.clone().to_peer_id(); - let transport = build_transport(MemoryTransport::default().boxed(), local_public_key); + let transport = upgrade_transport(MemoryTransport::default().boxed(), local_public_key); Swarm::new( transport, @@ -213,9 +214,11 @@ fn build_client() -> Swarm { let local_public_key = local_key.public(); let local_peer_id = local_public_key.clone().to_peer_id(); - let (transport, behaviour) = - client::Client::new_transport_and_behaviour(local_peer_id, MemoryTransport::default()); - let transport = build_transport(transport.boxed(), local_public_key); + let (relay_transport, behaviour) = client::Client::new_transport_and_behaviour(local_peer_id); + let transport = upgrade_transport( + OrTransport::new(relay_transport, MemoryTransport::default()).boxed(), + local_public_key, + ); Swarm::new( transport, @@ -227,20 +230,18 @@ fn build_client() -> Swarm { ) } -fn build_transport( +fn upgrade_transport( transport: Boxed, local_public_key: PublicKey, ) -> Boxed<(PeerId, StreamMuxerBox)> where StreamSink: AsyncRead + AsyncWrite + Send + Unpin + 'static, { - let transport = transport + transport .upgrade(upgrade::Version::V1) .authenticate(PlainText2Config { local_public_key }) .multiplex(libp2p_yamux::YamuxConfig::default()) - .boxed(); - - transport + .boxed() } #[derive(NetworkBehaviour)] From bfd92a1637256f05278df51b456e21bf31fe9675 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 9 Dec 2021 20:42:09 +0100 Subject: [PATCH 056/108] protocols/relay/src/v2: Fix typo --- protocols/relay/src/v2/relay/rate_limiter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs index 9148e039d4f..c89a6c0761b 100644 --- a/protocols/relay/src/v2/relay/rate_limiter.rs +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -30,7 +30,7 @@ use std::net::IpAddr; /// [`Multiaddr`] of a remote peer. /// /// See [`new_per_peer`] and [`new_per_ip`] for precast implementations. Use -/// [`GenericRateLimiter`] to build your own, e.g. based on the atonomous system +/// [`GenericRateLimiter`] to build your own, e.g. based on the autonomous system /// number of a peers IP address. pub trait RateLimiter: Send { fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool; From a60ffbab65f749b5f198cdc0b962c17f3de03f8b Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 9 Dec 2021 21:59:03 +0100 Subject: [PATCH 057/108] protocols/relay/src/v2: Renew reservation after 3/4 of expiration --- .../relay/src/v2/protocol/outbound_hop.rs | 25 ++++++++----------- protocols/relay/src/v2/relay/handler.rs | 2 +- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index 892a998b707..31acf795c29 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -127,10 +127,18 @@ impl upgrade::OutboundUpgrade for Upgrade { .map_err(|_| UpgradeError::InvalidReservationAddrs)? }; - let renewal_timeout = if let Some(expires) = reservation.expire { + let renewal_timeout = if let Some(timestamp) = reservation.expire { Some( - unix_timestamp_to_instant(expires) - .and_then(|instant| instant.checked_duration_since(Instant::now())) + timestamp + .checked_sub( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ) + // Renew the reservation after 3/4 of the reservation expiration timestamp. + .and_then(|duration| duration.checked_sub(duration / 4)) + .map(Duration::from_secs) .map(Delay::new) .ok_or(UpgradeError::InvalidReservationExpiration)?, ) @@ -202,17 +210,6 @@ pub enum UpgradeError { UnexpectedStatus(Status), } -fn unix_timestamp_to_instant(secs: u64) -> Option { - Instant::now().checked_add(Duration::from_secs( - secs.checked_sub( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - )?, - )) -} - pub enum Output { Reservation { renewal_timeout: Option, diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 353df82b1f8..beb0d384986 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -649,7 +649,7 @@ impl ProtocolsHandler for Handler { )) } // While useless for reaching this particular destination, the - // connection to the relay might still proof helpful for other + // connection to the relay might still prove helpful for other // destinations. Thus do not terminate the connection. Status::ResourceLimitExceeded | Status::PermissionDenied => {} } From 112395275d9a9838fe802586090e1e2777e15f91 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 8 Dec 2021 22:04:54 +0100 Subject: [PATCH 058/108] protocols/relay/v2: Use AcceptReservationReqBuilder concept --- protocols/relay/src/v2/relay.rs | 281 ++++++++++++++++++-------------- 1 file changed, 162 insertions(+), 119 deletions(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 378c5cce1a4..1a67ff533a1 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -24,6 +24,7 @@ mod handler; pub mod rate_limiter; use crate::v2::message_proto; +use crate::v2::protocol::inbound_hop; use instant::Instant; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; use libp2p_core::multiaddr::Protocol; @@ -156,7 +157,7 @@ pub struct Relay { circuits: CircuitsTracker, /// Queue of actions to return when polled. - queued_actions: VecDeque>, + queued_actions: VecDeque, } impl Relay { @@ -212,14 +213,14 @@ impl NetworkBehaviour for Relay { // Only emit [`CircuitClosed`] for accepted requests. .filter(|c| matches!(c.status, CircuitStatus::Accepted)) { - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitClosed { - src_peer_id: circuit.src_peer_id, - dst_peer_id: circuit.dst_peer_id, - error: Some(std::io::ErrorKind::ConnectionAborted.into()), - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitClosed { + src_peer_id: circuit.src_peer_id, + dst_peer_id: circuit.dst_peer_id, + error: Some(std::io::ErrorKind::ConnectionAborted.into()), + }) + .into(), + ); } } @@ -236,12 +237,17 @@ impl NetworkBehaviour for Relay { } => { let now = Instant::now(); - let handler_event = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { + let action = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { // Deny reservation requests over relayed circuits. - handler::In::DenyReservationReq { - inbound_reservation_req, - status: message_proto::Status::PermissionDenied, + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: handler::In::DenyReservationReq { + inbound_reservation_req, + status: message_proto::Status::PermissionDenied, + }, } + .into() } else if self .reservations .iter() @@ -255,28 +261,30 @@ impl NetworkBehaviour for Relay { .all(|limiter| limiter.try_next(event_source, &remote_addr, now)) { // Deny reservation exceeding limits. - handler::In::DenyReservationReq { - inbound_reservation_req, - status: message_proto::Status::ResourceLimitExceeded, + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: handler::In::DenyReservationReq { + inbound_reservation_req, + status: message_proto::Status::ResourceLimitExceeded, + }, } + .into() } else { // Accept reservation. self.reservations .entry(event_source) .or_default() .insert(connection); - handler::In::AcceptReservationReq { + + Action::AcceptReservationPrototype { + handler: NotifyHandler::One(connection), + peer_id: event_source, inbound_reservation_req, - addrs: vec![], } }; - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - handler: NotifyHandler::One(connection), - peer_id: event_source, - event: handler_event, - }); + self.queued_actions.push_back(action); } handler::Event::ReservationReqAccepted { renewed } => { // Ensure local eventual consistent reservation state matches handler (source of @@ -286,51 +294,51 @@ impl NetworkBehaviour for Relay { .or_default() .insert(connection); - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqAccepted { - src_peer_id: event_source, - renewed, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::ReservationReqAccepted { + src_peer_id: event_source, + renewed, + }) + .into(), + ); } handler::Event::ReservationReqAcceptFailed { error } => { - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqAcceptFailed { - src_peer_id: event_source, - error, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::ReservationReqAcceptFailed { + src_peer_id: event_source, + error, + }) + .into(), + ); } handler::Event::ReservationReqDenied {} => { - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqDenied { - src_peer_id: event_source, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::ReservationReqDenied { + src_peer_id: event_source, + }) + .into(), + ); } handler::Event::ReservationReqDenyFailed { error } => { - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqDenyFailed { - src_peer_id: event_source, - error, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::ReservationReqDenyFailed { + src_peer_id: event_source, + error, + }) + .into(), + ); } handler::Event::ReservationTimedOut {} => { self.reservations .get_mut(&event_source) .map(|cs| cs.remove(&connection)); - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationTimedOut { - src_peer_id: event_source, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::ReservationTimedOut { + src_peer_id: event_source, + }) + .into(), + ); } handler::Event::CircuitReqReceived { inbound_circuit_req, @@ -406,7 +414,7 @@ impl NetworkBehaviour for Relay { }, } }; - self.queued_actions.push_back(action); + self.queued_actions.push_back(action.into()); } handler::Event::CircuitReqDenied { circuit_id, @@ -416,13 +424,13 @@ impl NetworkBehaviour for Relay { self.circuits.remove(circuit_id); } - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitReqDenied { - src_peer_id: event_source, - dst_peer_id, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitReqDenied { + src_peer_id: event_source, + dst_peer_id, + }) + .into(), + ); } handler::Event::CircuitReqDenyFailed { circuit_id, @@ -433,14 +441,14 @@ impl NetworkBehaviour for Relay { self.circuits.remove(circuit_id); } - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitReqDenyFailed { - src_peer_id: event_source, - dst_peer_id, - error, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitReqDenyFailed { + src_peer_id: event_source, + dst_peer_id, + error, + }) + .into(), + ); } handler::Event::OutboundConnectNegotiated { circuit_id, @@ -451,8 +459,8 @@ impl NetworkBehaviour for Relay { dst_stream, dst_pending_data, } => { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { + self.queued_actions.push_back( + NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(src_connection_id), peer_id: src_peer_id, event: handler::In::AcceptAndDriveCircuit { @@ -463,7 +471,9 @@ impl NetworkBehaviour for Relay { dst_stream, dst_pending_data, }, - }); + } + .into(), + ); } handler::Event::OutboundConnectNegotiationFailed { circuit_id, @@ -472,8 +482,8 @@ impl NetworkBehaviour for Relay { inbound_circuit_req, status, } => { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { + self.queued_actions.push_back( + NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(src_connection_id), peer_id: src_peer_id, event: handler::In::DenyCircuitReq { @@ -481,20 +491,22 @@ impl NetworkBehaviour for Relay { inbound_circuit_req, status, }, - }); + } + .into(), + ); } handler::Event::CircuitReqAccepted { dst_peer_id, circuit_id, } => { self.circuits.accepted(circuit_id); - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitReqAccepted { - src_peer_id: event_source, - dst_peer_id, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitReqAccepted { + src_peer_id: event_source, + dst_peer_id, + }) + .into(), + ); } handler::Event::CircuitReqAcceptFailed { dst_peer_id, @@ -502,14 +514,14 @@ impl NetworkBehaviour for Relay { error, } => { self.circuits.remove(circuit_id); - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitReqAcceptFailed { - src_peer_id: event_source, - dst_peer_id, - error, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitReqAcceptFailed { + src_peer_id: event_source, + dst_peer_id, + error, + }) + .into(), + ); } handler::Event::CircuitClosed { dst_peer_id, @@ -518,14 +530,14 @@ impl NetworkBehaviour for Relay { } => { self.circuits.remove(circuit_id); - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::CircuitClosed { - src_peer_id: event_source, - dst_peer_id, - error, - }, - )); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitClosed { + src_peer_id: event_source, + dst_peer_id, + error, + }) + .into(), + ); } } } @@ -535,23 +547,8 @@ impl NetworkBehaviour for Relay { _cx: &mut Context<'_>, poll_parameters: &mut impl PollParameters, ) -> Poll> { - if let Some(mut event) = self.queued_actions.pop_front() { - // Set external addresses in [`AcceptReservationReq`]. - if let NetworkBehaviourAction::NotifyHandler { - event: handler::In::AcceptReservationReq { ref mut addrs, .. }, - .. - } = &mut event - { - *addrs = poll_parameters - .external_addresses() - .map(|a| { - a.addr - .with(Protocol::P2p((*poll_parameters.local_peer_id()).into())) - }) - .collect(); - } - - return Poll::Ready(event); + if let Some(action) = self.queued_actions.pop_front() { + return Poll::Ready(action.build(poll_parameters)); } Poll::Pending @@ -640,3 +637,49 @@ impl Add for CircuitId { CircuitId(self.0 + rhs) } } + +/// A [`NetworkBehaviourAction`], either complete, or still requiring data from [`PollParameters`] +/// before being returned in [`::poll`]. +enum Action { + Done(NetworkBehaviourAction), + AcceptReservationPrototype { + inbound_reservation_req: inbound_hop::ReservationReq, + handler: NotifyHandler, + peer_id: PeerId, + }, +} + +impl From> for Action { + fn from(action: NetworkBehaviourAction) -> Self { + Self::Done(action) + } +} + +impl Action { + fn build( + self, + poll_parameters: &mut impl PollParameters, + ) -> NetworkBehaviourAction { + match self { + Action::Done(action) => action, + Action::AcceptReservationPrototype { + inbound_reservation_req, + handler, + peer_id, + } => NetworkBehaviourAction::NotifyHandler { + handler, + peer_id, + event: handler::In::AcceptReservationReq { + inbound_reservation_req, + addrs: poll_parameters + .external_addresses() + .map(|a| { + a.addr + .with(Protocol::P2p((*poll_parameters.local_peer_id()).into())) + }) + .collect(), + }, + }, + } + } +} From 1af928ab3f104347308430385e8a9c551a48342d Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 12 Dec 2021 16:20:39 +0100 Subject: [PATCH 059/108] protocols/relay: Capitalize I in NoAddressesInReservation --- protocols/relay/src/v2/client/handler.rs | 4 ++-- protocols/relay/src/v2/protocol/outbound_hop.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index da4fc82c5de..05e951ba4c0 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -359,7 +359,7 @@ impl ProtocolsHandler for Handler { | outbound_hop::UpgradeError::ParseStatusField | outbound_hop::UpgradeError::MissingStatusField | outbound_hop::UpgradeError::MissingReservationField - | outbound_hop::UpgradeError::NoAddressesinReservation + | outbound_hop::UpgradeError::NoAddressesInReservation | outbound_hop::UpgradeError::InvalidReservationExpiration | outbound_hop::UpgradeError::InvalidReservationAddrs | outbound_hop::UpgradeError::UnexpectedTypeConnect @@ -438,7 +438,7 @@ impl ProtocolsHandler for Handler { | outbound_hop::UpgradeError::ParseStatusField | outbound_hop::UpgradeError::MissingStatusField | outbound_hop::UpgradeError::MissingReservationField - | outbound_hop::UpgradeError::NoAddressesinReservation + | outbound_hop::UpgradeError::NoAddressesInReservation | outbound_hop::UpgradeError::InvalidReservationExpiration | outbound_hop::UpgradeError::InvalidReservationAddrs | outbound_hop::UpgradeError::UnexpectedTypeConnect diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index 31acf795c29..be9b3eec90f 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -117,7 +117,7 @@ impl upgrade::OutboundUpgrade for Upgrade { let reservation = reservation.ok_or(UpgradeError::MissingReservationField)?; let addrs = if reservation.addrs.is_empty() { - return Err(UpgradeError::NoAddressesinReservation); + return Err(UpgradeError::NoAddressesInReservation); } else { reservation .addrs @@ -193,7 +193,7 @@ pub enum UpgradeError { #[error("Expected 'reservation' field to be set.")] MissingReservationField, #[error("Expected at least one address in reservation.")] - NoAddressesinReservation, + NoAddressesInReservation, #[error("Invalid expiration timestamp in reservation.")] InvalidReservationExpiration, #[error("Invalid addresses in reservation.")] From a9d9b4158462a7b1f1d285a46d0de730b03e7754 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 12 Dec 2021 16:21:30 +0100 Subject: [PATCH 060/108] misc/metrics: Derive Debug for event types --- misc/metrics/src/relay.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/metrics/src/relay.rs b/misc/metrics/src/relay.rs index 9bf85c46c56..dece3a7f294 100644 --- a/misc/metrics/src/relay.rs +++ b/misc/metrics/src/relay.rs @@ -42,12 +42,12 @@ impl Metrics { } } -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode)] struct EventLabels { event: EventType, } -#[derive(Clone, Hash, PartialEq, Eq, Encode)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode)] enum EventType { ReservationReqAccepted, ReservationReqAcceptFailed, From a8f21ae7572efd73acbc4bd2bef27d2d46bb0539 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 12 Dec 2021 16:23:58 +0100 Subject: [PATCH 061/108] protocols/relay/src/v2/client/handler: Consistent mpsc and oneshot import --- protocols/relay/src/v2/client/handler.rs | 9 ++++----- protocols/relay/src/v2/protocol/outbound_hop.rs | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 05e951ba4c0..4a56a3b103b 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -21,8 +21,7 @@ use crate::v2::client::transport; use crate::v2::message_proto::Status; use crate::v2::protocol::{inbound_stop, outbound_hop}; -use futures::channel::mpsc::Sender; -use futures::channel::oneshot; +use futures::channel::{mpsc, oneshot}; use futures::future::{BoxFuture, FutureExt}; use futures::sink::SinkExt; use futures::stream::{FuturesUnordered, StreamExt}; @@ -44,7 +43,7 @@ use std::time::Duration; pub enum In { Reserve { - to_listener: Sender, + to_listener: mpsc::Sender, }, EstablishCircuit { dst_peer_id: PeerId, @@ -621,7 +620,7 @@ enum Reservation { /// [`None`] if reservation does not expire. renewal_timeout: Option, pending_msgs: VecDeque, - to_listener: Sender, + to_listener: mpsc::Sender, }, Renewal { pending_msgs: VecDeque, @@ -630,7 +629,7 @@ enum Reservation { pub enum OutboundOpenInfo { Reserve { - to_listener: Sender, + to_listener: mpsc::Sender, }, Connect { send_back: oneshot::Sender>, diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index be9b3eec90f..2c048b86490 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -24,7 +24,6 @@ use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::{future::BoxFuture, prelude::*}; use futures_timer::Delay; -use instant::Instant; use libp2p_core::{upgrade, Multiaddr, PeerId}; use libp2p_swarm::NegotiatedSubstream; use prost::Message; From daa06865cb1157ed67f3526532c706c180bffee2 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 12 Dec 2021 16:52:05 +0100 Subject: [PATCH 062/108] protocols/relay/src/v2/client/transport: Loop on from_behaviour Previous version was (a) confusing due to assuming `queued_new_addresses` to be empty and (b) error prone in the case where `addrs` is emtpy and thus no `Waker` being registered. --- protocols/relay/src/v2/client/transport.rs | 72 +++++++++++----------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 32176861edc..a6da2ed65d3 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -285,46 +285,48 @@ impl Stream for RelayListener { Result>, RelayError>, RelayError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if let Some(msg) = &mut self.msg_to_behaviour { - match Future::poll(msg.as_mut(), cx) { - Poll::Ready(Ok(())) => self.msg_to_behaviour = None, - Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))), - Poll::Pending => {} + loop { + if let Some(msg) = &mut self.msg_to_behaviour { + match Future::poll(msg.as_mut(), cx) { + Poll::Ready(Ok(())) => self.msg_to_behaviour = None, + Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))), + Poll::Pending => {} + } } - } - - if let Some(addr) = self.queued_new_addresses.pop_front() { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); - } - match self.from_behaviour.poll_next_unpin(cx) { - Poll::Ready(Some(ToListenerMsg::IncomingRelayedConnection { - stream, - src_peer_id, - relay_addr, - relay_peer_id: _, - })) => { - return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - upgrade: ready(Ok(stream)), - local_addr: relay_addr.with(Protocol::P2pCircuit), - remote_addr: Protocol::P2p(src_peer_id.into()).into(), - }))); + if let Some(addr) = self.queued_new_addresses.pop_front() { + return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); } - Poll::Ready(Some(ToListenerMsg::Reservation(Ok(Reservation { addrs })))) => { - let mut iter = addrs.into_iter(); - let first = iter.next(); - self.queued_new_addresses.extend(iter); - if let Some(addr) = first { - return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); + + match self.from_behaviour.poll_next_unpin(cx) { + Poll::Ready(Some(ToListenerMsg::IncomingRelayedConnection { + stream, + src_peer_id, + relay_addr, + relay_peer_id: _, + })) => { + return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { + upgrade: ready(Ok(stream)), + local_addr: relay_addr.with(Protocol::P2pCircuit), + remote_addr: Protocol::P2p(src_peer_id.into()).into(), + }))); } + Poll::Ready(Some(ToListenerMsg::Reservation(Ok(Reservation { addrs })))) => { + debug_assert!( + self.queued_new_addresses.is_empty(), + "Assert empty due to previous `pop_front` attempt." + ); + // Returned as [`ListenerEvent::NewAddress`] in next iteration of loop. + self.queued_new_addresses = addrs.into(); + } + Poll::Ready(Some(ToListenerMsg::Reservation(Err(())))) => { + return Poll::Ready(Some(Err(RelayError::Reservation))); + } + Poll::Ready(None) => { + panic!("Expect sender of `from_behaviour` not to be dropped before listener."); + } + Poll::Pending => break, } - Poll::Ready(Some(ToListenerMsg::Reservation(Err(())))) => { - return Poll::Ready(Some(Err(RelayError::Reservation))); - } - Poll::Ready(None) => { - panic!("Expect sender of `from_behaviour` not to be dropped before listener."); - } - Poll::Pending => {} } Poll::Pending From 8856fdef4f089b2e201b11c5687593ccc4b48975 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 12 Dec 2021 17:02:07 +0100 Subject: [PATCH 063/108] protocols/relay: Fix intra doc link --- protocols/relay/src/v2/relay.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 1a67ff533a1..f00faa56cbb 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -639,7 +639,7 @@ impl Add for CircuitId { } /// A [`NetworkBehaviourAction`], either complete, or still requiring data from [`PollParameters`] -/// before being returned in [`::poll`]. +/// before being returned in [`Relay::poll`]. enum Action { Done(NetworkBehaviourAction), AcceptReservationPrototype { From a1fad8c8f435fae997500ae764a22acef0091346 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 12 Dec 2021 17:03:27 +0100 Subject: [PATCH 064/108] protocols/relay/: Allow large enum variant While a good lint in itself, I don't think we need to optimize these enums without previously benchmarking its impact. --- protocols/relay/src/v2/relay.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index f00faa56cbb..bd582fad538 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -640,6 +640,7 @@ impl Add for CircuitId { /// A [`NetworkBehaviourAction`], either complete, or still requiring data from [`PollParameters`] /// before being returned in [`Relay::poll`]. +#[allow(clippy::large_enum_variant)] enum Action { Done(NetworkBehaviourAction), AcceptReservationPrototype { From 5d9f95af7b22c8cf3b2d09b295bde326f51d57ca Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 13 Dec 2021 12:49:48 +0100 Subject: [PATCH 065/108] protocols/relay/src/v2/client/transport: Remove destination address Don't include destination address when establishing relayed connection in doc example. --- protocols/relay/src/v2/client/transport.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index a6da2ed65d3..7b4cd7a96ed 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -53,12 +53,13 @@ use thiserror::Error; /// PeerId::random(), /// ); /// let transport = OrTransport::new(relay_transport, actual_transport); +/// # let relay_id = PeerId::random(); +/// # let destination_id = PeerId::random(); /// let dst_addr_via_relay = Multiaddr::empty() /// .with(Protocol::Memory(40)) // Relay address. -/// .with(Protocol::P2p(PeerId::random().into())) // Relay peer id. +/// .with(Protocol::P2p(relay_id.into())) // Relay peer id. /// .with(Protocol::P2pCircuit) // Signal to connect via relay and not directly. -/// .with(Protocol::Memory(42)) // Destination address. -/// .with(Protocol::P2p(PeerId::random().into())); // Destination peer id. +/// .with(Protocol::P2p(destination_id.into())); // Destination peer id. /// transport.dial(dst_addr_via_relay).unwrap(); /// ``` /// From eaab34e0da262b4dbebcb4ad41088c5694179b48 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 13 Dec 2021 13:07:08 +0100 Subject: [PATCH 066/108] protocols/relay/src/v2: Don't override NetworkBehaviour methods with default --- protocols/relay/src/v2/client.rs | 8 -------- protocols/relay/src/v2/relay.rs | 8 -------- 2 files changed, 16 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 674b35c6798..82626226c23 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -107,12 +107,6 @@ impl NetworkBehaviour for Client { handler::Prototype::new(self.local_peer_id) } - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { - vec![] - } - - fn inject_connected(&mut self, _peer_id: &PeerId) {} - fn inject_connection_established( &mut self, peer_id: &PeerId, @@ -171,8 +165,6 @@ impl NetworkBehaviour for Client { } } - fn inject_disconnected(&mut self, _peer: &PeerId) {} - fn inject_connection_closed( &mut self, peer_id: &PeerId, diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index bd582fad538..f51fd1147c4 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -186,14 +186,6 @@ impl NetworkBehaviour for Relay { } } - fn addresses_of_peer(&mut self, _remote_peer_id: &PeerId) -> Vec { - vec![] - } - - fn inject_connected(&mut self, _peer_id: &PeerId) {} - - fn inject_disconnected(&mut self, _peer: &PeerId) {} - fn inject_connection_closed( &mut self, peer: &PeerId, From 26aaa6e28f55a4bb8961bb67dcf3945a0c4f0a72 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 13 Dec 2021 13:23:37 +0100 Subject: [PATCH 067/108] protocols/relay/src/v2/client: Pass event at handler initialization Instead of creating a new connection and only once established pass a command to said connection, provide the command early on to the handler, thus not requiring to buffer the command within the `NetworkBehaviour` implementation. --- protocols/relay/src/v2/client.rs | 86 ++++-------------------- protocols/relay/src/v2/client/handler.rs | 19 +++++- protocols/relay/src/v2/relay.rs | 2 +- 3 files changed, 31 insertions(+), 76 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 82626226c23..39d0e18da2d 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -25,7 +25,7 @@ pub mod transport; use crate::v2::protocol::inbound_stop; use bytes::Bytes; -use futures::channel::mpsc::{Receiver, Sender}; +use futures::channel::mpsc::Receiver; use futures::channel::oneshot; use futures::future::{BoxFuture, FutureExt}; use futures::io::{AsyncRead, AsyncWrite}; @@ -35,8 +35,7 @@ use libp2p_core::connection::{ConnectedPoint, ConnectionId}; use libp2p_core::{Multiaddr, PeerId}; use libp2p_swarm::dial_opts::DialOpts; use libp2p_swarm::{ - DialError, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, - PollParameters, + NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, }; use std::collections::{HashMap, VecDeque}; use std::io::{Error, IoSlice}; @@ -77,7 +76,6 @@ pub struct Client { from_transport: Receiver, connected_peers: HashMap>, - rqsts_pending_connection: HashMap>, /// Queue of actions to return when polled. queued_actions: VecDeque>, @@ -92,7 +90,6 @@ impl Client { local_peer_id, from_transport, connected_peers: Default::default(), - rqsts_pending_connection: Default::default(), queued_actions: Default::default(), }; (transport, behaviour) @@ -104,7 +101,7 @@ impl NetworkBehaviour for Client { type OutEvent = Event; fn new_handler(&mut self) -> Self::ProtocolsHandler { - handler::Prototype::new(self.local_peer_id) + handler::Prototype::new(self.local_peer_id, None) } fn inject_connection_established( @@ -118,51 +115,6 @@ impl NetworkBehaviour for Client { .entry(*peer_id) .or_default() .push(*connection_id); - - for rqst in self - .rqsts_pending_connection - .remove(peer_id) - .map(|rqsts| rqsts.into_iter()) - .into_iter() - .flatten() - { - match rqst { - RqstPendingConnection::Reservation { to_listener, .. } => { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer_id, - handler: NotifyHandler::One(*connection_id), - event: handler::In::Reserve { to_listener }, - }); - } - RqstPendingConnection::Circuit { - send_back, - dst_peer_id, - .. - } => { - self.queued_actions - .push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer_id, - handler: NotifyHandler::One(*connection_id), - event: handler::In::EstablishCircuit { - send_back, - dst_peer_id, - }, - }); - } - } - } - } - - fn inject_dial_failure( - &mut self, - peer_id: Option, - _handler: handler::Prototype, - _error: &DialError, - ) { - if let Some(peer_id) = peer_id { - self.rqsts_pending_connection.remove(&peer_id); - } } fn inject_connection_closed( @@ -256,11 +208,10 @@ impl NetworkBehaviour for Client { }); } None => { - self.rqsts_pending_connection - .entry(relay_peer_id) - .or_default() - .push(RqstPendingConnection::Reservation { to_listener }); - let handler = self.new_handler(); + let handler = handler::Prototype::new( + self.local_peer_id, + Some(handler::In::Reserve { to_listener }), + ); return Poll::Ready(NetworkBehaviourAction::Dial { opts: DialOpts::peer_id(relay_peer_id) .addresses(vec![relay_addr]) @@ -294,14 +245,13 @@ impl NetworkBehaviour for Client { }); } None => { - self.rqsts_pending_connection - .entry(relay_peer_id) - .or_default() - .push(RqstPendingConnection::Circuit { - dst_peer_id, + let handler = handler::Prototype::new( + self.local_peer_id, + Some(handler::In::EstablishCircuit { send_back, - }); - let handler = self.new_handler(); + dst_peer_id, + }), + ); return Poll::Ready(NetworkBehaviourAction::Dial { opts: DialOpts::peer_id(relay_peer_id) .addresses(vec![relay_addr]) @@ -460,13 +410,3 @@ impl AsyncRead for RelayedConnection { } } } - -enum RqstPendingConnection { - Reservation { - to_listener: Sender, - }, - Circuit { - dst_peer_id: PeerId, - send_back: oneshot::Sender>, - }, -} diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 4a56a3b103b..17cd5f3af7e 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -90,11 +90,16 @@ pub enum Event { pub struct Prototype { local_peer_id: PeerId, + /// Initial [`In`] event from [`NetworkBehaviour`] provided at creation time. + initial_in: Option, } impl Prototype { - pub(crate) fn new(local_peer_id: PeerId) -> Self { - Self { local_peer_id } + pub(crate) fn new(local_peer_id: PeerId, initial_in: Option) -> Self { + Self { + local_peer_id, + initial_in, + } } } @@ -106,6 +111,7 @@ impl IntoProtocolsHandler for Prototype { remote_peer_id: *remote_peer_id, remote_addr: endpoint.get_remote_address().clone(), local_peer_id: self.local_peer_id, + initial_in: self.initial_in, queued_events: Default::default(), pending_error: Default::default(), reservation: None, @@ -134,6 +140,9 @@ pub struct Handler { /// Until when to keep the connection alive. keep_alive: KeepAlive, + /// Initial [`In`] event from [`NetworkBehaviour`] provided at creation time. + initial_in: Option, + /// Queue of events to return when polled. queued_events: VecDeque< ProtocolsHandlerEvent< @@ -512,6 +521,12 @@ impl ProtocolsHandler for Handler { return Poll::Ready(event); } + // See whether the [`NetworkBehaviour`] provided a first [`In`] event at handler + // construction time. + if let Some(initial_in) = self.initial_in.take() { + self.inject_event(initial_in); + } + // Check if reservation needs renewal. match self.reservation.take() { Some(Reservation::Accepted { diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index f51fd1147c4..d044ff93ec8 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -28,7 +28,7 @@ use crate::v2::protocol::inbound_hop; use instant::Instant; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; use libp2p_core::multiaddr::Protocol; -use libp2p_core::{Multiaddr, PeerId}; +use libp2p_core::PeerId; use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; use std::collections::{HashMap, HashSet, VecDeque}; use std::num::NonZeroU32; From 49a63273889b3670957bedcb2cf9562abcb5057d Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 13 Dec 2021 13:29:53 +0100 Subject: [PATCH 068/108] protocols/relay/src/v2: Use lent instead of lend --- protocols/relay/src/v2/client/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 17cd5f3af7e..c44fabc8320 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -155,7 +155,7 @@ pub struct Handler { reservation: Option, - /// Tracks substreams lend out to the transport. + /// Tracks substreams lent out to the transport. /// /// Contains a [`futures::future::Future`] for each lend out substream that /// resolves once the substream is dropped. From 2e4e74306939cc658407f7d2afa4e2ca4d2c48b1 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 13 Dec 2021 14:31:38 +0100 Subject: [PATCH 069/108] protocols/relay/v2: Use Renewing instead of Renewal --- protocols/relay/src/v2/client/handler.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index c44fabc8320..ac516c53102 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -196,7 +196,7 @@ impl ProtocolsHandler for Handler { ) { match &mut self.reservation { Some(Reservation::Accepted { pending_msgs, .. }) - | Some(Reservation::Renewal { pending_msgs, .. }) => { + | Some(Reservation::Renewing { pending_msgs, .. }) => { let src_peer_id = inbound_circuit.src_peer_id(); let (tx, rx) = oneshot::channel(); self.alive_lend_out_substreams.push(rx); @@ -235,7 +235,7 @@ impl ProtocolsHandler for Handler { ) => { let (renewal, mut pending_msgs) = match self.reservation.take() { Some(Reservation::Accepted { pending_msgs, .. }) - | Some(Reservation::Renewal { pending_msgs, .. }) => (true, pending_msgs), + | Some(Reservation::Renewing { pending_msgs, .. }) => (true, pending_msgs), None => (false, VecDeque::new()), }; @@ -535,7 +535,7 @@ impl ProtocolsHandler for Handler { to_listener, }) => match renewal_timeout.as_mut().map(|t| t.poll_unpin(cx)) { Some(Poll::Ready(())) => { - self.reservation = Some(Reservation::Renewal { pending_msgs }); + self.reservation = Some(Reservation::Renewing { pending_msgs }); return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { protocol: SubstreamProtocol::new( outbound_hop::Upgrade::Reserve, @@ -604,7 +604,6 @@ impl ProtocolsHandler for Handler { loop { match self.alive_lend_out_substreams.poll_next_unpin(cx) { Poll::Ready(Some(Err(oneshot::Canceled))) => {} - // TODO: Use void. Poll::Ready(Some(Ok(v))) => void::unreachable(v), Poll::Ready(None) | Poll::Pending => break, } @@ -631,13 +630,17 @@ impl ProtocolsHandler for Handler { } enum Reservation { + /// The Rreservation is accepted by the relay. Accepted { /// [`None`] if reservation does not expire. renewal_timeout: Option, + /// Buffer of messages to be send to the transport listener. pending_msgs: VecDeque, to_listener: mpsc::Sender, }, - Renewal { + /// The reservation is being renewed with the relay. + Renewing { + /// Buffer of messages to be send to the transport listener. pending_msgs: VecDeque, }, } From bce3ba045ffd55e76e5a161c32f40faa1af553bd Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 9 Dec 2021 09:32:12 +1100 Subject: [PATCH 070/108] protocols/relay/src/v2/client: Introduce Reservation::None This allows us to put more logic on the actual `Reservation` type, making the implementation of the handler slightly simpler. --- protocols/relay/src/v2/client/handler.rs | 230 ++++++++++++++--------- 1 file changed, 146 insertions(+), 84 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index ac516c53102..4cd2ab3fe86 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -114,7 +114,7 @@ impl IntoProtocolsHandler for Prototype { initial_in: self.initial_in, queued_events: Default::default(), pending_error: Default::default(), - reservation: None, + reservation: Reservation::None, alive_lend_out_substreams: Default::default(), circuit_deny_futs: Default::default(), send_error_futs: Default::default(), @@ -153,7 +153,7 @@ pub struct Handler { >, >, - reservation: Option, + reservation: Reservation, /// Tracks substreams lent out to the transport. /// @@ -195,8 +195,8 @@ impl ProtocolsHandler for Handler { _: Self::InboundOpenInfo, ) { match &mut self.reservation { - Some(Reservation::Accepted { pending_msgs, .. }) - | Some(Reservation::Renewing { pending_msgs, .. }) => { + Reservation::Accepted { pending_msgs, .. } + | Reservation::Renewing { pending_msgs, .. } => { let src_peer_id = inbound_circuit.src_peer_id(); let (tx, rx) = oneshot::channel(); self.alive_lend_out_substreams.push(rx); @@ -208,7 +208,7 @@ impl ProtocolsHandler for Handler { relay_addr: self.remote_addr.clone(), }); } - _ => { + Reservation::None => { let src_peer_id = inbound_circuit.src_peer_id(); self.circuit_deny_futs.push( inbound_circuit @@ -233,32 +233,15 @@ impl ProtocolsHandler for Handler { }, OutboundOpenInfo::Reserve { to_listener }, ) => { - let (renewal, mut pending_msgs) = match self.reservation.take() { - Some(Reservation::Accepted { pending_msgs, .. }) - | Some(Reservation::Renewing { pending_msgs, .. }) => (true, pending_msgs), - None => (false, VecDeque::new()), - }; - - pending_msgs.push_back(transport::ToListenerMsg::Reservation(Ok( - transport::Reservation { - addrs: addrs - .into_iter() - .map(|a| { - a.with(Protocol::P2pCircuit) - .with(Protocol::P2p(self.local_peer_id.into())) - }) - .collect(), - }, - ))); - self.reservation = Some(Reservation::Accepted { + let event = self.reservation.accepted( renewal_timeout, - pending_msgs, + addrs, to_listener, - }); + self.local_peer_id, + ); - self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - Event::ReservationReqAccepted { renewal }, - )); + self.queued_events + .push_back(ProtocolsHandlerEvent::Custom(event)); } ( outbound_hop::Output::Circuit { @@ -343,7 +326,7 @@ impl ProtocolsHandler for Handler { ) { match open_info { OutboundOpenInfo::Reserve { mut to_listener } => { - let renewal = self.reservation.take().is_some(); + let event = self.reservation.failed(); match error { ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} @@ -419,9 +402,8 @@ impl ProtocolsHandler for Handler { // Transport is notified through dropping `to_listener`. } - self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - Event::ReservationReqFailed { renewal }, - )); + self.queued_events + .push_back(ProtocolsHandlerEvent::Custom(event)); } OutboundOpenInfo::Connect { send_back } => { match error { @@ -527,57 +509,8 @@ impl ProtocolsHandler for Handler { self.inject_event(initial_in); } - // Check if reservation needs renewal. - match self.reservation.take() { - Some(Reservation::Accepted { - mut renewal_timeout, - pending_msgs, - to_listener, - }) => match renewal_timeout.as_mut().map(|t| t.poll_unpin(cx)) { - Some(Poll::Ready(())) => { - self.reservation = Some(Reservation::Renewing { pending_msgs }); - return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new( - outbound_hop::Upgrade::Reserve, - OutboundOpenInfo::Reserve { to_listener }, - ), - }); - } - Some(Poll::Pending) | None => { - self.reservation = Some(Reservation::Accepted { - renewal_timeout, - pending_msgs, - to_listener, - }); - } - }, - r => self.reservation = r, - } - - // Forward messages to transport listener. - if let Some(Reservation::Accepted { - pending_msgs, - to_listener, - .. - }) = &mut self.reservation - { - if !pending_msgs.is_empty() { - match to_listener.poll_ready(cx) { - Poll::Ready(Ok(())) => { - if let Err(e) = to_listener - .start_send(pending_msgs.pop_front().expect("Called !is_empty().")) - { - debug!("Failed to sent pending message to listener: {:?}", e); - self.reservation.take(); - } - } - Poll::Ready(Err(e)) => { - debug!("Channel to listener failed: {:?}", e); - self.reservation.take(); - } - Poll::Pending => {} - } - } + if let Poll::Ready(Some(protocol)) = self.reservation.poll(cx) { + return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { protocol }); } // Deny incoming circuit requests. @@ -610,7 +543,7 @@ impl ProtocolsHandler for Handler { } // Update keep-alive handling. - if self.reservation.is_none() + if matches!(self.reservation, Reservation::None) && self.alive_lend_out_substreams.is_empty() && self.circuit_deny_futs.is_empty() { @@ -643,6 +576,135 @@ enum Reservation { /// Buffer of messages to be send to the transport listener. pending_msgs: VecDeque, }, + None, +} + +impl Reservation { + fn accepted( + &mut self, + renewal_timeout: Option, + addrs: Vec, + to_listener: mpsc::Sender, + local_peer_id: PeerId, + ) -> Event { + let (renewal, mut pending_msgs) = match std::mem::replace(self, Self::None) { + Reservation::Accepted { pending_msgs, .. } + | Reservation::Renewing { pending_msgs, .. } => (true, pending_msgs), + Reservation::None => (false, VecDeque::new()), + }; + + pending_msgs.push_back(transport::ToListenerMsg::Reservation(Ok( + transport::Reservation { + addrs: addrs + .into_iter() + .map(|a| { + a.with(Protocol::P2pCircuit) + .with(Protocol::P2p(local_peer_id.into())) + }) + .collect(), + }, + ))); + + *self = Reservation::Accepted { + renewal_timeout, + pending_msgs, + to_listener, + }; + + Event::ReservationReqAccepted { renewal } + } + + fn failed(&mut self) -> Event { + let renewal = matches!( + self, + Reservation::Accepted { .. } | Reservation::Renewing { .. } + ); + + *self = Reservation::None; + + Event::ReservationReqFailed { renewal } + } + + fn forward_messages_to_transport_listener(&mut self, cx: &mut Context<'_>) { + if let Reservation::Accepted { + pending_msgs, + to_listener, + .. + } = self + { + if !pending_msgs.is_empty() { + match to_listener.poll_ready(cx) { + Poll::Ready(Ok(())) => { + if let Err(e) = to_listener + .start_send(pending_msgs.pop_front().expect("Called !is_empty().")) + { + debug!("Failed to sent pending message to listener: {:?}", e); + *self = Reservation::None; + } + } + Poll::Ready(Err(e)) => { + debug!("Channel to listener failed: {:?}", e); + *self = Reservation::None; + } + Poll::Pending => {} + } + } + } + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll>> { + self.forward_messages_to_transport_listener(cx); + + let (mut renewal_timeout, pending_msgs, to_listener) = + match std::mem::replace(self, Reservation::None) { + Reservation::Accepted { + renewal_timeout: Some(renewal_timeout), + pending_msgs, + to_listener, + } => (renewal_timeout, pending_msgs, to_listener), + Reservation::Accepted { + renewal_timeout: None, + pending_msgs, + to_listener, + } => { + *self = Reservation::Accepted { + renewal_timeout: None, + pending_msgs, + to_listener, + }; + return Poll::Ready(None); + } + Reservation::Renewing { pending_msgs } => { + *self = Reservation::Renewing { pending_msgs }; + return Poll::Ready(None); + } + Reservation::None => { + *self = Reservation::None; + return Poll::Ready(None); + } + }; + + if let Poll::Pending = renewal_timeout.poll_unpin(cx) { + *self = Reservation::Accepted { + renewal_timeout: Some(renewal_timeout), + pending_msgs, + to_listener, + }; + + return Poll::Pending; + } + + // if we make it this far, we are ready for renewal + + *self = Reservation::Renewing { pending_msgs }; + return Poll::Ready(Some(SubstreamProtocol::new( + outbound_hop::Upgrade::Reserve, + OutboundOpenInfo::Reserve { to_listener }, + ))); + } } pub enum OutboundOpenInfo { From 01e12f769457a6e5233107ae1c06f985f26d4863 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Mon, 13 Dec 2021 14:57:27 +0100 Subject: [PATCH 071/108] protocols/relay/src/v2: Fix intra doc link --- protocols/relay/src/v2/client/handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 4cd2ab3fe86..e3145f5fe21 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -90,7 +90,7 @@ pub enum Event { pub struct Prototype { local_peer_id: PeerId, - /// Initial [`In`] event from [`NetworkBehaviour`] provided at creation time. + /// Initial [`In`] event from [`super::Client`] provided at creation time. initial_in: Option, } @@ -140,7 +140,7 @@ pub struct Handler { /// Until when to keep the connection alive. keep_alive: KeepAlive, - /// Initial [`In`] event from [`NetworkBehaviour`] provided at creation time. + /// Initial [`In`] event from [`super::Client`] provided at creation time. initial_in: Option, /// Queue of events to return when polled. From 912d3140bb363e0933a3725e6390ef39437e4eff Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 15 Dec 2021 12:09:07 +0100 Subject: [PATCH 072/108] protocols/relay/src/v2/client/handler: Inject event at creation time --- protocols/relay/src/v2/client/handler.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index e3145f5fe21..426b4b02116 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -107,11 +107,10 @@ impl IntoProtocolsHandler for Prototype { type Handler = Handler; fn into_handler(self, remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { - Handler { + let mut handler = Handler { remote_peer_id: *remote_peer_id, remote_addr: endpoint.get_remote_address().clone(), local_peer_id: self.local_peer_id, - initial_in: self.initial_in, queued_events: Default::default(), pending_error: Default::default(), reservation: Reservation::None, @@ -119,7 +118,13 @@ impl IntoProtocolsHandler for Prototype { circuit_deny_futs: Default::default(), send_error_futs: Default::default(), keep_alive: KeepAlive::Yes, + }; + + if let Some(event) = self.initial_in { + handler.inject_event(event) } + + handler } fn inbound_protocol(&self) -> ::InboundProtocol { @@ -140,9 +145,6 @@ pub struct Handler { /// Until when to keep the connection alive. keep_alive: KeepAlive, - /// Initial [`In`] event from [`super::Client`] provided at creation time. - initial_in: Option, - /// Queue of events to return when polled. queued_events: VecDeque< ProtocolsHandlerEvent< @@ -503,12 +505,6 @@ impl ProtocolsHandler for Handler { return Poll::Ready(event); } - // See whether the [`NetworkBehaviour`] provided a first [`In`] event at handler - // construction time. - if let Some(initial_in) = self.initial_in.take() { - self.inject_event(initial_in); - } - if let Poll::Ready(Some(protocol)) = self.reservation.poll(cx) { return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { protocol }); } From dbfe25bf5872700a281d058e62b4adf822f10d56 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 14 Dec 2021 16:50:15 -0800 Subject: [PATCH 073/108] protocols/relay/src/v2/client/handler: Refactor Reservation::poll --- protocols/relay/src/v2/client/handler.rs | 68 +++++++++--------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 426b4b02116..3e65838c11a 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -654,52 +654,34 @@ impl Reservation { ) -> Poll>> { self.forward_messages_to_transport_listener(cx); - let (mut renewal_timeout, pending_msgs, to_listener) = - match std::mem::replace(self, Reservation::None) { - Reservation::Accepted { - renewal_timeout: Some(renewal_timeout), - pending_msgs, - to_listener, - } => (renewal_timeout, pending_msgs, to_listener), - Reservation::Accepted { - renewal_timeout: None, - pending_msgs, - to_listener, - } => { - *self = Reservation::Accepted { - renewal_timeout: None, - pending_msgs, - to_listener, - }; - return Poll::Ready(None); - } - Reservation::Renewing { pending_msgs } => { - *self = Reservation::Renewing { pending_msgs }; - return Poll::Ready(None); - } - Reservation::None => { - *self = Reservation::None; - return Poll::Ready(None); - } - }; - - if let Poll::Pending = renewal_timeout.poll_unpin(cx) { - *self = Reservation::Accepted { - renewal_timeout: Some(renewal_timeout), + // Check renewal timeout if any. + let (next_reservation, poll_val) = match std::mem::replace(self, Reservation::None) { + Reservation::Accepted { + renewal_timeout: Some(mut renewal_timeout), pending_msgs, to_listener, - }; - - return Poll::Pending; - } - - // if we make it this far, we are ready for renewal + } => match renewal_timeout.poll_unpin(cx) { + Poll::Ready(()) => ( + Reservation::Renewing { pending_msgs }, + Poll::Ready(Some(SubstreamProtocol::new( + outbound_hop::Upgrade::Reserve, + OutboundOpenInfo::Reserve { to_listener }, + ))), + ), + Poll::Pending => ( + Reservation::Accepted { + renewal_timeout: Some(renewal_timeout), + pending_msgs, + to_listener, + }, + Poll::Pending, + ), + }, + r => (r, Poll::Pending), + }; + *self = next_reservation; - *self = Reservation::Renewing { pending_msgs }; - return Poll::Ready(Some(SubstreamProtocol::new( - outbound_hop::Upgrade::Reserve, - OutboundOpenInfo::Reserve { to_listener }, - ))); + poll_val } } From c09861b65072dd485d09a85bc1aa7f53ac2e1f4a Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 16 Dec 2021 21:27:49 +0100 Subject: [PATCH 074/108] protocols/relay/src/v2/client/handler.rs: Fix typo Co-authored-by: Elena Frank <57632201+elenaf9@users.noreply.github.com> --- protocols/relay/src/v2/client/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 3e65838c11a..13d0da35bee 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -559,7 +559,7 @@ impl ProtocolsHandler for Handler { } enum Reservation { - /// The Rreservation is accepted by the relay. + /// The Reservation is accepted by the relay. Accepted { /// [`None`] if reservation does not expire. renewal_timeout: Option, From 5afa13bdeb3184297661d90a4150458d6ffe5a32 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 16 Dec 2021 21:28:55 +0100 Subject: [PATCH 075/108] protocols/relay/src/v2/client/transport.rs: Make doc examples consistent Co-authored-by: Elena Frank <57632201+elenaf9@users.noreply.github.com> --- protocols/relay/src/v2/client/transport.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 7b4cd7a96ed..b9aaf78c38b 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -70,14 +70,16 @@ use thiserror::Error; /// # use libp2p_core::transport::memory::MemoryTransport; /// # use libp2p_core::transport::choice::OrTransport; /// # use libp2p_relay::v2::client; +/// # let relay_id = PeerId::random(); +/// # let local_peer_id = PeerId::random(); /// let actual_transport = MemoryTransport::default(); /// let (relay_transport, behaviour) = client::Client::new_transport_and_behaviour( -/// PeerId::random(), +/// local_peer_id, /// ); /// let transport = OrTransport::new(relay_transport, actual_transport); /// let relay_addr = Multiaddr::empty() /// .with(Protocol::Memory(40)) // Relay address. -/// .with(Protocol::P2p(PeerId::random().into())) // Relay peer id. +/// .with(Protocol::P2p(relay_id.into())) // Relay peer id. /// .with(Protocol::P2pCircuit); // Signal to listen via remote relay node. /// transport.listen_on(relay_addr).unwrap(); /// ``` From 9a0f886637587910c2664d0f87ecc6897a9f29f8 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 16 Dec 2021 21:32:20 +0100 Subject: [PATCH 076/108] protocols/relay/src/v2: Stress required usage of other Transport --- protocols/relay/src/v2/client/transport.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index b9aaf78c38b..0b91a1387dc 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -36,7 +36,8 @@ use thiserror::Error; /// A [`Transport`] enabling client relay capabilities. /// -/// Needs to be combined with another protocol, e.g. via +/// Note: The transport only handles listening and dialing on relayed [`Multiaddr`], and depends on +/// an other transport to do the actual transmission of data. They should be combined through the /// [`OrTransport`](libp2p_core::transport::choice::OrTransport). /// /// Allows the local node to: @@ -89,8 +90,11 @@ pub struct ClientTransport { } impl ClientTransport { - /// Create a new [`ClientTransport`] by wrapping an existing [`Transport`] in a - /// [`ClientTransport`]. + /// Create a new [`ClientTransport`]. + /// + /// Note: The transport only handles listening and dialing on relayed [`Multiaddr`], and depends on + /// an other transport to do the actual transmission of data. They should be combined through the + /// [`OrTransport`](libp2p_core::transport::choice::OrTransport). /// /// ``` /// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, PeerId}; From a15bf3db287ca089f6689798a0cd6437b004bac0 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 16 Dec 2021 21:43:04 +0100 Subject: [PATCH 077/108] protocols/relay/src/v2/protocol/inbound_stop.rs: Remove src_peer_id pub(crate) Co-authored-by: Elena Frank <57632201+elenaf9@users.noreply.github.com> --- protocols/relay/src/v2/protocol/inbound_stop.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 4911c60f536..951acdb3d70 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -110,7 +110,7 @@ pub struct Circuit { } impl Circuit { - pub(crate) fn src_peer_id(&self) -> PeerId { + pub fn src_peer_id(&self) -> PeerId { self.src_peer_id } From 7b2eeb5468ba8b875056fdafada6950a52d8277f Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 16 Dec 2021 21:56:37 +0100 Subject: [PATCH 078/108] protocols/relay/src/v2/relay/handler: Remove wrong unused variable assignment Co-authored-by: Elena Frank <57632201+elenaf9@users.noreply.github.com> --- protocols/relay/src/v2/relay/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index beb0d384986..09c06196d72 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -431,7 +431,7 @@ impl ProtocolsHandler for Handler { fn inject_fully_negotiated_inbound( &mut self, request: >::Output, - _request_id: Self::InboundOpenInfo, + _: Self::InboundOpenInfo, ) { match request { inbound_hop::Req::Reserve(inbound_reservation_req) => { From 8e6ce38ac10ac43d194efefcdd600d28e4f87bb7 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 17 Dec 2021 13:29:22 +0100 Subject: [PATCH 079/108] protocols/relay/src/v2/client: Garbage collect unconnected nodes --- protocols/relay/src/v2/client.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 39d0e18da2d..41cabf821ea 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -37,7 +37,7 @@ use libp2p_swarm::dial_opts::DialOpts; use libp2p_swarm::{ NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, }; -use std::collections::{HashMap, VecDeque}; +use std::collections::{hash_map, HashMap, VecDeque}; use std::io::{Error, IoSlice}; use std::ops::DerefMut; use std::pin::Pin; @@ -124,13 +124,23 @@ impl NetworkBehaviour for Client { _: &ConnectedPoint, _handler: handler::Handler, ) { - self.connected_peers.get_mut(peer_id).map(|cs| { - cs.remove( - cs.iter() + match self.connected_peers.entry(*peer_id) { + hash_map::Entry::Occupied(mut connections) => { + let position = connections + .get() + .iter() .position(|c| c == connection_id) - .expect("Connection to be known."), - ) - }); + .expect("Connection to be known."); + connections.get_mut().remove(position); + + if connections.get().is_empty() { + connections.remove(); + } + } + hash_map::Entry::Vacant(_) => { + unreachable!("`inject_connection_closed` for unconnected peer.") + } + }; } fn inject_event( From 451d86c36b0cbcca637bf0b1578e891d2542c313 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 17 Dec 2021 14:55:37 +0100 Subject: [PATCH 080/108] protocols/relay/src/v2: Prevent nested relay on incoming relayed conn --- protocols/relay/src/v2/relay.rs | 28 +++++++++++++++++++------ protocols/relay/src/v2/relay/handler.rs | 20 +++++++++--------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index d044ff93ec8..c9ee35dcd6c 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -225,11 +225,17 @@ impl NetworkBehaviour for Relay { match event { handler::Event::ReservationReqReceived { inbound_reservation_req, - remote_addr, + endpoint, } => { let now = Instant::now(); + let is_from_relayed_connection = match &endpoint { + ConnectedPoint::Dialer { address } => address, + ConnectedPoint::Listener { local_addr, .. } => local_addr, + } + .iter() + .any(|p| p == Protocol::P2pCircuit); - let action = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { + let action = if is_from_relayed_connection { // Deny reservation requests over relayed circuits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), @@ -250,7 +256,9 @@ impl NetworkBehaviour for Relay { .config .reservation_rate_limiters .iter_mut() - .all(|limiter| limiter.try_next(event_source, &remote_addr, now)) + .all(|limiter| { + limiter.try_next(event_source, endpoint.get_remote_address(), now) + }) { // Deny reservation exceeding limits. NetworkBehaviourAction::NotifyHandler { @@ -334,11 +342,17 @@ impl NetworkBehaviour for Relay { } handler::Event::CircuitReqReceived { inbound_circuit_req, - remote_addr, + endpoint, } => { let now = Instant::now(); + let is_from_relayed_connection = match &endpoint { + ConnectedPoint::Dialer { address } => address, + ConnectedPoint::Listener { local_addr, .. } => local_addr, + } + .iter() + .any(|p| p == Protocol::P2pCircuit); - let action = if remote_addr.iter().any(|p| p == Protocol::P2pCircuit) { + let action = if is_from_relayed_connection { // Deny circuit requests over relayed circuit. // // An attacker could otherwise build recursive or cyclic circuits. @@ -356,7 +370,9 @@ impl NetworkBehaviour for Relay { .config .circuit_src_rate_limiters .iter_mut() - .all(|limiter| limiter.try_next(event_source, &remote_addr, now)) + .all(|limiter| { + limiter.try_next(event_source, endpoint.get_remote_address(), now) + }) { // Deny circuit exceeding limits. NetworkBehaviourAction::NotifyHandler { diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 09c06196d72..00495e35532 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -140,7 +140,7 @@ pub enum Event { /// An inbound reservation request has been received. ReservationReqReceived { inbound_reservation_req: inbound_hop::ReservationReq, - remote_addr: Multiaddr, + endpoint: ConnectedPoint, }, /// An inbound reservation request has been accepted. ReservationReqAccepted { @@ -158,7 +158,7 @@ pub enum Event { /// An inbound circuit request has been received. CircuitReqReceived { inbound_circuit_req: inbound_hop::CircuitReq, - remote_addr: Multiaddr, + endpoint: ConnectedPoint, }, /// An inbound circuit request has been denied. CircuitReqDenied { @@ -214,10 +214,10 @@ impl fmt::Debug for Event { match self { Event::ReservationReqReceived { inbound_reservation_req: _, - remote_addr, + endpoint, } => f .debug_struct("Event::ReservationReqReceived") - .field("remote_addr", remote_addr) + .field("endpoint", endpoint) .finish(), Event::ReservationReqAccepted { renewed } => f .debug_struct("Event::ReservationReqAccepted") @@ -236,11 +236,11 @@ impl fmt::Debug for Event { .finish(), Event::ReservationTimedOut {} => f.debug_struct("Event::ReservationTimedOut").finish(), Event::CircuitReqReceived { - remote_addr, + endpoint, inbound_circuit_req: _, } => f .debug_struct("Event::CircuitReqReceived") - .field("remote_addr", remote_addr) + .field("endpoint", endpoint) .finish(), Event::CircuitReqDenied { circuit_id, @@ -328,7 +328,7 @@ impl IntoProtocolsHandler for Prototype { fn into_handler(self, _remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { Handler { - remote_addr: endpoint.get_remote_address().clone(), + endpoint: endpoint.clone(), config: self.config, queued_events: Default::default(), pending_error: Default::default(), @@ -355,7 +355,7 @@ impl IntoProtocolsHandler for Prototype { /// [`ProtocolsHandler`] that manages substreams for a relay on a single /// connection with a peer. pub struct Handler { - remote_addr: Multiaddr, + endpoint: ConnectedPoint, /// Static [`Handler`] [`Config`]. config: Config, @@ -438,7 +438,7 @@ impl ProtocolsHandler for Handler { self.queued_events.push_back(ProtocolsHandlerEvent::Custom( Event::ReservationReqReceived { inbound_reservation_req, - remote_addr: self.remote_addr.clone(), + endpoint: self.endpoint.clone(), }, )); } @@ -446,7 +446,7 @@ impl ProtocolsHandler for Handler { self.queued_events.push_back(ProtocolsHandlerEvent::Custom( Event::CircuitReqReceived { inbound_circuit_req, - remote_addr: self.remote_addr.clone(), + endpoint: self.endpoint.clone(), }, )); } From a0750efd6d515da9b0eefd3fa99f7d5b0885f980 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 17 Dec 2021 16:37:41 +0100 Subject: [PATCH 081/108] protocols/relay/tests/v2: Test new reservation replacing old --- protocols/relay/tests/v2.rs | 91 +++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index 879dc8e22f2..4cc7f06580a 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -78,6 +78,97 @@ fn reservation() { )); } +#[test] +fn new_reservation_to_same_relay_replaces_old() { + let _ = env_logger::try_init(); + let mut pool = LocalPool::new(); + + let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); + let mut relay = build_relay(); + let relay_peer_id = *relay.local_peer_id(); + + relay.listen_on(relay_addr.clone()).unwrap(); + relay.add_external_address(relay_addr.clone(), AddressScore::Infinite); + spawn_swarm_on_pool(&pool, relay); + + let mut client = build_client(); + let client_peer_id = *client.local_peer_id(); + let client_addr = relay_addr + .clone() + .with(Protocol::P2p(relay_peer_id.into())) + .with(Protocol::P2pCircuit); + let client_addr_with_peer_id = client_addr + .clone() + .with(Protocol::P2p(client_peer_id.into())); + + let old_listener = client.listen_on(client_addr.clone()).unwrap(); + + // Wait for first (old) reservation. + pool.run_until(wait_for_reservation( + &mut client, + client_addr_with_peer_id.clone(), + relay_peer_id, + false, // No renewal. + )); + + // Trigger new reservation. + let new_listener = client.listen_on(client_addr.clone()).unwrap(); + + // Wait for + // - listener of old reservation to close + // - new reservation to be accepted + // - new listener address to be reported + pool.run_until(async { + let mut old_listener_closed = false; + let mut new_reservation_accepted = false; + let mut new_listener_address_reported = false; + loop { + match client.select_next_some().await { + SwarmEvent::ListenerClosed { + addresses, + listener_id, + .. + } => { + assert_eq!(addresses, vec![client_addr_with_peer_id.clone()]); + assert_eq!(listener_id, old_listener); + + old_listener_closed = true; + if new_reservation_accepted && new_listener_address_reported { + break; + } + } + SwarmEvent::Behaviour(ClientEvent::Relay( + client::Event::ReservationReqAccepted { + relay_peer_id: peer_id, + .. + }, + )) => { + assert_eq!(relay_peer_id, peer_id); + + new_reservation_accepted = true; + if old_listener_closed && new_listener_address_reported { + break; + } + } + SwarmEvent::NewListenAddr { + address, + listener_id, + } => { + assert_eq!(address, client_addr_with_peer_id); + assert_eq!(listener_id, new_listener); + + new_listener_address_reported = true; + if old_listener_closed && new_reservation_accepted { + break; + } + } + SwarmEvent::Behaviour(ClientEvent::Ping(_)) => {} + e => panic!("{:?}", e), + } + } + }); +} + #[test] fn connect() { let _ = env_logger::try_init(); From 4b90626ab232faacba28139471f7c6fba0b520ab Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 17 Dec 2021 17:00:01 +0100 Subject: [PATCH 082/108] protocols/relay/src/v2: Close listener when channel from behaviour dropped --- protocols/relay/src/v2/client/transport.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 0b91a1387dc..ab7d706a942 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -330,7 +330,8 @@ impl Stream for RelayListener { return Poll::Ready(Some(Err(RelayError::Reservation))); } Poll::Ready(None) => { - panic!("Expect sender of `from_behaviour` not to be dropped before listener."); + // Sender of `from_behaviour` has been dropped, signaling listener to close. + return Poll::Ready(None); } Poll::Pending => break, } From 45f7e218203cc17d132d253b7dad615737507a12 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 17 Dec 2021 18:08:40 +0100 Subject: [PATCH 083/108] protocols/relay/src/v2: Expose connection limit via OutEvent --- protocols/relay/src/v2/client.rs | 36 +++++++++++++++---- protocols/relay/src/v2/client/handler.rs | 32 +++++++++++++++-- protocols/relay/src/v2/protocol.rs | 18 ++++++++++ .../relay/src/v2/protocol/inbound_stop.rs | 10 ++++-- .../relay/src/v2/protocol/outbound_hop.rs | 11 ++++-- protocols/relay/tests/v2.rs | 4 +++ 6 files changed, 99 insertions(+), 12 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 41cabf821ea..f8bdfe892ef 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -23,7 +23,7 @@ mod handler; pub mod transport; -use crate::v2::protocol::inbound_stop; +use crate::v2::protocol::{self, inbound_stop}; use bytes::Bytes; use futures::channel::mpsc::Receiver; use futures::channel::oneshot; @@ -51,15 +51,25 @@ pub enum Event { relay_peer_id: PeerId, /// Indicates whether the request replaces an existing reservation. renewal: bool, + limit: Option, }, ReservationReqFailed { relay_peer_id: PeerId, /// Indicates whether the request replaces an existing reservation. renewal: bool, }, + OutboundCircuitEstablished { + relay_peer_id: PeerId, + limit: Option, + }, OutboundCircuitReqFailed { relay_peer_id: PeerId, }, + /// An inbound circuit has been established. + InboundCircuitEstablished { + src_peer_id: PeerId, + limit: Option, + }, /// An inbound circuit request has been denied. InboundCircuitReqDenied { src_peer_id: PeerId, @@ -150,21 +160,30 @@ impl NetworkBehaviour for Client { handler_event: handler::Event, ) { match handler_event { - handler::Event::ReservationReqAccepted { renewal } => { + handler::Event::ReservationReqAccepted { renewal, limit } => self + .queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::ReservationReqAccepted { + relay_peer_id: event_source, + renewal, + limit, + }, + )), + handler::Event::ReservationReqFailed { renewal } => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqAccepted { + Event::ReservationReqFailed { relay_peer_id: event_source, renewal, }, )) } - handler::Event::ReservationReqFailed { renewal } => { + handler::Event::OutboundCircuitEstablished { limit } => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqFailed { + Event::OutboundCircuitEstablished { relay_peer_id: event_source, - renewal, + limit, }, )) } @@ -176,6 +195,11 @@ impl NetworkBehaviour for Client { }, )) } + handler::Event::InboundCircuitEstablished { src_peer_id, limit } => self + .queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::InboundCircuitEstablished { src_peer_id, limit }, + )), handler::Event::InboundCircuitReqDenied { src_peer_id } => self .queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 13d0da35bee..998e1fb2ea5 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -20,7 +20,7 @@ use crate::v2::client::transport; use crate::v2::message_proto::Status; -use crate::v2::protocol::{inbound_stop, outbound_hop}; +use crate::v2::protocol::{self, inbound_stop, outbound_hop}; use futures::channel::{mpsc, oneshot}; use futures::future::{BoxFuture, FutureExt}; use futures::sink::SinkExt; @@ -71,12 +71,22 @@ pub enum Event { ReservationReqAccepted { /// Indicates whether the request replaces an existing reservation. renewal: bool, + limit: Option, }, ReservationReqFailed { /// Indicates whether the request replaces an existing reservation. renewal: bool, }, + /// An outbound circuit has been established. + OutboundCircuitEstablished { + limit: Option, + }, OutboundCircuitReqFailed {}, + /// An inbound circuit has been established. + InboundCircuitEstablished { + src_peer_id: PeerId, + limit: Option, + }, /// An inbound circuit request has been denied. InboundCircuitReqDenied { src_peer_id: PeerId, @@ -200,15 +210,25 @@ impl ProtocolsHandler for Handler { Reservation::Accepted { pending_msgs, .. } | Reservation::Renewing { pending_msgs, .. } => { let src_peer_id = inbound_circuit.src_peer_id(); + let limit = inbound_circuit.limit(); + let (tx, rx) = oneshot::channel(); self.alive_lend_out_substreams.push(rx); let connection = super::RelayedConnection::new_inbound(inbound_circuit, tx); + pending_msgs.push_back(transport::ToListenerMsg::IncomingRelayedConnection { stream: connection, src_peer_id, relay_peer_id: self.remote_peer_id, relay_addr: self.remote_addr.clone(), }); + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::InboundCircuitEstablished { + src_peer_id: self.remote_peer_id, + limit, + }, + )); } Reservation::None => { let src_peer_id = inbound_circuit.src_peer_id(); @@ -232,6 +252,7 @@ impl ProtocolsHandler for Handler { outbound_hop::Output::Reservation { renewal_timeout, addrs, + limit, }, OutboundOpenInfo::Reserve { to_listener }, ) => { @@ -240,6 +261,7 @@ impl ProtocolsHandler for Handler { addrs, to_listener, self.local_peer_id, + limit, ); self.queued_events @@ -249,6 +271,7 @@ impl ProtocolsHandler for Handler { outbound_hop::Output::Circuit { substream, read_buffer, + limit, }, OutboundOpenInfo::Connect { send_back }, ) => { @@ -265,6 +288,10 @@ impl ProtocolsHandler for Handler { self.remote_peer_id, ), } + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::OutboundCircuitEstablished { limit }, + )); } _ => unreachable!(), } @@ -582,6 +609,7 @@ impl Reservation { addrs: Vec, to_listener: mpsc::Sender, local_peer_id: PeerId, + limit: Option, ) -> Event { let (renewal, mut pending_msgs) = match std::mem::replace(self, Self::None) { Reservation::Accepted { pending_msgs, .. } @@ -607,7 +635,7 @@ impl Reservation { to_listener, }; - Event::ReservationReqAccepted { renewal } + Event::ReservationReqAccepted { renewal, limit } } fn failed(&mut self) -> Event { diff --git a/protocols/relay/src/v2/protocol.rs b/protocols/relay/src/v2/protocol.rs index b76ef6379d4..ab2dc487b6f 100644 --- a/protocols/relay/src/v2/protocol.rs +++ b/protocols/relay/src/v2/protocol.rs @@ -18,6 +18,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::v2::message_proto; +use std::time::Duration; + pub mod inbound_hop; pub mod inbound_stop; pub mod outbound_hop; @@ -27,3 +30,18 @@ const HOP_PROTOCOL_NAME: &[u8; 31] = b"/libp2p/circuit/relay/0.2.0/hop"; const STOP_PROTOCOL_NAME: &[u8; 32] = b"/libp2p/circuit/relay/0.2.0/stop"; const MAX_MESSAGE_SIZE: usize = 4096; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Limit { + duration: Option, + data_in_bytes: Option, +} + +impl From for Limit { + fn from(limit: message_proto::Limit) -> Self { + Limit { + duration: limit.duration.map(|d| Duration::from_secs(d.into())), + data_in_bytes: limit.data, + } + } +} diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 951acdb3d70..27a1fb9acde 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::v2::message_proto::{stop_message, Status, StopMessage}; -use crate::v2::protocol::{MAX_MESSAGE_SIZE, STOP_PROTOCOL_NAME}; +use crate::v2::protocol::{self, MAX_MESSAGE_SIZE, STOP_PROTOCOL_NAME}; use asynchronous_codec::{Framed, FramedParts}; use bytes::{Bytes, BytesMut}; use futures::{future::BoxFuture, prelude::*}; @@ -61,7 +61,7 @@ impl upgrade::InboundUpgrade for Upgrade { let StopMessage { r#type, peer, - limit: _, + limit, status: _, } = StopMessage::decode(Cursor::new(msg))?; @@ -75,6 +75,7 @@ impl upgrade::InboundUpgrade for Upgrade { Ok(Circuit { substream, src_peer_id, + limit: limit.map(Into::into), }) } stop_message::Type::Status => Err(UpgradeError::UnexpectedTypeStatus), @@ -107,6 +108,7 @@ pub enum UpgradeError { pub struct Circuit { substream: Framed, src_peer_id: PeerId, + limit: Option, } impl Circuit { @@ -114,6 +116,10 @@ impl Circuit { self.src_peer_id } + pub fn limit(&self) -> Option { + self.limit + } + pub async fn accept(mut self) -> Result<(NegotiatedSubstream, Bytes), std::io::Error> { let msg = StopMessage { r#type: stop_message::Type::Status.into(), diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index 2c048b86490..58d151bf0c8 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::v2::message_proto::{hop_message, HopMessage, Peer, Status}; -use crate::v2::protocol::{HOP_PROTOCOL_NAME, MAX_MESSAGE_SIZE}; +use crate::v2::protocol::{Limit, HOP_PROTOCOL_NAME, MAX_MESSAGE_SIZE}; use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::{future::BoxFuture, prelude::*}; @@ -93,7 +93,7 @@ impl upgrade::OutboundUpgrade for Upgrade { r#type, peer: _, reservation, - limit: _, + limit, status, } = HopMessage::decode(Cursor::new(msg))?; @@ -111,6 +111,8 @@ impl upgrade::OutboundUpgrade for Upgrade { s => return Err(UpgradeError::UnexpectedStatus(s)), } + let limit = limit.map(Into::into); + let output = match self { Upgrade::Reserve => { let reservation = reservation.ok_or(UpgradeError::MissingReservationField)?; @@ -126,6 +128,7 @@ impl upgrade::OutboundUpgrade for Upgrade { .map_err(|_| UpgradeError::InvalidReservationAddrs)? }; + // TODO: Use Option::transport. let renewal_timeout = if let Some(timestamp) = reservation.expire { Some( timestamp @@ -150,6 +153,7 @@ impl upgrade::OutboundUpgrade for Upgrade { Output::Reservation { renewal_timeout, addrs, + limit, } } Upgrade::Connect { .. } => { @@ -167,6 +171,7 @@ impl upgrade::OutboundUpgrade for Upgrade { Output::Circuit { substream: io, read_buffer: read_buffer.freeze(), + limit, } } }; @@ -213,9 +218,11 @@ pub enum Output { Reservation { renewal_timeout: Option, addrs: Vec, + limit: Option, }, Circuit { substream: NegotiatedSubstream, read_buffer: Bytes, + limit: Option, }, } diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index 4cc7f06580a..c203d0781ad 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -214,6 +214,9 @@ fn connect() { { break } + SwarmEvent::Behaviour(ClientEvent::Relay( + client::Event::OutboundCircuitEstablished { .. }, + )) => {} SwarmEvent::Behaviour(ClientEvent::Ping(PingEvent { peer, .. })) if peer == relay_peer_id => {} SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == dst_peer_id => { @@ -405,6 +408,7 @@ async fn wait_for_reservation( SwarmEvent::Behaviour(ClientEvent::Relay(client::Event::ReservationReqAccepted { relay_peer_id: peer_id, renewal, + .. })) if relay_peer_id == peer_id && renewal == is_renewal => { reservation_req_accepted = true; if new_listen_addr { From 1b5cb8e45b5aaae0c16927f055668cab60a7ea51 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 17 Dec 2021 18:10:52 +0100 Subject: [PATCH 084/108] protocols/relay/v2: Use Option::transpose --- protocols/relay/src/v2/protocol/outbound_hop.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index 58d151bf0c8..f55c45d64c3 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -128,9 +128,9 @@ impl upgrade::OutboundUpgrade for Upgrade { .map_err(|_| UpgradeError::InvalidReservationAddrs)? }; - // TODO: Use Option::transport. - let renewal_timeout = if let Some(timestamp) = reservation.expire { - Some( + let renewal_timeout = reservation + .expire + .map(|timestamp| { timestamp .checked_sub( SystemTime::now() @@ -142,11 +142,9 @@ impl upgrade::OutboundUpgrade for Upgrade { .and_then(|duration| duration.checked_sub(duration / 4)) .map(Duration::from_secs) .map(Delay::new) - .ok_or(UpgradeError::InvalidReservationExpiration)?, - ) - } else { - None - }; + .ok_or(UpgradeError::InvalidReservationExpiration) + }) + .transpose()?; substream.close().await?; From 2f5b8e6393286773abe66423b99583b26e2ecb49 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 17 Dec 2021 19:23:26 +0100 Subject: [PATCH 085/108] protocols/relay/src/v2: Rework outbound_hop error types --- protocols/relay/src/v2/client/handler.rs | 84 +++--------------- .../relay/src/v2/protocol/outbound_hop.rs | 86 ++++++++++++++++--- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 998e1fb2ea5..aaf1f2c55ef 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -373,45 +373,16 @@ impl ProtocolsHandler for Handler { } ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { match error { - outbound_hop::UpgradeError::Decode(_) - | outbound_hop::UpgradeError::Io(_) - | outbound_hop::UpgradeError::ParseTypeField - | outbound_hop::UpgradeError::ParseStatusField - | outbound_hop::UpgradeError::MissingStatusField - | outbound_hop::UpgradeError::MissingReservationField - | outbound_hop::UpgradeError::NoAddressesInReservation - | outbound_hop::UpgradeError::InvalidReservationExpiration - | outbound_hop::UpgradeError::InvalidReservationAddrs - | outbound_hop::UpgradeError::UnexpectedTypeConnect - | outbound_hop::UpgradeError::UnexpectedTypeReserve => { + error @ outbound_hop::UpgradeError::Fatal(_) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Apply(EitherError::B(error)), )); } - outbound_hop::UpgradeError::UnexpectedStatus(status) => { - match status { - Status::Ok => { - unreachable!( - "Status success was explicitly expected earlier." - ) - } - // With either status below there is either no reason to stay - // connected or it is a protocol violation. - // Thus terminate the connection. - Status::ConnectionFailed - | Status::NoReservation - | Status::PermissionDenied - | Status::UnexpectedMessage - | Status::MalformedMessage => { - self.pending_error = - Some(ProtocolsHandlerUpgrErr::Upgrade( - upgrade::UpgradeError::Apply(EitherError::B(error)), - )); - } - // The connection to the relay might still proof helpful CONNECT - // requests. Thus do not terminate the connection. - Status::ReservationRefused | Status::ResourceLimitExceeded => {} - } + outbound_hop::UpgradeError::ReservationFailed(_) => {} + outbound_hop::UpgradeError::CircuitFailed(_) => { + unreachable!( + "Do not emitt `CircuitFailed` for outgoing reservation." + ) } } } @@ -451,47 +422,16 @@ impl ProtocolsHandler for Handler { } ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { match error { - outbound_hop::UpgradeError::Decode(_) - | outbound_hop::UpgradeError::Io(_) - | outbound_hop::UpgradeError::ParseTypeField - | outbound_hop::UpgradeError::ParseStatusField - | outbound_hop::UpgradeError::MissingStatusField - | outbound_hop::UpgradeError::MissingReservationField - | outbound_hop::UpgradeError::NoAddressesInReservation - | outbound_hop::UpgradeError::InvalidReservationExpiration - | outbound_hop::UpgradeError::InvalidReservationAddrs - | outbound_hop::UpgradeError::UnexpectedTypeConnect - | outbound_hop::UpgradeError::UnexpectedTypeReserve => { + error @ outbound_hop::UpgradeError::Fatal(_) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Apply(EitherError::B(error)), )); } - outbound_hop::UpgradeError::UnexpectedStatus(status) => { - match status { - Status::Ok => { - unreachable!( - "Status success was explicitly expected earlier." - ) - } - // With either status below there is either no reason to stay - // connected or it is a protocol violation. - // Thus terminate the connection. - Status::ReservationRefused - | Status::UnexpectedMessage - | Status::MalformedMessage => { - self.pending_error = - Some(ProtocolsHandlerUpgrErr::Upgrade( - upgrade::UpgradeError::Apply(EitherError::B(error)), - )); - } - // While useless for reaching this particular destination, the - // connection to the relay might still proof helpful for other - // destinations. Thus do not terminate the connection. - Status::ResourceLimitExceeded - | Status::ConnectionFailed - | Status::NoReservation - | Status::PermissionDenied => {} - } + outbound_hop::UpgradeError::CircuitFailed(_) => {} + outbound_hop::UpgradeError::ReservationFailed(_) => { + unreachable!( + "Do not emitt `ReservationFailed` for outgoing circuit." + ) } } } diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index f55c45d64c3..4405281e2f1 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -97,35 +97,42 @@ impl upgrade::OutboundUpgrade for Upgrade { status, } = HopMessage::decode(Cursor::new(msg))?; - let r#type = hop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + let r#type = + hop_message::Type::from_i32(r#type).ok_or(FatalUpgradeError::ParseTypeField)?; match r#type { - hop_message::Type::Connect => return Err(UpgradeError::UnexpectedTypeConnect), - hop_message::Type::Reserve => return Err(UpgradeError::UnexpectedTypeReserve), + hop_message::Type::Connect => Err(FatalUpgradeError::UnexpectedTypeConnect)?, + hop_message::Type::Reserve => Err(FatalUpgradeError::UnexpectedTypeReserve)?, hop_message::Type::Status => {} } - let status = Status::from_i32(status.ok_or(UpgradeError::MissingStatusField)?) - .ok_or(UpgradeError::ParseStatusField)?; - match status { - Status::Ok => {} - s => return Err(UpgradeError::UnexpectedStatus(s)), - } + let status = Status::from_i32(status.ok_or(FatalUpgradeError::MissingStatusField)?) + .ok_or(FatalUpgradeError::ParseStatusField)?; let limit = limit.map(Into::into); let output = match self { Upgrade::Reserve => { - let reservation = reservation.ok_or(UpgradeError::MissingReservationField)?; + match status { + Status::Ok => {} + Status::ReservationRefused => Err(ReservationFailedReason::Refused)?, + Status::ResourceLimitExceeded => { + Err(ReservationFailedReason::ResourceLimitExceeded)? + } + s => Err(FatalUpgradeError::UnexpectedStatus(s))?, + } + + let reservation = + reservation.ok_or(FatalUpgradeError::MissingReservationField)?; let addrs = if reservation.addrs.is_empty() { - return Err(UpgradeError::NoAddressesInReservation); + Err(FatalUpgradeError::NoAddressesInReservation)? } else { reservation .addrs .into_iter() .map(TryFrom::try_from) .collect::, _>>() - .map_err(|_| UpgradeError::InvalidReservationAddrs)? + .map_err(|_| FatalUpgradeError::InvalidReservationAddrs)? }; let renewal_timeout = reservation @@ -142,7 +149,7 @@ impl upgrade::OutboundUpgrade for Upgrade { .and_then(|duration| duration.checked_sub(duration / 4)) .map(Duration::from_secs) .map(Delay::new) - .ok_or(UpgradeError::InvalidReservationExpiration) + .ok_or(FatalUpgradeError::InvalidReservationExpiration) }) .transpose()?; @@ -155,6 +162,17 @@ impl upgrade::OutboundUpgrade for Upgrade { } } Upgrade::Connect { .. } => { + match status { + Status::Ok => {} + Status::ResourceLimitExceeded => { + Err(CircuitFailedReason::ResourceLimitExceeded)? + } + Status::ConnectionFailed => Err(CircuitFailedReason::ConnectionFailed)?, + Status::NoReservation => Err(CircuitFailedReason::NoReservation)?, + Status::PermissionDenied => Err(CircuitFailedReason::PermissionDenied)?, + s => Err(FatalUpgradeError::UnexpectedStatus(s))?, + } + let FramedParts { io, read_buffer, @@ -182,6 +200,48 @@ impl upgrade::OutboundUpgrade for Upgrade { #[derive(Debug, Error)] pub enum UpgradeError { + #[error("Reservation failed")] + ReservationFailed(#[from] ReservationFailedReason), + #[error("Circuit failed")] + CircuitFailed(#[from] CircuitFailedReason), + #[error("Fatal")] + Fatal(#[from] FatalUpgradeError), +} + +impl From for UpgradeError { + fn from(error: std::io::Error) -> Self { + Self::Fatal(error.into()) + } +} + +impl From for UpgradeError { + fn from(error: prost::DecodeError) -> Self { + Self::Fatal(error.into()) + } +} + +#[derive(Debug, Error)] +pub enum CircuitFailedReason { + #[error("Remote reported resource limit exceeded.")] + ResourceLimitExceeded, + #[error("Relay failed to connect to destination.")] + ConnectionFailed, + #[error("Relay has no reservation for destination.")] + NoReservation, + #[error("Remote denied permission.")] + PermissionDenied, +} + +#[derive(Debug, Error)] +pub enum ReservationFailedReason { + #[error("Reservation refused.")] + Refused, + #[error("Remote reported resource limit exceeded.")] + ResourceLimitExceeded, +} + +#[derive(Debug, Error)] +pub enum FatalUpgradeError { #[error("Failed to decode message: {0}.")] Decode( #[from] From a056b4b8e82413aa894b5a5c229c310d61ae9f4e Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 17:58:01 +0100 Subject: [PATCH 086/108] protocols/relay/src/v2/client: Queue Event and not Action --- protocols/relay/src/v2/client.rs | 57 ++++++++++++-------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index f8bdfe892ef..5785dfb897d 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -88,7 +88,7 @@ pub struct Client { connected_peers: HashMap>, /// Queue of actions to return when polled. - queued_actions: VecDeque>, + queued_actions: VecDeque, } impl Client { @@ -162,54 +162,39 @@ impl NetworkBehaviour for Client { match handler_event { handler::Event::ReservationReqAccepted { renewal, limit } => self .queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqAccepted { - relay_peer_id: event_source, - renewal, - limit, - }, - )), + .push_back(Event::ReservationReqAccepted { + relay_peer_id: event_source, + renewal, + limit, + }), handler::Event::ReservationReqFailed { renewal } => { - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqFailed { - relay_peer_id: event_source, - renewal, - }, - )) + self.queued_actions.push_back(Event::ReservationReqFailed { + relay_peer_id: event_source, + renewal, + }) } handler::Event::OutboundCircuitEstablished { limit } => { self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::OutboundCircuitEstablished { - relay_peer_id: event_source, - limit, - }, - )) + .push_back(Event::OutboundCircuitEstablished { + relay_peer_id: event_source, + limit, + }) } handler::Event::OutboundCircuitReqFailed {} => { self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::OutboundCircuitReqFailed { - relay_peer_id: event_source, - }, - )) + .push_back(Event::OutboundCircuitReqFailed { + relay_peer_id: event_source, + }) } handler::Event::InboundCircuitEstablished { src_peer_id, limit } => self .queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::InboundCircuitEstablished { src_peer_id, limit }, - )), + .push_back(Event::InboundCircuitEstablished { src_peer_id, limit }), handler::Event::InboundCircuitReqDenied { src_peer_id } => self .queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::InboundCircuitReqDenied { src_peer_id }, - )), + .push_back(Event::InboundCircuitReqDenied { src_peer_id }), handler::Event::InboundCircuitReqDenyFailed { src_peer_id, error } => self .queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::InboundCircuitReqDenyFailed { src_peer_id, error }, - )), + .push_back(Event::InboundCircuitReqDenyFailed { src_peer_id, error }), } } @@ -219,7 +204,7 @@ impl NetworkBehaviour for Client { _poll_parameters: &mut impl PollParameters, ) -> Poll> { if let Some(event) = self.queued_actions.pop_front() { - return Poll::Ready(event); + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } loop { From 7a4cf0a970ea7c9fd5b4d89dd0c809accb7057c0 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 18:03:53 +0100 Subject: [PATCH 087/108] protocols/relay/src/v2/client: Remove loop in NetworkBehaviour::poll --- protocols/relay/src/v2/client.rs | 143 +++++++++++++++---------------- 1 file changed, 68 insertions(+), 75 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 5785dfb897d..1cc23b5c16f 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -207,91 +207,84 @@ impl NetworkBehaviour for Client { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); } - loop { - match self.from_transport.poll_next_unpin(cx) { - Poll::Ready(Some(transport::TransportToBehaviourMsg::ListenReq { - relay_peer_id, - relay_addr, - to_listener, - })) => { - match self - .connected_peers - .get(&relay_peer_id) - .and_then(|cs| cs.get(0)) - { - Some(connection_id) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id: relay_peer_id, - handler: NotifyHandler::One(*connection_id), - event: handler::In::Reserve { to_listener }, - }); - } - None => { - let handler = handler::Prototype::new( - self.local_peer_id, - Some(handler::In::Reserve { to_listener }), - ); - return Poll::Ready(NetworkBehaviourAction::Dial { - opts: DialOpts::peer_id(relay_peer_id) - .addresses(vec![relay_addr]) - .extend_addresses_through_behaviour() - .build(), - handler, - }); + let action = match ready!(self.from_transport.poll_next_unpin(cx)) { + Some(transport::TransportToBehaviourMsg::ListenReq { + relay_peer_id, + relay_addr, + to_listener, + }) => { + match self + .connected_peers + .get(&relay_peer_id) + .and_then(|cs| cs.get(0)) + { + Some(connection_id) => NetworkBehaviourAction::NotifyHandler { + peer_id: relay_peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::Reserve { to_listener }, + }, + None => { + let handler = handler::Prototype::new( + self.local_peer_id, + Some(handler::In::Reserve { to_listener }), + ); + NetworkBehaviourAction::Dial { + opts: DialOpts::peer_id(relay_peer_id) + .addresses(vec![relay_addr]) + .extend_addresses_through_behaviour() + .build(), + handler, } } } - Poll::Ready(Some(transport::TransportToBehaviourMsg::DialReq { - relay_addr, - relay_peer_id, - dst_peer_id, - send_back, - .. - })) => { - match self - .connected_peers - .get(&relay_peer_id) - .and_then(|cs| cs.get(0)) - { - Some(connection_id) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id: relay_peer_id, - handler: NotifyHandler::One(*connection_id), - event: handler::In::EstablishCircuit { - send_back, - dst_peer_id, - }, - }); - } - None => { - let handler = handler::Prototype::new( - self.local_peer_id, - Some(handler::In::EstablishCircuit { - send_back, - dst_peer_id, - }), - ); - return Poll::Ready(NetworkBehaviourAction::Dial { - opts: DialOpts::peer_id(relay_peer_id) - .addresses(vec![relay_addr]) - .extend_addresses_through_behaviour() - .build(), - handler, - }); + } + Some(transport::TransportToBehaviourMsg::DialReq { + relay_addr, + relay_peer_id, + dst_peer_id, + send_back, + .. + }) => { + match self + .connected_peers + .get(&relay_peer_id) + .and_then(|cs| cs.get(0)) + { + Some(connection_id) => NetworkBehaviourAction::NotifyHandler { + peer_id: relay_peer_id, + handler: NotifyHandler::One(*connection_id), + event: handler::In::EstablishCircuit { + send_back, + dst_peer_id, + }, + }, + None => { + let handler = handler::Prototype::new( + self.local_peer_id, + Some(handler::In::EstablishCircuit { + send_back, + dst_peer_id, + }), + ); + NetworkBehaviourAction::Dial { + opts: DialOpts::peer_id(relay_peer_id) + .addresses(vec![relay_addr]) + .extend_addresses_through_behaviour() + .build(), + handler, } } } - Poll::Ready(None) => unreachable!( - "`Relay` `NetworkBehaviour` polled after channel from \ + } + None => unreachable!( + "`Relay` `NetworkBehaviour` polled after channel from \ `RelayTransport` has been closed. Unreachable under \ the assumption that the `Client` is never polled after \ `ClientTransport` is dropped.", - ), - Poll::Pending => break, - } - } + ), + }; - Poll::Pending + return Poll::Ready(action); } } From ed79c72ae67a4acec6f8bfd58d379a5bd0b956cf Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 18:13:33 +0100 Subject: [PATCH 088/108] protocols/relay/src/v2/client/: Refactor parse_relayed_multiaddr --- protocols/relay/src/v2/client/transport.rs | 154 +++++++++------------ 1 file changed, 69 insertions(+), 85 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index ab7d706a942..c83809c3317 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -124,88 +124,76 @@ impl Transport for ClientTransport { type Dial = RelayedDial; fn listen_on(self, addr: Multiaddr) -> Result> { - match parse_relayed_multiaddr(addr)? { - // Address does not contain circuit relay protocol. - Err(addr) => return Err(TransportError::MultiaddrNotSupported(addr)), - // Address does contain circuit relay protocol. - Ok(relayed_addr) => { - let (relay_peer_id, relay_addr) = match relayed_addr { - RelayedMultiaddr { - relay_peer_id: None, - relay_addr: _, - .. - } => return Err(RelayError::MissingDstPeerId.into()), - RelayedMultiaddr { - relay_peer_id: _, - relay_addr: None, - .. - } => return Err(RelayError::MissingRelayAddr.into()), - RelayedMultiaddr { - relay_peer_id: Some(peer_id), - relay_addr: Some(addr), - .. - } => (peer_id, addr), - }; + let (relay_peer_id, relay_addr) = match parse_relayed_multiaddr(addr)? { + RelayedMultiaddr { + relay_peer_id: None, + relay_addr: _, + .. + } => return Err(RelayError::MissingDstPeerId.into()), + RelayedMultiaddr { + relay_peer_id: _, + relay_addr: None, + .. + } => return Err(RelayError::MissingRelayAddr.into()), + RelayedMultiaddr { + relay_peer_id: Some(peer_id), + relay_addr: Some(addr), + .. + } => (peer_id, addr), + }; - let (to_listener, from_behaviour) = mpsc::channel(0); - let mut to_behaviour = self.to_behaviour; - let msg_to_behaviour = Some( - async move { - to_behaviour - .send(TransportToBehaviourMsg::ListenReq { - relay_peer_id, - relay_addr, - to_listener, - }) - .await - } - .boxed(), - ); - - Ok(RelayListener { - queued_new_addresses: Default::default(), - from_behaviour, - msg_to_behaviour, - }) + let (to_listener, from_behaviour) = mpsc::channel(0); + let mut to_behaviour = self.to_behaviour; + let msg_to_behaviour = Some( + async move { + to_behaviour + .send(TransportToBehaviourMsg::ListenReq { + relay_peer_id, + relay_addr, + to_listener, + }) + .await } - } + .boxed(), + ); + + Ok(RelayListener { + queued_new_addresses: Default::default(), + from_behaviour, + msg_to_behaviour, + }) } fn dial(self, addr: Multiaddr) -> Result> { - match parse_relayed_multiaddr(addr)? { - // Address does not contain circuit relay protocol. - Err(addr) => return Err(TransportError::MultiaddrNotSupported(addr)), - // Address does contain circuit relay protocol. Dial destination via relay. - Ok(RelayedMultiaddr { - relay_peer_id, - relay_addr, - dst_peer_id, - dst_addr, - }) => { - // TODO: In the future we might want to support dialing a relay by its address only. - let relay_peer_id = relay_peer_id.ok_or(RelayError::MissingRelayPeerId)?; - let relay_addr = relay_addr.ok_or(RelayError::MissingRelayAddr)?; - let dst_peer_id = dst_peer_id.ok_or(RelayError::MissingDstPeerId)?; + let RelayedMultiaddr { + relay_peer_id, + relay_addr, + dst_peer_id, + dst_addr, + } = parse_relayed_multiaddr(addr)?; - let mut to_behaviour = self.to_behaviour; - Ok(async move { - let (tx, rx) = oneshot::channel(); - to_behaviour - .send(TransportToBehaviourMsg::DialReq { - request_id: RequestId::new(), - relay_addr, - relay_peer_id, - dst_addr, - dst_peer_id, - send_back: tx, - }) - .await?; - let stream = rx.await?.map_err(|()| RelayError::Connect)?; - Ok(stream) - } - .boxed()) - } + // TODO: In the future we might want to support dialing a relay by its address only. + let relay_peer_id = relay_peer_id.ok_or(RelayError::MissingRelayPeerId)?; + let relay_addr = relay_addr.ok_or(RelayError::MissingRelayAddr)?; + let dst_peer_id = dst_peer_id.ok_or(RelayError::MissingDstPeerId)?; + + let mut to_behaviour = self.to_behaviour; + Ok(async move { + let (tx, rx) = oneshot::channel(); + to_behaviour + .send(TransportToBehaviourMsg::DialReq { + request_id: RequestId::new(), + relay_addr, + relay_peer_id, + dst_addr, + dst_peer_id, + send_back: tx, + }) + .await?; + let stream = rx.await?.map_err(|()| RelayError::Connect)?; + Ok(stream) } + .boxed()) } fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { @@ -222,15 +210,11 @@ struct RelayedMultiaddr { } /// Parse a [`Multiaddr`] containing a [`Protocol::P2pCircuit`]. -/// -/// Returns `Ok(Err(provided_addr))` when passed address contains no [`Protocol::P2pCircuit`]. -/// -/// Returns `Err(_)` when address is malformed. fn parse_relayed_multiaddr( addr: Multiaddr, -) -> Result, RelayError> { +) -> Result> { if !addr.iter().any(|p| matches!(p, Protocol::P2pCircuit)) { - return Ok(Err(addr)); + return Err(TransportError::MultiaddrNotSupported(addr)); } let mut relayed_multiaddr = RelayedMultiaddr::default(); @@ -242,7 +226,7 @@ fn parse_relayed_multiaddr( if before_circuit { before_circuit = false; } else { - return Err(RelayError::MultipleCircuitRelayProtocolsUnsupported); + Err(RelayError::MultipleCircuitRelayProtocolsUnsupported)?; } } Protocol::P2p(hash) => { @@ -250,12 +234,12 @@ fn parse_relayed_multiaddr( if before_circuit { if relayed_multiaddr.relay_peer_id.is_some() { - return Err(RelayError::MalformedMultiaddr); + Err(RelayError::MalformedMultiaddr)?; } relayed_multiaddr.relay_peer_id = Some(peer_id) } else { if relayed_multiaddr.dst_peer_id.is_some() { - return Err(RelayError::MalformedMultiaddr); + Err(RelayError::MalformedMultiaddr)?; } relayed_multiaddr.dst_peer_id = Some(peer_id) } @@ -276,7 +260,7 @@ fn parse_relayed_multiaddr( } } - Ok(Ok(relayed_multiaddr)) + Ok(relayed_multiaddr) } pub struct RelayListener { From 0b6aca35c830401639c0a3edda18787422f0d555 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 18:23:51 +0100 Subject: [PATCH 089/108] protocols/relay/src/v2: Use ready! in RelayListener as Stream --- protocols/relay/src/v2/client/transport.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index c83809c3317..93d064aeed2 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -24,6 +24,7 @@ use crate::v2::RequestId; use futures::channel::mpsc; use futures::channel::oneshot; use futures::future::{ready, BoxFuture, Future, FutureExt, Ready}; +use futures::ready; use futures::sink::SinkExt; use futures::stream::{Stream, StreamExt}; use libp2p_core::multiaddr::{Multiaddr, Protocol}; @@ -289,20 +290,20 @@ impl Stream for RelayListener { return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); } - match self.from_behaviour.poll_next_unpin(cx) { - Poll::Ready(Some(ToListenerMsg::IncomingRelayedConnection { + match ready!(self.from_behaviour.poll_next_unpin(cx)) { + Some(ToListenerMsg::IncomingRelayedConnection { stream, src_peer_id, relay_addr, relay_peer_id: _, - })) => { + }) => { return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { upgrade: ready(Ok(stream)), local_addr: relay_addr.with(Protocol::P2pCircuit), remote_addr: Protocol::P2p(src_peer_id.into()).into(), }))); } - Poll::Ready(Some(ToListenerMsg::Reservation(Ok(Reservation { addrs })))) => { + Some(ToListenerMsg::Reservation(Ok(Reservation { addrs }))) => { debug_assert!( self.queued_new_addresses.is_empty(), "Assert empty due to previous `pop_front` attempt." @@ -310,18 +311,15 @@ impl Stream for RelayListener { // Returned as [`ListenerEvent::NewAddress`] in next iteration of loop. self.queued_new_addresses = addrs.into(); } - Poll::Ready(Some(ToListenerMsg::Reservation(Err(())))) => { + Some(ToListenerMsg::Reservation(Err(()))) => { return Poll::Ready(Some(Err(RelayError::Reservation))); } - Poll::Ready(None) => { + None => { // Sender of `from_behaviour` has been dropped, signaling listener to close. return Poll::Ready(None); } - Poll::Pending => break, } } - - Poll::Pending } } From 0e1751221c748c8f8d1bb2a0b05f7103e162a6e8 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 18:31:45 +0100 Subject: [PATCH 090/108] protocols/relay/src/v2: Remove unnecessary else --- .../relay/src/v2/protocol/outbound_hop.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index f55c45d64c3..ed1b8199619 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -117,16 +117,16 @@ impl upgrade::OutboundUpgrade for Upgrade { Upgrade::Reserve => { let reservation = reservation.ok_or(UpgradeError::MissingReservationField)?; - let addrs = if reservation.addrs.is_empty() { + if reservation.addrs.is_empty() { return Err(UpgradeError::NoAddressesInReservation); - } else { - reservation - .addrs - .into_iter() - .map(TryFrom::try_from) - .collect::, _>>() - .map_err(|_| UpgradeError::InvalidReservationAddrs)? - }; + } + + let addrs = reservation + .addrs + .into_iter() + .map(TryFrom::try_from) + .collect::, _>>() + .map_err(|_| UpgradeError::InvalidReservationAddrs)?; let renewal_timeout = reservation .expire From be9b94ec70784492c6ca23ba7300e5dd0066462d Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 18:54:05 +0100 Subject: [PATCH 091/108] protocols/relay/src/v2/protocol: Use Vec::with_capacity --- .../relay/src/v2/protocol/inbound_hop.rs | 24 +++++++++---------- .../relay/src/v2/protocol/inbound_stop.rs | 12 +++++----- .../relay/src/v2/protocol/outbound_hop.rs | 4 ++-- .../relay/src/v2/protocol/outbound_stop.rs | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 0da45c603ab..2273000b097 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -21,7 +21,7 @@ use crate::v2::message_proto::{hop_message, HopMessage, Limit, Reservation, Status}; use crate::v2::protocol::{HOP_PROTOCOL_NAME, MAX_MESSAGE_SIZE}; use asynchronous_codec::{Framed, FramedParts}; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::{future::BoxFuture, prelude::*}; use libp2p_core::{upgrade, Multiaddr, PeerId}; use libp2p_swarm::NegotiatedSubstream; @@ -54,7 +54,7 @@ impl upgrade::InboundUpgrade for Upgrade { type Future = BoxFuture<'static, Result>; fn upgrade_inbound(self, substream: NegotiatedSubstream, _: Self::Info) -> Self::Future { - let mut codec = UviBytes::::default(); + let mut codec = UviBytes::default(); codec.set_max_len(MAX_MESSAGE_SIZE); let mut substream = Framed::new(substream, codec); @@ -118,7 +118,7 @@ pub enum Req { } pub struct ReservationReq { - substream: Framed, + substream: Framed>>>, reservation_duration: Duration, max_circuit_duration: Duration, max_circuit_bytes: u64, @@ -167,10 +167,10 @@ impl ReservationReq { } async fn send(mut self, msg: HopMessage) -> Result<(), std::io::Error> { - let mut msg_bytes = BytesMut::new(); - msg.encode(&mut msg_bytes) - .expect("BytesMut to have sufficient capacity."); - self.substream.send(msg_bytes.freeze()).await?; + let mut encoded_msg = Vec::with_capacity(msg.encoded_len()); + msg.encode(&mut encoded_msg) + .expect("Vec to have sufficient capacity."); + self.substream.send(Cursor::new(encoded_msg)).await?; self.substream.flush().await?; self.substream.close().await?; @@ -180,7 +180,7 @@ impl ReservationReq { pub struct CircuitReq { dst: PeerId, - substream: Framed, + substream: Framed>>>, } impl CircuitReq { @@ -226,10 +226,10 @@ impl CircuitReq { } async fn send(&mut self, msg: HopMessage) -> Result<(), std::io::Error> { - let mut msg_bytes = BytesMut::new(); - msg.encode(&mut msg_bytes) - .expect("BytesMut to have sufficient capacity."); - self.substream.send(msg_bytes.freeze()).await?; + let mut encoded_msg = Vec::with_capacity(msg.encoded_len()); + msg.encode(&mut encoded_msg) + .expect("Vec to have sufficient capacity."); + self.substream.send(Cursor::new(encoded_msg)).await?; self.substream.flush().await?; Ok(()) diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 27a1fb9acde..392d72c2110 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -48,7 +48,7 @@ impl upgrade::InboundUpgrade for Upgrade { type Future = BoxFuture<'static, Result>; fn upgrade_inbound(self, substream: NegotiatedSubstream, _: Self::Info) -> Self::Future { - let mut codec = UviBytes::::default(); + let mut codec = UviBytes::default(); codec.set_max_len(MAX_MESSAGE_SIZE); let mut substream = Framed::new(substream, codec); @@ -106,7 +106,7 @@ pub enum UpgradeError { } pub struct Circuit { - substream: Framed, + substream: Framed>>>, src_peer_id: PeerId, limit: Option, } @@ -156,10 +156,10 @@ impl Circuit { } async fn send(&mut self, msg: StopMessage) -> Result<(), std::io::Error> { - let mut msg_bytes = BytesMut::new(); - msg.encode(&mut msg_bytes) - .expect("BytesMut to have sufficient capacity."); - self.substream.send(msg_bytes.freeze()).await?; + let mut encoded_msg = Vec::with_capacity(msg.encoded_len()); + msg.encode(&mut encoded_msg) + .expect("Vec to have sufficient capacity."); + self.substream.send(Cursor::new(encoded_msg)).await?; self.substream.flush().await?; Ok(()) diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index ed1b8199619..7d3f7ba429a 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -74,7 +74,7 @@ impl upgrade::OutboundUpgrade for Upgrade { }, }; - let mut encoded_msg = Vec::new(); + let mut encoded_msg = Vec::with_capacity(msg.encoded_len()); msg.encode(&mut encoded_msg) .expect("Vec to have sufficient capacity."); @@ -83,7 +83,7 @@ impl upgrade::OutboundUpgrade for Upgrade { let mut substream = Framed::new(substream, codec); async move { - substream.send(std::io::Cursor::new(encoded_msg)).await?; + substream.send(Cursor::new(encoded_msg)).await?; let msg: bytes::BytesMut = substream .next() .await diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs index b3de1e98383..32f60df0cbc 100644 --- a/protocols/relay/src/v2/protocol/outbound_stop.rs +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -72,7 +72,7 @@ impl upgrade::OutboundUpgrade for Upgrade { status: None, }; - let mut encoded_msg = Vec::new(); + let mut encoded_msg = Vec::with_capacity(msg.encoded_len()); msg.encode(&mut encoded_msg) .expect("Vec to have sufficient capacity."); From b9eb0f061f939210229eff85eb2e4be7c9ae10a3 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 18:58:38 +0100 Subject: [PATCH 092/108] protocols/relay/src/v2: Separate error paths and happy path --- protocols/relay/src/v2/protocol/inbound_hop.rs | 14 ++++++++------ protocols/relay/src/v2/protocol/inbound_stop.rs | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 2273000b097..22a73c239b7 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -73,20 +73,22 @@ impl upgrade::InboundUpgrade for Upgrade { } = HopMessage::decode(Cursor::new(msg))?; let r#type = hop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; - match r#type { - hop_message::Type::Reserve => Ok(Req::Reserve(ReservationReq { + let req = match r#type { + hop_message::Type::Reserve => Req::Reserve(ReservationReq { substream, reservation_duration: self.reservation_duration, max_circuit_duration: self.max_circuit_duration, max_circuit_bytes: self.max_circuit_bytes, - })), + }), hop_message::Type::Connect => { let dst = PeerId::from_bytes(&peer.ok_or(UpgradeError::MissingPeer)?.id) .map_err(|_| UpgradeError::ParsePeerId)?; - Ok(Req::Connect(CircuitReq { dst, substream })) + Req::Connect(CircuitReq { dst, substream }) } - hop_message::Type::Status => Err(UpgradeError::UnexpectedTypeStatus), - } + hop_message::Type::Status => return Err(UpgradeError::UnexpectedTypeStatus), + }; + + Ok(req) } .boxed() } diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 392d72c2110..f202dbb1342 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -21,7 +21,7 @@ use crate::v2::message_proto::{stop_message, Status, StopMessage}; use crate::v2::protocol::{self, MAX_MESSAGE_SIZE, STOP_PROTOCOL_NAME}; use asynchronous_codec::{Framed, FramedParts}; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::{future::BoxFuture, prelude::*}; use libp2p_core::{upgrade, PeerId}; use libp2p_swarm::NegotiatedSubstream; From 93691a81b48fc325f9a392e3dafbe2c2f6fd3226 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 20:25:58 +0100 Subject: [PATCH 093/108] protocols/relay/src/v2/copy_future.rs: Add unit tests - Copying from a to b and vice versa. - Checking max bytes. - Checking max duration. --- protocols/relay/src/v2/copy_future.rs | 142 ++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/protocols/relay/src/v2/copy_future.rs b/protocols/relay/src/v2/copy_future.rs index eec0ab01f6a..47652c92ed7 100644 --- a/protocols/relay/src/v2/copy_future.rs +++ b/protocols/relay/src/v2/copy_future.rs @@ -147,3 +147,145 @@ fn forward_data( Poll::Ready(Ok(i.try_into().expect("usize to fit into u64."))) } + +#[cfg(test)] +mod tests { + use super::CopyFuture; + use futures::executor::block_on; + use futures::io::{AsyncRead, AsyncWrite}; + use quickcheck::QuickCheck; + use std::io::ErrorKind; + use std::pin::Pin; + use std::task::{Context, Poll}; + use std::time::Duration; + + struct Connection { + read: Vec, + write: Vec, + } + + impl AsyncWrite for Connection { + fn poll_write( + mut self: std::pin::Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.write).poll_write(cx, buf) + } + + fn poll_flush( + mut self: std::pin::Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.write).poll_flush(cx) + } + + fn poll_close( + mut self: std::pin::Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.write).poll_close(cx) + } + } + + impl AsyncRead for Connection { + fn poll_read( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let n = std::cmp::min(self.read.len(), buf.len()); + buf[0..n].copy_from_slice(&self.read[0..n]); + self.read = self.read.split_off(n); + return Poll::Ready(Ok(n)); + } + } + + struct PendingConnection {} + + impl AsyncWrite for PendingConnection { + fn poll_write( + self: std::pin::Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + Poll::Pending + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } + + fn poll_close( + self: std::pin::Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Pending + } + } + + impl AsyncRead for PendingConnection { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &mut [u8], + ) -> Poll> { + Poll::Pending + } + } + + #[test] + fn quickcheck() { + fn prop(a: Vec, b: Vec, max_circuit_bytes: u64) { + let connection_a = Connection { + read: a.clone(), + write: Vec::new(), + }; + + let connection_b = Connection { + read: b.clone(), + write: Vec::new(), + }; + + let mut copy_future = CopyFuture::new( + connection_a, + connection_b, + Duration::from_secs(60), + max_circuit_bytes, + ); + + match block_on(&mut copy_future) { + Ok(()) => { + assert_eq!(copy_future.src.into_inner().write, b); + assert_eq!(copy_future.dst.into_inner().write, a); + } + Err(error) => { + assert_eq!(error.kind(), ErrorKind::Other); + assert_eq!(error.to_string(), "Max circuit bytes reached."); + assert!(a.len() + b.len() > max_circuit_bytes as usize); + } + } + } + + QuickCheck::new().quickcheck(prop as fn(_, _, _)) + } + + #[test] + fn max_circuit_duration() { + let copy_future = CopyFuture::new( + PendingConnection {}, + PendingConnection {}, + Duration::from_millis(1), + u64::MAX, + ); + + std::thread::sleep(Duration::from_millis(2)); + + let error = + block_on(copy_future).expect_err("Expect maximum circuit duration to be reached."); + assert_eq!(error.kind(), ErrorKind::TimedOut); + } +} From 0cdb1b6a9cf3b267dd86e0f06868bf54ea454933 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sat, 18 Dec 2021 21:05:43 +0100 Subject: [PATCH 094/108] protocols/relay/src/v2/relay: Remove unnecessary unwrap_or --- protocols/relay/src/v2/relay.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index c9ee35dcd6c..c5fbd14a2a8 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -193,10 +193,9 @@ impl NetworkBehaviour for Relay { _: &ConnectedPoint, _handler: handler::Handler, ) { - self.reservations - .get_mut(peer) - .map(|cs| cs.remove(&connection)) - .unwrap_or(false); + if let Some(connections) = self.reservations.get_mut(peer) { + connections.remove(&connection); + } for circuit in self .circuits From 040909eedcd6a6dca1cb48a6f7690f3b9406888e Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 19 Dec 2021 14:57:11 +0100 Subject: [PATCH 095/108] protocols/relay/src/v2: Use ConnectedPoint::is_relayed --- protocols/relay/src/v2/relay.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index c5fbd14a2a8..e7491a841a9 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -227,14 +227,8 @@ impl NetworkBehaviour for Relay { endpoint, } => { let now = Instant::now(); - let is_from_relayed_connection = match &endpoint { - ConnectedPoint::Dialer { address } => address, - ConnectedPoint::Listener { local_addr, .. } => local_addr, - } - .iter() - .any(|p| p == Protocol::P2pCircuit); - let action = if is_from_relayed_connection { + let action = if endpoint.is_relayed() { // Deny reservation requests over relayed circuits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), @@ -344,14 +338,8 @@ impl NetworkBehaviour for Relay { endpoint, } => { let now = Instant::now(); - let is_from_relayed_connection = match &endpoint { - ConnectedPoint::Dialer { address } => address, - ConnectedPoint::Listener { local_addr, .. } => local_addr, - } - .iter() - .any(|p| p == Protocol::P2pCircuit); - let action = if is_from_relayed_connection { + let action = if endpoint.is_relayed() { // Deny circuit requests over relayed circuit. // // An attacker could otherwise build recursive or cyclic circuits. From d5ee477b92a9342a6f2cb98462f5a58526348bcb Mon Sep 17 00:00:00 2001 From: Max Inden Date: Sun, 19 Dec 2021 15:41:03 +0100 Subject: [PATCH 096/108] protocols/relay/src/v2: Make reservation expiration required See https://github.com/libp2p/specs/pull/384/ for corresponding specification change. --- protocols/relay/src/v2/client/handler.rs | 9 +++--- protocols/relay/src/v2/message.proto | 3 +- .../relay/src/v2/protocol/inbound_hop.rs | 10 +++---- .../relay/src/v2/protocol/outbound_hop.rs | 28 ++++++++----------- 4 files changed, 21 insertions(+), 29 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 998e1fb2ea5..6b3ab4436ef 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -588,8 +588,7 @@ impl ProtocolsHandler for Handler { enum Reservation { /// The Reservation is accepted by the relay. Accepted { - /// [`None`] if reservation does not expire. - renewal_timeout: Option, + renewal_timeout: Delay, /// Buffer of messages to be send to the transport listener. pending_msgs: VecDeque, to_listener: mpsc::Sender, @@ -605,7 +604,7 @@ enum Reservation { impl Reservation { fn accepted( &mut self, - renewal_timeout: Option, + renewal_timeout: Delay, addrs: Vec, to_listener: mpsc::Sender, local_peer_id: PeerId, @@ -685,7 +684,7 @@ impl Reservation { // Check renewal timeout if any. let (next_reservation, poll_val) = match std::mem::replace(self, Reservation::None) { Reservation::Accepted { - renewal_timeout: Some(mut renewal_timeout), + mut renewal_timeout, pending_msgs, to_listener, } => match renewal_timeout.poll_unpin(cx) { @@ -698,7 +697,7 @@ impl Reservation { ), Poll::Pending => ( Reservation::Accepted { - renewal_timeout: Some(renewal_timeout), + renewal_timeout, pending_msgs, to_listener, }, diff --git a/protocols/relay/src/v2/message.proto b/protocols/relay/src/v2/message.proto index b3b16511269..0a42ce3d773 100644 --- a/protocols/relay/src/v2/message.proto +++ b/protocols/relay/src/v2/message.proto @@ -38,11 +38,10 @@ message Peer { } message Reservation { - optional uint64 expire = 1; // Unix expiration time (UTC) + required uint64 expire = 1; // Unix expiration time (UTC) repeated bytes addrs = 2; // relay addrs for reserving peer optional bytes voucher = 3; // reservation voucher } - message Limit { optional uint32 duration = 1; // seconds optional uint64 data = 2; // bytes diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 22a73c239b7..0f8994f9ead 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -133,12 +133,10 @@ impl ReservationReq { peer: None, reservation: Some(Reservation { addrs: addrs.into_iter().map(|a| a.to_vec()).collect(), - expire: Some( - (SystemTime::now() + self.reservation_duration) - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - ), + expire: (SystemTime::now() + self.reservation_duration) + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), voucher: None, }), limit: Some(Limit { diff --git a/protocols/relay/src/v2/protocol/outbound_hop.rs b/protocols/relay/src/v2/protocol/outbound_hop.rs index 7d3f7ba429a..ba1bc54f91e 100644 --- a/protocols/relay/src/v2/protocol/outbound_hop.rs +++ b/protocols/relay/src/v2/protocol/outbound_hop.rs @@ -130,21 +130,17 @@ impl upgrade::OutboundUpgrade for Upgrade { let renewal_timeout = reservation .expire - .map(|timestamp| { - timestamp - .checked_sub( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - ) - // Renew the reservation after 3/4 of the reservation expiration timestamp. - .and_then(|duration| duration.checked_sub(duration / 4)) - .map(Duration::from_secs) - .map(Delay::new) - .ok_or(UpgradeError::InvalidReservationExpiration) - }) - .transpose()?; + .checked_sub( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ) + // Renew the reservation after 3/4 of the reservation expiration timestamp. + .and_then(|duration| duration.checked_sub(duration / 4)) + .map(Duration::from_secs) + .map(Delay::new) + .ok_or(UpgradeError::InvalidReservationExpiration)?; substream.close().await?; @@ -214,7 +210,7 @@ pub enum UpgradeError { pub enum Output { Reservation { - renewal_timeout: Option, + renewal_timeout: Delay, addrs: Vec, limit: Option, }, From 36276108fcfe21c48087bdffbc333991d27fe251 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 21 Dec 2021 16:09:24 +0100 Subject: [PATCH 097/108] protocols/relay/src/v2: Refactor inbound_stop error handling --- protocols/relay/src/v2/client.rs | 43 ++++--- protocols/relay/src/v2/client/handler.rs | 106 ++++++++++++------ .../relay/src/v2/protocol/inbound_stop.rs | 26 ++++- 3 files changed, 125 insertions(+), 50 deletions(-) diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index f8bdfe892ef..18790d0101d 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -23,7 +23,7 @@ mod handler; pub mod transport; -use crate::v2::protocol::{self, inbound_stop}; +use crate::v2::protocol::{self, inbound_stop, outbound_hop}; use bytes::Bytes; use futures::channel::mpsc::Receiver; use futures::channel::oneshot; @@ -36,6 +36,7 @@ use libp2p_core::{Multiaddr, PeerId}; use libp2p_swarm::dial_opts::DialOpts; use libp2p_swarm::{ NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, + ProtocolsHandlerUpgrErr, }; use std::collections::{hash_map, HashMap, VecDeque}; use std::io::{Error, IoSlice}; @@ -57,6 +58,7 @@ pub enum Event { relay_peer_id: PeerId, /// Indicates whether the request replaces an existing reservation. renewal: bool, + error: ProtocolsHandlerUpgrErr, }, OutboundCircuitEstablished { relay_peer_id: PeerId, @@ -64,16 +66,19 @@ pub enum Event { }, OutboundCircuitReqFailed { relay_peer_id: PeerId, + error: ProtocolsHandlerUpgrErr, }, /// An inbound circuit has been established. InboundCircuitEstablished { src_peer_id: PeerId, limit: Option, }, - /// An inbound circuit request has been denied. - InboundCircuitReqDenied { - src_peer_id: PeerId, + InboundCircuitReqFailed { + relay_peer_id: PeerId, + error: ProtocolsHandlerUpgrErr, }, + /// An inbound circuit request has been denied. + InboundCircuitReqDenied { src_peer_id: PeerId }, /// Denying an inbound circuit request failed. InboundCircuitReqDenyFailed { src_peer_id: PeerId, @@ -169,15 +174,15 @@ impl NetworkBehaviour for Client { limit, }, )), - handler::Event::ReservationReqFailed { renewal } => { - self.queued_actions - .push_back(NetworkBehaviourAction::GenerateEvent( - Event::ReservationReqFailed { - relay_peer_id: event_source, - renewal, - }, - )) - } + handler::Event::ReservationReqFailed { renewal, error } => self + .queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::ReservationReqFailed { + relay_peer_id: event_source, + renewal, + error, + }, + )), handler::Event::OutboundCircuitEstablished { limit } => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( @@ -187,11 +192,12 @@ impl NetworkBehaviour for Client { }, )) } - handler::Event::OutboundCircuitReqFailed {} => { + handler::Event::OutboundCircuitReqFailed { error } => { self.queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( Event::OutboundCircuitReqFailed { relay_peer_id: event_source, + error, }, )) } @@ -200,6 +206,15 @@ impl NetworkBehaviour for Client { .push_back(NetworkBehaviourAction::GenerateEvent( Event::InboundCircuitEstablished { src_peer_id, limit }, )), + handler::Event::InboundCircuitReqFailed { error } => { + self.queued_actions + .push_back(NetworkBehaviourAction::GenerateEvent( + Event::InboundCircuitReqFailed { + relay_peer_id: event_source, + error, + }, + )) + } handler::Event::InboundCircuitReqDenied { src_peer_id } => self .queued_actions .push_back(NetworkBehaviourAction::GenerateEvent( diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index aaf1f2c55ef..66e7d77a067 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -76,21 +76,24 @@ pub enum Event { ReservationReqFailed { /// Indicates whether the request replaces an existing reservation. renewal: bool, + error: ProtocolsHandlerUpgrErr, }, /// An outbound circuit has been established. - OutboundCircuitEstablished { - limit: Option, + OutboundCircuitEstablished { limit: Option }, + OutboundCircuitReqFailed { + error: ProtocolsHandlerUpgrErr, }, - OutboundCircuitReqFailed {}, /// An inbound circuit has been established. InboundCircuitEstablished { src_peer_id: PeerId, limit: Option, }, - /// An inbound circuit request has been denied. - InboundCircuitReqDenied { - src_peer_id: PeerId, + /// An inbound circuit request has failed. + InboundCircuitReqFailed { + error: ProtocolsHandlerUpgrErr, }, + /// An inbound circuit request has been denied. + InboundCircuitReqDenied { src_peer_id: PeerId }, /// Denying an inbound circuit request failed. InboundCircuitReqDenyFailed { src_peer_id: PeerId, @@ -149,7 +152,7 @@ pub struct Handler { /// A pending fatal error that results in the connection being closed. pending_error: Option< ProtocolsHandlerUpgrErr< - EitherError, + EitherError, >, >, /// Until when to keep the connection alive. @@ -190,7 +193,7 @@ impl ProtocolsHandler for Handler { type InEvent = In; type OutEvent = Event; type Error = ProtocolsHandlerUpgrErr< - EitherError, + EitherError, >; type InboundProtocol = inbound_stop::Upgrade; type OutboundProtocol = outbound_hop::Upgrade; @@ -328,24 +331,37 @@ impl ProtocolsHandler for Handler { _: Self::InboundOpenInfo, error: ProtocolsHandlerUpgrErr<::Error>, ) { - match error { - ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + let non_fatal_error = match error { + ProtocolsHandlerUpgrErr::Timeout => ProtocolsHandlerUpgrErr::Timeout, + ProtocolsHandlerUpgrErr::Timer => ProtocolsHandlerUpgrErr::Timer, ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::Failed, - )) => {} + )) => ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )), ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::ProtocolError(e), )) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Select(upgrade::NegotiationError::ProtocolError(e)), )); + return; } - ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply( + inbound_stop::UpgradeError::Fatal(error), + )) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Apply(EitherError::A(error)), - )) + )); + return; } - } + }; + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::InboundCircuitReqFailed { + error: non_fatal_error, + }, + )); } fn inject_dial_upgrade_error( @@ -355,13 +371,14 @@ impl ProtocolsHandler for Handler { ) { match open_info { OutboundOpenInfo::Reserve { mut to_listener } => { - let event = self.reservation.failed(); - - match error { - ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + let non_fatal_error = match error { + ProtocolsHandlerUpgrErr::Timeout => ProtocolsHandlerUpgrErr::Timeout, + ProtocolsHandlerUpgrErr::Timer => ProtocolsHandlerUpgrErr::Timer, ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::Failed, - )) => {} + )) => ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )), ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::ProtocolError(e), )) => { @@ -370,15 +387,21 @@ impl ProtocolsHandler for Handler { upgrade::NegotiationError::ProtocolError(e), ), )); + return; } ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { match error { - error @ outbound_hop::UpgradeError::Fatal(_) => { + outbound_hop::UpgradeError::Fatal(error) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Apply(EitherError::B(error)), )); + return; + } + outbound_hop::UpgradeError::ReservationFailed(error) => { + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply( + error, + )) } - outbound_hop::UpgradeError::ReservationFailed(_) => {} outbound_hop::UpgradeError::CircuitFailed(_) => { unreachable!( "Do not emitt `CircuitFailed` for outgoing reservation." @@ -386,7 +409,7 @@ impl ProtocolsHandler for Handler { } } } - } + }; if self.pending_error.is_none() { self.send_error_futs.push( @@ -402,15 +425,23 @@ impl ProtocolsHandler for Handler { // Transport is notified through dropping `to_listener`. } - self.queued_events - .push_back(ProtocolsHandlerEvent::Custom(event)); + let renewal = self.reservation.failed(); + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::ReservationReqFailed { + renewal, + error: non_fatal_error, + }, + )); } OutboundOpenInfo::Connect { send_back } => { - match error { - ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + let non_fatal_error = match error { + ProtocolsHandlerUpgrErr::Timeout => ProtocolsHandlerUpgrErr::Timeout, + ProtocolsHandlerUpgrErr::Timer => ProtocolsHandlerUpgrErr::Timer, ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::Failed, - )) => {} + )) => ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )), ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::ProtocolError(e), )) => { @@ -419,15 +450,21 @@ impl ProtocolsHandler for Handler { upgrade::NegotiationError::ProtocolError(e), ), )); + return; } ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { match error { - error @ outbound_hop::UpgradeError::Fatal(_) => { + outbound_hop::UpgradeError::Fatal(error) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Apply(EitherError::B(error)), )); + return; + } + outbound_hop::UpgradeError::CircuitFailed(error) => { + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply( + error, + )) } - outbound_hop::UpgradeError::CircuitFailed(_) => {} outbound_hop::UpgradeError::ReservationFailed(_) => { unreachable!( "Do not emitt `ReservationFailed` for outgoing circuit." @@ -440,7 +477,9 @@ impl ProtocolsHandler for Handler { let _ = send_back.send(Err(())); self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - Event::OutboundCircuitReqFailed {}, + Event::OutboundCircuitReqFailed { + error: non_fatal_error, + }, )); } } @@ -578,7 +617,10 @@ impl Reservation { Event::ReservationReqAccepted { renewal, limit } } - fn failed(&mut self) -> Event { + /// Marks the current reservation as failed. + /// + /// Returns whether the reservation request was a renewal. + fn failed(&mut self) -> bool { let renewal = matches!( self, Reservation::Accepted { .. } | Reservation::Renewing { .. } @@ -586,7 +628,7 @@ impl Reservation { *self = Reservation::None; - Event::ReservationReqFailed { renewal } + renewal } fn forward_messages_to_transport_listener(&mut self, cx: &mut Context<'_>) { diff --git a/protocols/relay/src/v2/protocol/inbound_stop.rs b/protocols/relay/src/v2/protocol/inbound_stop.rs index 27a1fb9acde..6050edb07a2 100644 --- a/protocols/relay/src/v2/protocol/inbound_stop.rs +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -66,19 +66,19 @@ impl upgrade::InboundUpgrade for Upgrade { } = StopMessage::decode(Cursor::new(msg))?; let r#type = - stop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + stop_message::Type::from_i32(r#type).ok_or(FatalUpgradeError::ParseTypeField)?; match r#type { stop_message::Type::Connect => { let src_peer_id = - PeerId::from_bytes(&peer.ok_or(UpgradeError::MissingPeer)?.id) - .map_err(|_| UpgradeError::ParsePeerId)?; + PeerId::from_bytes(&peer.ok_or(FatalUpgradeError::MissingPeer)?.id) + .map_err(|_| FatalUpgradeError::ParsePeerId)?; Ok(Circuit { substream, src_peer_id, limit: limit.map(Into::into), }) } - stop_message::Type::Status => Err(UpgradeError::UnexpectedTypeStatus), + stop_message::Type::Status => Err(FatalUpgradeError::UnexpectedTypeStatus)?, } } .boxed() @@ -87,6 +87,24 @@ impl upgrade::InboundUpgrade for Upgrade { #[derive(Debug, Error)] pub enum UpgradeError { + #[error("Fatal")] + Fatal(#[from] FatalUpgradeError), +} + +impl From for UpgradeError { + fn from(error: prost::DecodeError) -> Self { + Self::Fatal(error.into()) + } +} + +impl From for UpgradeError { + fn from(error: std::io::Error) -> Self { + Self::Fatal(error.into()) + } +} + +#[derive(Debug, Error)] +pub enum FatalUpgradeError { #[error("Failed to decode message: {0}.")] Decode( #[from] From 9a4ad189fc1b01136f850d601c50619d4fe2fb08 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 21 Dec 2021 16:43:32 +0100 Subject: [PATCH 098/108] protocols/relay/src/v2: Refactor outbound_stop error handling --- .../relay/src/v2/protocol/outbound_stop.rs | 40 +++++++-- protocols/relay/src/v2/relay.rs | 22 ++++- protocols/relay/src/v2/relay/handler.rs | 82 ++++++++----------- 3 files changed, 90 insertions(+), 54 deletions(-) diff --git a/protocols/relay/src/v2/protocol/outbound_stop.rs b/protocols/relay/src/v2/protocol/outbound_stop.rs index b3de1e98383..f21616cf935 100644 --- a/protocols/relay/src/v2/protocol/outbound_stop.rs +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -95,17 +95,19 @@ impl upgrade::OutboundUpgrade for Upgrade { } = StopMessage::decode(Cursor::new(msg))?; let r#type = - stop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + stop_message::Type::from_i32(r#type).ok_or(FatalUpgradeError::ParseTypeField)?; match r#type { - stop_message::Type::Connect => return Err(UpgradeError::UnexpectedTypeConnect), + stop_message::Type::Connect => Err(FatalUpgradeError::UnexpectedTypeConnect)?, stop_message::Type::Status => {} } - let status = Status::from_i32(status.ok_or(UpgradeError::MissingStatusField)?) - .ok_or(UpgradeError::ParseStatusField)?; + let status = Status::from_i32(status.ok_or(FatalUpgradeError::MissingStatusField)?) + .ok_or(FatalUpgradeError::ParseStatusField)?; match status { Status::Ok => {} - s => return Err(UpgradeError::UnexpectedStatus(s)), + Status::ResourceLimitExceeded => Err(CircuitFailedReason::ResourceLimitExceeded)?, + Status::PermissionDenied => Err(CircuitFailedReason::PermissionDenied)?, + s => Err(FatalUpgradeError::UnexpectedStatus(s))?, } let FramedParts { @@ -127,6 +129,34 @@ impl upgrade::OutboundUpgrade for Upgrade { #[derive(Debug, Error)] pub enum UpgradeError { + #[error("Circuit failed")] + CircuitFailed(#[from] CircuitFailedReason), + #[error("Fatal")] + Fatal(#[from] FatalUpgradeError), +} + +impl From for UpgradeError { + fn from(error: std::io::Error) -> Self { + Self::Fatal(error.into()) + } +} + +impl From for UpgradeError { + fn from(error: prost::DecodeError) -> Self { + Self::Fatal(error.into()) + } +} + +#[derive(Debug, Error)] +pub enum CircuitFailedReason { + #[error("Remote reported resource limit exceeded.")] + ResourceLimitExceeded, + #[error("Remote reported permission denied.")] + PermissionDenied, +} + +#[derive(Debug, Error)] +pub enum FatalUpgradeError { #[error("Failed to decode message: {0}.")] Decode( #[from] diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index c9ee35dcd6c..e2240ea2a0e 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -29,13 +29,18 @@ use instant::Instant; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; use libp2p_core::multiaddr::Protocol; use libp2p_core::PeerId; -use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; +use libp2p_swarm::{ + NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, + ProtocolsHandlerUpgrErr, +}; use std::collections::{HashMap, HashSet, VecDeque}; use std::num::NonZeroU32; use std::ops::Add; use std::task::{Context, Poll}; use std::time::Duration; +use super::protocol::outbound_stop; + /// Configuration for the [`Relay`] [`NetworkBehaviour`]. /// /// # Panics @@ -132,6 +137,12 @@ pub enum Event { src_peer_id: PeerId, dst_peer_id: PeerId, }, + /// An outbound connect for an inbound cirucit request failed. + CircuitReqOutboundConnectFailed { + src_peer_id: PeerId, + dst_peer_id: PeerId, + error: ProtocolsHandlerUpgrErr, + }, /// Accepting an inbound circuit request failed. CircuitReqAcceptFailed { src_peer_id: PeerId, @@ -489,6 +500,7 @@ impl NetworkBehaviour for Relay { src_connection_id, inbound_circuit_req, status, + error, } => { self.queued_actions.push_back( NetworkBehaviourAction::NotifyHandler { @@ -502,6 +514,14 @@ impl NetworkBehaviour for Relay { } .into(), ); + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitReqOutboundConnectFailed { + src_peer_id, + dst_peer_id: event_source, + error, + }) + .into(), + ); } handler::Event::CircuitReqAccepted { dst_peer_id, diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 00495e35532..e4f92bafc5a 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -200,6 +200,7 @@ pub enum Event { src_connection_id: ConnectionId, inbound_circuit_req: inbound_hop::CircuitReq, status: Status, + error: ProtocolsHandlerUpgrErr, }, /// An inbound circuit has closed. CircuitClosed { @@ -298,12 +299,14 @@ impl fmt::Debug for Event { src_connection_id, inbound_circuit_req: _, status, + error, } => f .debug_struct("Event::OutboundConnectNegotiationFailed") .field("circuit_id", circuit_id) .field("src_peer_id", src_peer_id) .field("src_connection_id", src_connection_id) .field("status", status) + .field("error", error) .finish(), Event::CircuitClosed { circuit_id, @@ -373,7 +376,7 @@ pub struct Handler { /// A pending fatal error that results in the connection being closed. pending_error: Option< ProtocolsHandlerUpgrErr< - EitherError, + EitherError, >, >, @@ -410,7 +413,7 @@ impl ProtocolsHandler for Handler { type InEvent = In; type OutEvent = Event; type Error = ProtocolsHandlerUpgrErr< - EitherError, + EitherError, >; type InboundProtocol = inbound_hop::Upgrade; type OutboundProtocol = outbound_stop::Upgrade; @@ -592,9 +595,12 @@ impl ProtocolsHandler for Handler { open_info: Self::OutboundOpenInfo, error: ProtocolsHandlerUpgrErr<::Error>, ) { - let status = match error { - ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => { - Status::ConnectionFailed + let (non_fatal_error, status) = match error { + ProtocolsHandlerUpgrErr::Timeout => { + (ProtocolsHandlerUpgrErr::Timeout, Status::ConnectionFailed) + } + ProtocolsHandlerUpgrErr::Timer => { + (ProtocolsHandlerUpgrErr::Timer, Status::ConnectionFailed) } ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::Failed, @@ -604,7 +610,7 @@ impl ProtocolsHandler for Handler { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Select(upgrade::NegotiationError::Failed), )); - Status::ConnectionFailed + return; } ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::ProtocolError(e), @@ -612,51 +618,30 @@ impl ProtocolsHandler for Handler { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Select(upgrade::NegotiationError::ProtocolError(e)), )); - Status::ConnectionFailed + return; } - ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { - match error { - outbound_stop::UpgradeError::Decode(_) - | outbound_stop::UpgradeError::Io(_) - | outbound_stop::UpgradeError::ParseTypeField - | outbound_stop::UpgradeError::MissingStatusField - | outbound_stop::UpgradeError::ParseStatusField - | outbound_stop::UpgradeError::UnexpectedTypeConnect => { - self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( - upgrade::UpgradeError::Apply(EitherError::B(error)), - )); - Status::ConnectionFailed - } - outbound_stop::UpgradeError::UnexpectedStatus(status) => { - match status { - Status::Ok => { - unreachable!("Status success is explicitly exempt.") - } - // A destination node returning nonsensical status is a protocol - // violation. Thus terminate the connection. - Status::ReservationRefused - | Status::NoReservation - | Status::ConnectionFailed => { - self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( - upgrade::UpgradeError::Apply(EitherError::B(error)), - )); - } - // With either status below there is no reason to stay connected. - // Thus terminate the connection. - Status::MalformedMessage | Status::UnexpectedMessage => { - self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( - upgrade::UpgradeError::Apply(EitherError::B(error)), - )) - } - // While useless for reaching this particular destination, the - // connection to the relay might still prove helpful for other - // destinations. Thus do not terminate the connection. - Status::ResourceLimitExceeded | Status::PermissionDenied => {} + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => match error { + outbound_stop::UpgradeError::Fatal(error) => { + self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( + upgrade::UpgradeError::Apply(EitherError::B(error)), + )); + return; + } + outbound_stop::UpgradeError::CircuitFailed(error) => { + let status = match error { + outbound_stop::CircuitFailedReason::ResourceLimitExceeded => { + Status::ResourceLimitExceeded } - status - } + outbound_stop::CircuitFailedReason::PermissionDenied => { + Status::PermissionDenied + } + }; + ( + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)), + status, + ) } - } + }, }; let OutboundOpenInfo { @@ -673,6 +658,7 @@ impl ProtocolsHandler for Handler { src_connection_id, inbound_circuit_req, status, + error: non_fatal_error, }, )); } From 2aa6e94c1f8aec4f15117b46dcd560e55953f246 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 21 Dec 2021 16:54:12 +0100 Subject: [PATCH 099/108] protocols/relay/src/v2: Refactor inbound_hop error handling --- .../relay/src/v2/protocol/inbound_hop.rs | 27 ++++++++++++-- protocols/relay/src/v2/relay.rs | 13 +++++++ protocols/relay/src/v2/relay/handler.rs | 37 +++++++++++++++---- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/protocols/relay/src/v2/protocol/inbound_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs index 0da45c603ab..b9fc2ec569f 100644 --- a/protocols/relay/src/v2/protocol/inbound_hop.rs +++ b/protocols/relay/src/v2/protocol/inbound_hop.rs @@ -72,7 +72,8 @@ impl upgrade::InboundUpgrade for Upgrade { status: _, } = HopMessage::decode(Cursor::new(msg))?; - let r#type = hop_message::Type::from_i32(r#type).ok_or(UpgradeError::ParseTypeField)?; + let r#type = + hop_message::Type::from_i32(r#type).ok_or(FatalUpgradeError::ParseTypeField)?; match r#type { hop_message::Type::Reserve => Ok(Req::Reserve(ReservationReq { substream, @@ -81,11 +82,11 @@ impl upgrade::InboundUpgrade for Upgrade { max_circuit_bytes: self.max_circuit_bytes, })), hop_message::Type::Connect => { - let dst = PeerId::from_bytes(&peer.ok_or(UpgradeError::MissingPeer)?.id) - .map_err(|_| UpgradeError::ParsePeerId)?; + let dst = PeerId::from_bytes(&peer.ok_or(FatalUpgradeError::MissingPeer)?.id) + .map_err(|_| FatalUpgradeError::ParsePeerId)?; Ok(Req::Connect(CircuitReq { dst, substream })) } - hop_message::Type::Status => Err(UpgradeError::UnexpectedTypeStatus), + hop_message::Type::Status => Err(FatalUpgradeError::UnexpectedTypeStatus)?, } } .boxed() @@ -94,6 +95,24 @@ impl upgrade::InboundUpgrade for Upgrade { #[derive(Debug, Error)] pub enum UpgradeError { + #[error("Fatal")] + Fatal(#[from] FatalUpgradeError), +} + +impl From for UpgradeError { + fn from(error: prost::DecodeError) -> Self { + Self::Fatal(error.into()) + } +} + +impl From for UpgradeError { + fn from(error: std::io::Error) -> Self { + Self::Fatal(error.into()) + } +} + +#[derive(Debug, Error)] +pub enum FatalUpgradeError { #[error("Failed to decode message: {0}.")] Decode( #[from] diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index e2240ea2a0e..b1d29937abf 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -121,6 +121,10 @@ pub enum Event { }, /// An inbound reservation has timed out. ReservationTimedOut { src_peer_id: PeerId }, + CircuitReqReceiveFailed { + src_peer_id: PeerId, + error: ProtocolsHandlerUpgrErr, + }, /// An inbound circuit request has been denied. CircuitReqDenied { src_peer_id: PeerId, @@ -435,6 +439,15 @@ impl NetworkBehaviour for Relay { }; self.queued_actions.push_back(action.into()); } + handler::Event::CircuitReqReceiveFailed { error } => { + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitReqReceiveFailed { + src_peer_id: event_source, + error, + }) + .into(), + ); + } handler::Event::CircuitReqDenied { circuit_id, dst_peer_id, diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index e4f92bafc5a..2c3fafa7e68 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -160,6 +160,10 @@ pub enum Event { inbound_circuit_req: inbound_hop::CircuitReq, endpoint: ConnectedPoint, }, + /// Receiving an inbound circuit request failed. + CircuitReqReceiveFailed { + error: ProtocolsHandlerUpgrErr, + }, /// An inbound circuit request has been denied. CircuitReqDenied { circuit_id: Option, @@ -243,6 +247,10 @@ impl fmt::Debug for Event { .debug_struct("Event::CircuitReqReceived") .field("endpoint", endpoint) .finish(), + Event::CircuitReqReceiveFailed { error } => f + .debug_struct("Event::CircuitReqReceiveFailed") + .field("error", error) + .finish(), Event::CircuitReqDenied { circuit_id, dst_peer_id, @@ -376,7 +384,7 @@ pub struct Handler { /// A pending fatal error that results in the connection being closed. pending_error: Option< ProtocolsHandlerUpgrErr< - EitherError, + EitherError, >, >, @@ -413,7 +421,7 @@ impl ProtocolsHandler for Handler { type InEvent = In; type OutEvent = Event; type Error = ProtocolsHandlerUpgrErr< - EitherError, + EitherError, >; type InboundProtocol = inbound_hop::Upgrade; type OutboundProtocol = outbound_stop::Upgrade; @@ -570,24 +578,37 @@ impl ProtocolsHandler for Handler { _: Self::InboundOpenInfo, error: ProtocolsHandlerUpgrErr<::Error>, ) { - match error { - ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {} + let non_fatal_error = match error { + ProtocolsHandlerUpgrErr::Timeout => ProtocolsHandlerUpgrErr::Timeout, + ProtocolsHandlerUpgrErr::Timer => ProtocolsHandlerUpgrErr::Timer, ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::Failed, - )) => {} + )) => ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( + upgrade::NegotiationError::Failed, + )), ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select( upgrade::NegotiationError::ProtocolError(e), )) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Select(upgrade::NegotiationError::ProtocolError(e)), )); + return; } - ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => { + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply( + inbound_hop::UpgradeError::Fatal(error), + )) => { self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade( upgrade::UpgradeError::Apply(EitherError::A(error)), - )) + )); + return; } - } + }; + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::CircuitReqReceiveFailed { + error: non_fatal_error, + }, + )); } fn inject_dial_upgrade_error( From 6916384a900be45ac8aa5ff81c9c7b513317646e Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 21 Dec 2021 17:00:17 +0100 Subject: [PATCH 100/108] misc/metrics/src/relay: Update to error handling refactoring --- misc/metrics/src/relay.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/misc/metrics/src/relay.rs b/misc/metrics/src/relay.rs index dece3a7f294..db2b682b9d6 100644 --- a/misc/metrics/src/relay.rs +++ b/misc/metrics/src/relay.rs @@ -54,8 +54,10 @@ enum EventType { ReservationReqDenied, ReservationReqDenyFailed, ReservationTimedOut, + CircuitReqReceiveFailed, CircuitReqDenied, CircuitReqDenyFailed, + CircuitReqOutboundConnectFailed, CircuitReqAccepted, CircuitReqAcceptFailed, CircuitClosed, @@ -79,7 +81,13 @@ impl From<&libp2p_relay::v2::relay::Event> for EventType { libp2p_relay::v2::relay::Event::ReservationTimedOut { .. } => { EventType::ReservationTimedOut } + libp2p_relay::v2::relay::Event::CircuitReqReceiveFailed { .. } => { + EventType::CircuitReqReceiveFailed + } libp2p_relay::v2::relay::Event::CircuitReqDenied { .. } => EventType::CircuitReqDenied, + libp2p_relay::v2::relay::Event::CircuitReqOutboundConnectFailed { .. } => { + EventType::CircuitReqOutboundConnectFailed + } libp2p_relay::v2::relay::Event::CircuitReqDenyFailed { .. } => { EventType::CircuitReqDenyFailed } From 8aa386739dbb73d5f59461acc7cc8e6945b39f91 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sun, 19 Dec 2021 17:04:20 +1100 Subject: [PATCH 101/108] Separate control flow as much as possible --- protocols/relay/src/v2/client/transport.rs | 46 ++++++++++++---------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/protocols/relay/src/v2/client/transport.rs b/protocols/relay/src/v2/client/transport.rs index 93d064aeed2..6128f25bcc7 100644 --- a/protocols/relay/src/v2/client/transport.rs +++ b/protocols/relay/src/v2/client/transport.rs @@ -290,35 +290,39 @@ impl Stream for RelayListener { return Poll::Ready(Some(Ok(ListenerEvent::NewAddress(addr)))); } - match ready!(self.from_behaviour.poll_next_unpin(cx)) { - Some(ToListenerMsg::IncomingRelayedConnection { - stream, - src_peer_id, - relay_addr, - relay_peer_id: _, - }) => { - return Poll::Ready(Some(Ok(ListenerEvent::Upgrade { - upgrade: ready(Ok(stream)), - local_addr: relay_addr.with(Protocol::P2pCircuit), - remote_addr: Protocol::P2p(src_peer_id.into()).into(), - }))); + let msg = match ready!(self.from_behaviour.poll_next_unpin(cx)) { + Some(msg) => msg, + None => { + // Sender of `from_behaviour` has been dropped, signaling listener to close. + return Poll::Ready(None); } - Some(ToListenerMsg::Reservation(Ok(Reservation { addrs }))) => { + }; + + let result = match msg { + ToListenerMsg::Reservation(Ok(Reservation { addrs })) => { debug_assert!( self.queued_new_addresses.is_empty(), "Assert empty due to previous `pop_front` attempt." ); // Returned as [`ListenerEvent::NewAddress`] in next iteration of loop. self.queued_new_addresses = addrs.into(); + + continue; } - Some(ToListenerMsg::Reservation(Err(()))) => { - return Poll::Ready(Some(Err(RelayError::Reservation))); - } - None => { - // Sender of `from_behaviour` has been dropped, signaling listener to close. - return Poll::Ready(None); - } - } + ToListenerMsg::IncomingRelayedConnection { + stream, + src_peer_id, + relay_addr, + relay_peer_id: _, + } => Ok(ListenerEvent::Upgrade { + upgrade: ready(Ok(stream)), + local_addr: relay_addr.with(Protocol::P2pCircuit), + remote_addr: Protocol::P2p(src_peer_id.into()).into(), + }), + ToListenerMsg::Reservation(Err(())) => Err(RelayError::Reservation), + }; + + return Poll::Ready(Some(result)); } } } From ca5aaedd9b1b53c189d611a9c3e92822e31fc42c Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 21 Dec 2021 18:02:49 +0100 Subject: [PATCH 102/108] protocols/relay/src/v2: Implement max_reservations_per_peer --- protocols/relay/src/v2/relay.rs | 23 ++++++++++++++++++++++- protocols/relay/src/v2/relay/handler.rs | 5 +++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index e7491a841a9..339c952263f 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -43,6 +43,7 @@ use std::time::Duration; /// [`Config::max_circuit_duration`] may not exceed [`u32::MAX`]. pub struct Config { pub max_reservations: usize, + pub max_reservations_per_peer: usize, pub reservation_duration: Duration, pub reservation_rate_limiters: Vec>, @@ -82,6 +83,7 @@ impl Default for Config { Config { max_reservations: 128, + max_reservations_per_peer: 4, reservation_duration: Duration::from_secs(60 * 60), reservation_rate_limiters, @@ -225,6 +227,7 @@ impl NetworkBehaviour for Relay { handler::Event::ReservationReqReceived { inbound_reservation_req, endpoint, + renewed, } => { let now = Instant::now(); @@ -253,7 +256,25 @@ impl NetworkBehaviour for Relay { limiter.try_next(event_source, endpoint.get_remote_address(), now) }) { - // Deny reservation exceeding limits. + // Deny reservation exceeding general limits. + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(connection), + peer_id: event_source, + event: handler::In::DenyReservationReq { + inbound_reservation_req, + status: message_proto::Status::ResourceLimitExceeded, + }, + } + .into() + } else if !renewed + && self + .reservations + .get(&event_source) + .map(|cs| cs.len()) + .unwrap_or(0) + > self.config.max_reservations_per_peer + { + // Deny reservation exceeding per-peer limits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 00495e35532..c2da362d2e7 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -141,6 +141,8 @@ pub enum Event { ReservationReqReceived { inbound_reservation_req: inbound_hop::ReservationReq, endpoint: ConnectedPoint, + /// Indicates whether the request replaces an existing reservation. + renewed: bool, }, /// An inbound reservation request has been accepted. ReservationReqAccepted { @@ -215,9 +217,11 @@ impl fmt::Debug for Event { Event::ReservationReqReceived { inbound_reservation_req: _, endpoint, + renewed, } => f .debug_struct("Event::ReservationReqReceived") .field("endpoint", endpoint) + .field("renewed", renewed) .finish(), Event::ReservationReqAccepted { renewed } => f .debug_struct("Event::ReservationReqAccepted") @@ -439,6 +443,7 @@ impl ProtocolsHandler for Handler { Event::ReservationReqReceived { inbound_reservation_req, endpoint: self.endpoint.clone(), + renewed: self.active_reservation.is_some(), }, )); } From 48f7f6880390591c56ce47ed7543d94d4a6f4a32 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 21 Dec 2021 18:10:39 +0100 Subject: [PATCH 103/108] protocols/relay/src/v2: Implement max_circuits_per_peer --- protocols/relay/src/v2/relay.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 339c952263f..195e8394c38 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -48,6 +48,7 @@ pub struct Config { pub reservation_rate_limiters: Vec>, pub max_circuits: usize, + pub max_circuits_per_peer: usize, pub max_circuit_duration: Duration, pub max_circuit_bytes: u64, pub circuit_src_rate_limiters: Vec>, @@ -88,6 +89,7 @@ impl Default for Config { reservation_rate_limiters, max_circuits: 16, + max_circuits_per_peer: 4, max_circuit_duration: Duration::from_secs(2 * 60), max_circuit_bytes: 1 << 17, // 128 kibibyte circuit_src_rate_limiters, @@ -373,7 +375,9 @@ impl NetworkBehaviour for Relay { status: message_proto::Status::PermissionDenied, }, } - } else if self.circuits.len() >= self.config.max_circuits + } else if self.circuits.num_circuits_of_peer(event_source) + > self.config.max_circuits_per_peer + || self.circuits.len() >= self.config.max_circuits || !self .config .circuit_src_rate_limiters @@ -626,6 +630,13 @@ impl CircuitsTracker { removed } + + fn num_circuits_of_peer(&self, peer: PeerId) -> usize { + self.circuits + .iter() + .filter(|(_, c)| c.src_peer_id == peer || c.dst_peer_id == peer) + .count() + } } #[derive(Clone)] From 58daa474e6e58d9e10873af35c584373f8231081 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 21 Dec 2021 18:20:03 +0100 Subject: [PATCH 104/108] protocols/relay/src/v2: Deny duplicate reservation denial --- protocols/relay/src/v2/relay.rs | 42 +++++++++++++-------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index 195e8394c38..fd06532b546 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -244,12 +244,23 @@ impl NetworkBehaviour for Relay { }, } .into() - } else if self - .reservations - .iter() - .map(|(_, cs)| cs.len()) - .sum::() - >= self.config.max_reservations + } else if + // Deny if it is a new reservation and exceeds `max_reservations_per_peer`. + (!renewed + && self + .reservations + .get(&event_source) + .map(|cs| cs.len()) + .unwrap_or(0) + > self.config.max_reservations_per_peer) + // Deny if it exceeds `max_reservations`. + || self + .reservations + .iter() + .map(|(_, cs)| cs.len()) + .sum::() + >= self.config.max_reservations + // Deny if it exceeds the allowed rate of reservations. || !self .config .reservation_rate_limiters @@ -258,25 +269,6 @@ impl NetworkBehaviour for Relay { limiter.try_next(event_source, endpoint.get_remote_address(), now) }) { - // Deny reservation exceeding general limits. - NetworkBehaviourAction::NotifyHandler { - handler: NotifyHandler::One(connection), - peer_id: event_source, - event: handler::In::DenyReservationReq { - inbound_reservation_req, - status: message_proto::Status::ResourceLimitExceeded, - }, - } - .into() - } else if !renewed - && self - .reservations - .get(&event_source) - .map(|cs| cs.len()) - .unwrap_or(0) - > self.config.max_reservations_per_peer - { - // Deny reservation exceeding per-peer limits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, From 3f8e772516686361a6928ccf827e3274b898ee75 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Wed, 29 Dec 2021 16:11:40 +0100 Subject: [PATCH 105/108] protocols/relay/src/v2: Use DummyProtocolsHandler on relayed connection --- protocols/relay/Cargo.toml | 1 + protocols/relay/src/v2/client.rs | 73 +++++++++++--------- protocols/relay/src/v2/client/handler.rs | 54 +++++++++------ protocols/relay/src/v2/relay.rs | 85 +++++++++++------------- protocols/relay/src/v2/relay/handler.rs | 39 ++++++----- 5 files changed, 141 insertions(+), 111 deletions(-) diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index b534321153c..8e7ca1b3726 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -13,6 +13,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] asynchronous-codec = "0.6" bytes = "1" +either = "1.6.0" futures = "0.3.1" futures-timer = "3" instant = "0.1.11" diff --git a/protocols/relay/src/v2/client.rs b/protocols/relay/src/v2/client.rs index 871ec12f68d..e0459768879 100644 --- a/protocols/relay/src/v2/client.rs +++ b/protocols/relay/src/v2/client.rs @@ -25,6 +25,7 @@ pub mod transport; use crate::v2::protocol::{self, inbound_stop, outbound_hop}; use bytes::Bytes; +use either::Either; use futures::channel::mpsc::Receiver; use futures::channel::oneshot; use futures::future::{BoxFuture, FutureExt}; @@ -34,6 +35,7 @@ use futures::stream::StreamExt; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; use libp2p_core::{Multiaddr, PeerId}; use libp2p_swarm::dial_opts::DialOpts; +use libp2p_swarm::protocols_handler::DummyProtocolsHandler; use libp2p_swarm::{ NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, ProtocolsHandlerUpgrErr, @@ -90,7 +92,9 @@ pub struct Client { local_peer_id: PeerId, from_transport: Receiver, - connected_peers: HashMap>, + /// Set of directly connected peers, i.e. not connected via a relayed + /// connection. + directly_connected_peers: HashMap>, /// Queue of actions to return when polled. queued_actions: VecDeque, @@ -104,7 +108,7 @@ impl Client { let behaviour = Client { local_peer_id, from_transport, - connected_peers: Default::default(), + directly_connected_peers: Default::default(), queued_actions: Default::default(), }; (transport, behaviour) @@ -123,47 +127,56 @@ impl NetworkBehaviour for Client { &mut self, peer_id: &PeerId, connection_id: &ConnectionId, - _: &ConnectedPoint, + endpoint: &ConnectedPoint, _failed_addresses: Option<&Vec>, ) { - self.connected_peers - .entry(*peer_id) - .or_default() - .push(*connection_id); + if !endpoint.is_relayed() { + self.directly_connected_peers + .entry(*peer_id) + .or_default() + .push(*connection_id); + } } fn inject_connection_closed( &mut self, peer_id: &PeerId, connection_id: &ConnectionId, - _: &ConnectedPoint, - _handler: handler::Handler, + endpoint: &ConnectedPoint, + _handler: Either, ) { - match self.connected_peers.entry(*peer_id) { - hash_map::Entry::Occupied(mut connections) => { - let position = connections - .get() - .iter() - .position(|c| c == connection_id) - .expect("Connection to be known."); - connections.get_mut().remove(position); + if !endpoint.is_relayed() { + match self.directly_connected_peers.entry(*peer_id) { + hash_map::Entry::Occupied(mut connections) => { + let position = connections + .get() + .iter() + .position(|c| c == connection_id) + .expect("Connection to be known."); + connections.get_mut().remove(position); - if connections.get().is_empty() { - connections.remove(); + if connections.get().is_empty() { + connections.remove(); + } } - } - hash_map::Entry::Vacant(_) => { - unreachable!("`inject_connection_closed` for unconnected peer.") - } - }; + hash_map::Entry::Vacant(_) => { + unreachable!("`inject_connection_closed` for unconnected peer.") + } + }; + } } fn inject_event( &mut self, event_source: PeerId, _connection: ConnectionId, - handler_event: handler::Event, + handler_event: Either, ) { + let handler_event = match handler_event { + Either::Left(e) => e, + Either::Right(v) => void::unreachable(v), + }; + match handler_event { handler::Event::ReservationReqAccepted { renewal, limit } => self .queued_actions @@ -228,14 +241,14 @@ impl NetworkBehaviour for Client { to_listener, }) => { match self - .connected_peers + .directly_connected_peers .get(&relay_peer_id) .and_then(|cs| cs.get(0)) { Some(connection_id) => NetworkBehaviourAction::NotifyHandler { peer_id: relay_peer_id, handler: NotifyHandler::One(*connection_id), - event: handler::In::Reserve { to_listener }, + event: Either::Left(handler::In::Reserve { to_listener }), }, None => { let handler = handler::Prototype::new( @@ -260,17 +273,17 @@ impl NetworkBehaviour for Client { .. }) => { match self - .connected_peers + .directly_connected_peers .get(&relay_peer_id) .and_then(|cs| cs.get(0)) { Some(connection_id) => NetworkBehaviourAction::NotifyHandler { peer_id: relay_peer_id, handler: NotifyHandler::One(*connection_id), - event: handler::In::EstablishCircuit { + event: Either::Left(handler::In::EstablishCircuit { send_back, dst_peer_id, - }, + }), }, None => { let handler = handler::Prototype::new( diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 6e7ca346d54..779a32e2eab 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -21,6 +21,7 @@ use crate::v2::client::transport; use crate::v2::message_proto::Status; use crate::v2::protocol::{self, inbound_stop, outbound_hop}; +use either::Either; use futures::channel::{mpsc, oneshot}; use futures::future::{BoxFuture, FutureExt}; use futures::sink::SinkExt; @@ -30,7 +31,9 @@ use instant::Instant; use libp2p_core::either::EitherError; use libp2p_core::multiaddr::Protocol; use libp2p_core::{upgrade, ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::protocols_handler::{InboundUpgradeSend, OutboundUpgradeSend}; +use libp2p_swarm::protocols_handler::{ + DummyProtocolsHandler, InboundUpgradeSend, OutboundUpgradeSend, SendWrapper, +}; use libp2p_swarm::{ IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, @@ -117,31 +120,44 @@ impl Prototype { } impl IntoProtocolsHandler for Prototype { - type Handler = Handler; + type Handler = Either; fn into_handler(self, remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { - let mut handler = Handler { - remote_peer_id: *remote_peer_id, - remote_addr: endpoint.get_remote_address().clone(), - local_peer_id: self.local_peer_id, - queued_events: Default::default(), - pending_error: Default::default(), - reservation: Reservation::None, - alive_lend_out_substreams: Default::default(), - circuit_deny_futs: Default::default(), - send_error_futs: Default::default(), - keep_alive: KeepAlive::Yes, - }; + if endpoint.is_relayed() { + if let Some(event) = self.initial_in { + debug!( + "Established relayed instead of direct connection to {:?}, \ + dropping initial in event {:?}.", + remote_peer_id, event + ); + } - if let Some(event) = self.initial_in { - handler.inject_event(event) - } + // Deny all substreams on relayed connection. + Either::Right(DummyProtocolsHandler::default()) + } else { + let mut handler = Handler { + remote_peer_id: *remote_peer_id, + remote_addr: endpoint.get_remote_address().clone(), + local_peer_id: self.local_peer_id, + queued_events: Default::default(), + pending_error: Default::default(), + reservation: Reservation::None, + alive_lend_out_substreams: Default::default(), + circuit_deny_futs: Default::default(), + send_error_futs: Default::default(), + keep_alive: KeepAlive::Yes, + }; + + if let Some(event) = self.initial_in { + handler.inject_event(event) + } - handler + Either::Left(handler) + } } fn inbound_protocol(&self) -> ::InboundProtocol { - inbound_stop::Upgrade {} + upgrade::EitherUpgrade::A(SendWrapper(inbound_stop::Upgrade {})) } } diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs index e546dfa4d9b..eae734f3c3e 100644 --- a/protocols/relay/src/v2/relay.rs +++ b/protocols/relay/src/v2/relay.rs @@ -25,10 +25,12 @@ pub mod rate_limiter; use crate::v2::message_proto; use crate::v2::protocol::inbound_hop; +use either::Either; use instant::Instant; use libp2p_core::connection::{ConnectedPoint, ConnectionId}; use libp2p_core::multiaddr::Protocol; use libp2p_core::PeerId; +use libp2p_swarm::protocols_handler::DummyProtocolsHandler; use libp2p_swarm::{ NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, ProtocolsHandlerUpgrErr, @@ -210,7 +212,7 @@ impl NetworkBehaviour for Relay { peer: &PeerId, connection: &ConnectionId, _: &ConnectedPoint, - _handler: handler::Handler, + _handler: Either, ) { if let Some(connections) = self.reservations.get_mut(peer) { connections.remove(&connection); @@ -238,8 +240,13 @@ impl NetworkBehaviour for Relay { &mut self, event_source: PeerId, connection: ConnectionId, - event: handler::Event, + event: Either, ) { + let event = match event { + Either::Left(e) => e, + Either::Right(v) => void::unreachable(v), + }; + match event { handler::Event::ReservationReqReceived { inbound_reservation_req, @@ -248,18 +255,13 @@ impl NetworkBehaviour for Relay { } => { let now = Instant::now(); - let action = if endpoint.is_relayed() { - // Deny reservation requests over relayed circuits. - NetworkBehaviourAction::NotifyHandler { - handler: NotifyHandler::One(connection), - peer_id: event_source, - event: handler::In::DenyReservationReq { - inbound_reservation_req, - status: message_proto::Status::PermissionDenied, - }, - } - .into() - } else if + assert!( + !endpoint.is_relayed(), + "`DummyProtocolsHandler` handles relayed connections. It \ + denies all inbound substreams." + ); + + let action = if // Deny if it is a new reservation and exceeds `max_reservations_per_peer`. (!renewed && self @@ -282,15 +284,14 @@ impl NetworkBehaviour for Relay { .iter_mut() .all(|limiter| { limiter.try_next(event_source, endpoint.get_remote_address(), now) - }) - { + }) { NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, - event: handler::In::DenyReservationReq { + event: Either::Left(handler::In::DenyReservationReq { inbound_reservation_req, status: message_proto::Status::ResourceLimitExceeded, - }, + }), } .into() } else { @@ -369,20 +370,13 @@ impl NetworkBehaviour for Relay { } => { let now = Instant::now(); - let action = if endpoint.is_relayed() { - // Deny circuit requests over relayed circuit. - // - // An attacker could otherwise build recursive or cyclic circuits. - NetworkBehaviourAction::NotifyHandler { - handler: NotifyHandler::One(connection), - peer_id: event_source, - event: handler::In::DenyCircuitReq { - circuit_id: None, - inbound_circuit_req, - status: message_proto::Status::PermissionDenied, - }, - } - } else if self.circuits.num_circuits_of_peer(event_source) + assert!( + !endpoint.is_relayed(), + "`DummyProtocolsHandler` handles relayed connections. It \ + denies all inbound substreams." + ); + + let action = if self.circuits.num_circuits_of_peer(event_source) > self.config.max_circuits_per_peer || self.circuits.len() >= self.config.max_circuits || !self @@ -391,17 +385,16 @@ impl NetworkBehaviour for Relay { .iter_mut() .all(|limiter| { limiter.try_next(event_source, endpoint.get_remote_address(), now) - }) - { + }) { // Deny circuit exceeding limits. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, - event: handler::In::DenyCircuitReq { + event: Either::Left(handler::In::DenyCircuitReq { circuit_id: None, inbound_circuit_req, status: message_proto::Status::ResourceLimitExceeded, - }, + }), } } else if let Some(dst_conn) = self .reservations @@ -421,24 +414,24 @@ impl NetworkBehaviour for Relay { NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(*dst_conn), peer_id: event_source, - event: handler::In::NegotiateOutboundConnect { + event: Either::Left(handler::In::NegotiateOutboundConnect { circuit_id, inbound_circuit_req, relay_peer_id: self.local_peer_id, src_peer_id: event_source, src_connection_id: connection, - }, + }), } } else { // Deny circuit request if no reservation present. NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(connection), peer_id: event_source, - event: handler::In::DenyCircuitReq { + event: Either::Left(handler::In::DenyCircuitReq { circuit_id: None, inbound_circuit_req, status: message_proto::Status::NoReservation, - }, + }), } }; self.queued_actions.push_back(action.into()); @@ -499,14 +492,14 @@ impl NetworkBehaviour for Relay { NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(src_connection_id), peer_id: src_peer_id, - event: handler::In::AcceptAndDriveCircuit { + event: Either::Left(handler::In::AcceptAndDriveCircuit { circuit_id, dst_peer_id: event_source, inbound_circuit_req, dst_handler_notifier, dst_stream, dst_pending_data, - }, + }), } .into(), ); @@ -523,11 +516,11 @@ impl NetworkBehaviour for Relay { NetworkBehaviourAction::NotifyHandler { handler: NotifyHandler::One(src_connection_id), peer_id: src_peer_id, - event: handler::In::DenyCircuitReq { + event: Either::Left(handler::In::DenyCircuitReq { circuit_id: Some(circuit_id), inbound_circuit_req, status, - }, + }), } .into(), ); @@ -722,7 +715,7 @@ impl Action { } => NetworkBehaviourAction::NotifyHandler { handler, peer_id, - event: handler::In::AcceptReservationReq { + event: Either::Left(handler::In::AcceptReservationReq { inbound_reservation_req, addrs: poll_parameters .external_addresses() @@ -731,7 +724,7 @@ impl Action { .with(Protocol::P2p((*poll_parameters.local_peer_id()).into())) }) .collect(), - }, + }), }, } } diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs index 0945f63456f..547469797dd 100644 --- a/protocols/relay/src/v2/relay/handler.rs +++ b/protocols/relay/src/v2/relay/handler.rs @@ -23,6 +23,7 @@ use crate::v2::message_proto::Status; use crate::v2::protocol::{inbound_hop, outbound_stop}; use crate::v2::relay::CircuitId; use bytes::Bytes; +use either::Either; use futures::channel::oneshot::{self, Canceled}; use futures::future::{BoxFuture, FutureExt, TryFutureExt}; use futures::io::AsyncWriteExt; @@ -32,6 +33,7 @@ use instant::Instant; use libp2p_core::connection::ConnectionId; use libp2p_core::either::EitherError; use libp2p_core::{upgrade, ConnectedPoint, Multiaddr, PeerId}; +use libp2p_swarm::protocols_handler::{DummyProtocolsHandler, SendWrapper}; use libp2p_swarm::protocols_handler::{InboundUpgradeSend, OutboundUpgradeSend}; use libp2p_swarm::{ IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, ProtocolsHandlerEvent, @@ -339,31 +341,36 @@ pub struct Prototype { } impl IntoProtocolsHandler for Prototype { - type Handler = Handler; + type Handler = Either; fn into_handler(self, _remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { - Handler { - endpoint: endpoint.clone(), - config: self.config, - queued_events: Default::default(), - pending_error: Default::default(), - reservation_accept_futures: Default::default(), - reservation_deny_futures: Default::default(), - circuit_accept_futures: Default::default(), - circuit_deny_futures: Default::default(), - alive_lend_out_substreams: Default::default(), - circuits: Default::default(), - active_reservation: Default::default(), - keep_alive: KeepAlive::Yes, + if endpoint.is_relayed() { + // Deny all substreams on relayed connection. + Either::Right(DummyProtocolsHandler::default()) + } else { + Either::Left(Handler { + endpoint: endpoint.clone(), + config: self.config, + queued_events: Default::default(), + pending_error: Default::default(), + reservation_accept_futures: Default::default(), + reservation_deny_futures: Default::default(), + circuit_accept_futures: Default::default(), + circuit_deny_futures: Default::default(), + alive_lend_out_substreams: Default::default(), + circuits: Default::default(), + active_reservation: Default::default(), + keep_alive: KeepAlive::Yes, + }) } } fn inbound_protocol(&self) -> ::InboundProtocol { - inbound_hop::Upgrade { + upgrade::EitherUpgrade::A(SendWrapper(inbound_hop::Upgrade { reservation_duration: self.config.reservation_duration, max_circuit_duration: self.config.max_circuit_duration, max_circuit_bytes: self.config.max_circuit_bytes, - } + })) } } From 80a9e76b83363c385eb026f9a071962253353678 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 14 Jan 2022 10:46:28 +0100 Subject: [PATCH 106/108] protocols/relay/tests/v2: Use wait_for_dial to ensure connection reuse --- protocols/relay/tests/v2.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs index c203d0781ad..ea4b8b8fb3c 100644 --- a/protocols/relay/tests/v2.rs +++ b/protocols/relay/tests/v2.rs @@ -59,6 +59,9 @@ fn reservation() { client.listen_on(client_addr.clone()).unwrap(); + // Wait for connection to relay. + assert!(pool.run_until(wait_for_dial(&mut client, relay_peer_id))); + // Wait for initial reservation. pool.run_until(wait_for_reservation( &mut client, @@ -103,6 +106,9 @@ fn new_reservation_to_same_relay_replaces_old() { let old_listener = client.listen_on(client_addr.clone()).unwrap(); + // Wait for connection to relay. + assert!(pool.run_until(wait_for_dial(&mut client, relay_peer_id))); + // Wait for first (old) reservation. pool.run_until(wait_for_reservation( &mut client, @@ -192,6 +198,8 @@ fn connect() { dst.listen_on(dst_addr.clone()).unwrap(); + assert!(pool.run_until(wait_for_dial(&mut dst, relay_peer_id))); + pool.run_until(wait_for_reservation( &mut dst, dst_addr.clone(), @@ -272,6 +280,7 @@ fn reuse_connection() { assert!(pool.run_until(wait_for_dial(&mut client, relay_peer_id))); client.listen_on(client_addr.clone()).unwrap(); + pool.run_until(wait_for_reservation( &mut client, client_addr.with(Protocol::P2p(client_peer_id.into())), @@ -422,25 +431,20 @@ async fn wait_for_reservation( } } SwarmEvent::Behaviour(ClientEvent::Ping(_)) => {} - SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} - SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => {} e => panic!("{:?}", e), } } } -async fn wait_for_dial(client: &mut Swarm, relay_peer_id: PeerId) -> bool { +async fn wait_for_dial(client: &mut Swarm, remote: PeerId) -> bool { loop { match client.select_next_some().await { - SwarmEvent::Dialing(peer_id) if peer_id == relay_peer_id => {} - SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == relay_peer_id => { - return true - } - SwarmEvent::OutgoingConnectionError { peer_id, .. } - if peer_id == Some(relay_peer_id) => - { + SwarmEvent::Dialing(peer_id) if peer_id == remote => {} + SwarmEvent::ConnectionEstablished { peer_id, .. } if peer_id == remote => return true, + SwarmEvent::OutgoingConnectionError { peer_id, .. } if peer_id == Some(remote) => { return false } + SwarmEvent::Behaviour(ClientEvent::Ping(_)) => {} e => panic!("{:?}", e), } } From b1cb6848f2c6c9bbfd699f18649737e28767ada3 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 14 Jan 2022 11:10:47 +0100 Subject: [PATCH 107/108] protocols/relay/: Only emit CircuitEstablished when transport notified --- protocols/relay/src/v2/client/handler.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index 779a32e2eab..e37561e9734 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -267,6 +267,7 @@ impl ProtocolsHandler for Handler { info: Self::OutboundOpenInfo, ) { match (output, info) { + // Outbound reservation ( outbound_hop::Output::Reservation { renewal_timeout, @@ -286,6 +287,8 @@ impl ProtocolsHandler for Handler { self.queued_events .push_back(ProtocolsHandlerEvent::Custom(event)); } + + // Outbound circuit ( outbound_hop::Output::Circuit { substream, @@ -300,7 +303,12 @@ impl ProtocolsHandler for Handler { read_buffer, tx, ))) { - Ok(()) => self.alive_lend_out_substreams.push(rx), + Ok(()) => { + self.alive_lend_out_substreams.push(rx); + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::OutboundCircuitEstablished { limit }, + )); + }, Err(_) => debug!( "Oneshot to `RelayedDial` future dropped. \ Dropping established relayed connection to {:?}.", @@ -308,10 +316,8 @@ impl ProtocolsHandler for Handler { ), } - self.queued_events.push_back(ProtocolsHandlerEvent::Custom( - Event::OutboundCircuitEstablished { limit }, - )); } + _ => unreachable!(), } } From 00dbcc8656ba6c965173f7bef68622c7da2ff7b8 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 14 Jan 2022 13:24:04 +0100 Subject: [PATCH 108/108] protocols/relay-src/v2: Fix Rust fmt complaint --- protocols/relay/src/v2/client/handler.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs index e37561e9734..b9f327dd6d1 100644 --- a/protocols/relay/src/v2/client/handler.rs +++ b/protocols/relay/src/v2/client/handler.rs @@ -222,7 +222,7 @@ impl ProtocolsHandler for Handler { fn inject_fully_negotiated_inbound( &mut self, - inbound_circuit: >::Output, + inbound_circuit: inbound_stop::Circuit, _: Self::InboundOpenInfo, ) { match &mut self.reservation { @@ -308,14 +308,13 @@ impl ProtocolsHandler for Handler { self.queued_events.push_back(ProtocolsHandlerEvent::Custom( Event::OutboundCircuitEstablished { limit }, )); - }, + } Err(_) => debug!( "Oneshot to `RelayedDial` future dropped. \ Dropping established relayed connection to {:?}.", self.remote_peer_id, ), } - } _ => unreachable!(),