diff --git a/Cargo.toml b/Cargo.toml index 899c5ee64f2..4914493b724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,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 fcc6d4e863d..8ed8f2b59dc 100644 --- a/misc/metrics/Cargo.toml +++ b/misc/metrics/Cargo.toml @@ -15,6 +15,7 @@ gossipsub = ["libp2p-gossipsub"] identify = ["libp2p-identify"] kad = ["libp2p-kad"] ping = ["libp2p-ping"] +relay = ["libp2p-relay"] [dependencies] libp2p-core = { version = "0.31.0", path = "../../core", default-features = false } @@ -22,6 +23,7 @@ libp2p-gossipsub = { version = "0.35.0", path = "../../protocols/gossipsub", op libp2p-identify = { version = "0.33.0", path = "../../protocols/identify", optional = true } libp2p-kad = { version = "0.34.0", path = "../../protocols/kad", optional = true } libp2p-ping = { version = "0.33.0", path = "../../protocols/ping", optional = true } +libp2p-relay = { version = "0.6.0", path = "../../protocols/relay", optional = true } libp2p-swarm = { version = "0.33.0", path = "../../swarm" } open-metrics-client = "0.14.0" diff --git a/misc/metrics/src/lib.rs b/misc/metrics/src/lib.rs index c3aa376b241..0a507d7ae62 100644 --- a/misc/metrics/src/lib.rs +++ b/misc/metrics/src/lib.rs @@ -33,6 +33,8 @@ mod identify; mod kad; #[cfg(feature = "ping")] mod ping; +#[cfg(feature = "relay")] +mod relay; mod swarm; use open_metrics_client::registry::Registry; @@ -47,6 +49,8 @@ pub struct Metrics { kad: kad::Metrics, #[cfg(feature = "ping")] ping: ping::Metrics, + #[cfg(feature = "relay")] + relay: relay::Metrics, swarm: swarm::Metrics, } @@ -70,6 +74,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..db2b682b9d6 --- /dev/null +++ b/misc/metrics/src/relay.rs @@ -0,0 +1,114 @@ +// Copyright 2021 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +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(Debug, Clone, Hash, PartialEq, Eq, Encode)] +struct EventLabels { + event: EventType, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode)] +enum EventType { + ReservationReqAccepted, + ReservationReqAcceptFailed, + ReservationReqDenied, + ReservationReqDenyFailed, + ReservationTimedOut, + CircuitReqReceiveFailed, + CircuitReqDenied, + CircuitReqDenyFailed, + CircuitReqOutboundConnectFailed, + 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::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 + } + 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(); + } +} diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 87391c985e1..8e7ca1b3726 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" rust-version = "1.56.1" description = "Communications relaying for libp2p" version = "0.6.0" -authors = ["Parity Technologies "] +authors = ["Parity Technologies ", "Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] @@ -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" @@ -21,8 +22,10 @@ libp2p-swarm = { version = "0.33.0", path = "../../swarm" } log = "0.4" pin-project = "1" prost = "0.9" -rand = "0.7" +rand = "0.8.4" smallvec = "1.6.1" +static_assertions = "1" +thiserror = "1.0" unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] } void = "1" @@ -31,10 +34,11 @@ prost-build = "0.9" [dev-dependencies] env_logger = "0.9.0" -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/build.rs b/protocols/relay/build.rs index c3a7d4bd823..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 98% rename from protocols/relay/examples/relay.rs rename to protocols/relay/examples/relay_v1.rs index 0cd421959f8..30cb599c223 100644 --- a/protocols/relay/examples/relay.rs +++ b/protocols/relay/examples/relay_v1.rs @@ -61,7 +61,7 @@ use futures::executor::block_on; use futures::stream::StreamExt; use libp2p::dns::DnsConfig; use libp2p::plaintext; -use libp2p::relay::{Relay, RelayConfig}; +use libp2p::relay::v1::{Relay, RelayConfig}; use libp2p::swarm::SwarmEvent; use libp2p::tcp::TcpConfig; use libp2p::Transport; @@ -94,7 +94,7 @@ fn main() -> Result<(), Box> { ..Default::default() }; let (relay_wrapped_transport, relay_behaviour) = - libp2p_relay::new_transport_and_behaviour(relay_config, transport); + libp2p_relay::v1::new_transport_and_behaviour(relay_config, transport); let behaviour = Behaviour { relay: relay_behaviour, diff --git a/protocols/relay/examples/relay_v2.rs b/protocols/relay/examples/relay_v2.rs new file mode 100644 index 00000000000..8c4d7fabdb9 --- /dev/null +++ b/protocols/relay/examples/relay_v2.rs @@ -0,0 +1,152 @@ +// 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, IdentifyConfig, IdentifyEvent}; +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 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); + + 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(); + + 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 + 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 { + match swarm.next().await.expect("Infinite Stream.") { + SwarmEvent::Behaviour(Event::Relay(event)) => { + println!("{:?}", event) + } + SwarmEvent::NewListenAddr { address, .. } => { + println!("Listening on {:?}", address); + } + _ => {} + } + } + }) +} + +#[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, +} diff --git a/protocols/relay/src/lib.rs b/protocols/relay/src/lib.rs index 1b40b52f4fd..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,107 +19,12 @@ // 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, dial_opts::DialOpts}; -//! # 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.to_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(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*. +//! libp2p circuit relay implementations -mod behaviour; +pub mod v1; +pub mod v2; -mod message_proto { - include!(concat!(env!("OUT_DIR"), "/message.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) -} - -/// 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..83b7a7730d3 --- /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, dial_opts::DialOpts}; +//! # 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.to_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(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 2ad2023fbcf..953be32d6bd 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}; @@ -40,10 +41,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. @@ -84,11 +85,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 { @@ -772,18 +773,6 @@ impl NetworkBehaviour for Relay { } } -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -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, @@ -797,7 +786,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, .. } @@ -806,7 +795,14 @@ impl RelayListener { } } -#[derive(Debug, Eq, PartialEq)] -pub enum OutgoingRelayReqError { - DialingRelay, +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +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 792ffd50534..4da3ed93f14 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, Connection, RequestId}; use futures::channel::oneshot::{self, Canceled}; use futures::future::BoxFuture; use futures::prelude::*; @@ -93,10 +92,7 @@ pub struct RelayHandler { accept_dst_futures: FuturesUnordered< BoxFuture< 'static, - Result< - (PeerId, protocol::Connection, oneshot::Receiver<()>), - protocol::IncomingDstReqError, - >, + Result<(PeerId, Connection, oneshot::Receiver<()>), protocol::IncomingDstReqError>, >, >, /// Futures that copy from a source to a destination. @@ -108,8 +104,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. @@ -169,7 +165,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. @@ -178,7 +174,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, @@ -429,6 +425,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..274ad39d3ac 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,14 @@ 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 +103,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 94% rename from protocols/relay/src/protocol/incoming_dst_req.rs rename to protocols/relay/src/v1/protocol/incoming_dst_req.rs index 589cbc3d709..21ed1cb83a8 100644 --- a/protocols/relay/src/protocol/incoming_dst_req.rs +++ b/protocols/relay/src/v1/protocol/incoming_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; +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; @@ -69,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, 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()), @@ -102,7 +101,7 @@ impl IncomingDstReq { Ok(( src.peer_id, - super::Connection::new(read_buffer.freeze(), io, tx), + Connection::new(read_buffer.freeze(), io, tx), rx, )) } 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 948a2281f5b..97f1b96ce3f 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::{Bytes, BytesMut}; 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 168861a2715..87d77458050 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::{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/protocol/outgoing_dst_req.rs b/protocols/relay/src/v1/protocol/outgoing_dst_req.rs similarity index 98% rename from protocols/relay/src/protocol/outgoing_dst_req.rs rename to protocols/relay/src/v1/protocol/outgoing_dst_req.rs index 181e31ef4a8..60871b17a93 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::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/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 a9d450b04d7..b6e062d304e 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>; @@ -156,7 +157,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 b410faf0af4..811541793e8 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::behaviour::BehaviourToListenerMsg; +use crate::v1::{Connection, RequestId}; 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 { @@ -534,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. @@ -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..a50d13a126f --- /dev/null +++ b/protocols/relay/src/v2.rs @@ -0,0 +1,41 @@ +// 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. + +//! 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")); +} + +pub mod client; +mod copy_future; +mod protocol; +pub mod relay; + +/// 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..e0459768879 --- /dev/null +++ b/protocols/relay/src/v2/client.rs @@ -0,0 +1,451 @@ +// 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; +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}; +use futures::io::{AsyncRead, AsyncWrite}; +use futures::ready; +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, +}; +use std::collections::{hash_map, HashMap, VecDeque}; +use std::io::{Error, IoSlice}; +use std::ops::DerefMut; +use std::pin::Pin; +use std::task::{Context, Poll}; + +/// The events produced by the [`Client`] behaviour. +#[derive(Debug)] +pub enum Event { + /// An outbound reservation has been accepted. + ReservationReqAccepted { + 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, + error: ProtocolsHandlerUpgrErr, + }, + OutboundCircuitEstablished { + relay_peer_id: PeerId, + limit: Option, + }, + OutboundCircuitReqFailed { + relay_peer_id: PeerId, + error: ProtocolsHandlerUpgrErr, + }, + /// An inbound circuit has been established. + InboundCircuitEstablished { + src_peer_id: PeerId, + limit: Option, + }, + 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, + error: std::io::Error, + }, +} + +pub struct Client { + local_peer_id: PeerId, + + from_transport: Receiver, + /// 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, +} + +impl Client { + pub fn new_transport_and_behaviour( + local_peer_id: PeerId, + ) -> (transport::ClientTransport, Self) { + let (transport, from_transport) = transport::ClientTransport::new(); + let behaviour = Client { + local_peer_id, + from_transport, + directly_connected_peers: 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, None) + } + + fn inject_connection_established( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + endpoint: &ConnectedPoint, + _failed_addresses: Option<&Vec>, + ) { + 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, + endpoint: &ConnectedPoint, + _handler: Either, + ) { + 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(); + } + } + hash_map::Entry::Vacant(_) => { + unreachable!("`inject_connection_closed` for unconnected peer.") + } + }; + } + } + + fn inject_event( + &mut self, + event_source: PeerId, + _connection: ConnectionId, + 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 + .push_back(Event::ReservationReqAccepted { + relay_peer_id: event_source, + renewal, + limit, + }), + handler::Event::ReservationReqFailed { renewal, error } => { + self.queued_actions.push_back(Event::ReservationReqFailed { + relay_peer_id: event_source, + renewal, + error, + }) + } + handler::Event::OutboundCircuitEstablished { limit } => { + self.queued_actions + .push_back(Event::OutboundCircuitEstablished { + relay_peer_id: event_source, + limit, + }) + } + handler::Event::OutboundCircuitReqFailed { error } => { + self.queued_actions + .push_back(Event::OutboundCircuitReqFailed { + relay_peer_id: event_source, + error, + }) + } + handler::Event::InboundCircuitEstablished { src_peer_id, limit } => self + .queued_actions + .push_back(Event::InboundCircuitEstablished { src_peer_id, limit }), + handler::Event::InboundCircuitReqFailed { error } => { + self.queued_actions + .push_back(Event::InboundCircuitReqFailed { + relay_peer_id: event_source, + error, + }) + } + handler::Event::InboundCircuitReqDenied { src_peer_id } => self + .queued_actions + .push_back(Event::InboundCircuitReqDenied { src_peer_id }), + handler::Event::InboundCircuitReqDenyFailed { src_peer_id, error } => self + .queued_actions + .push_back(Event::InboundCircuitReqDenyFailed { src_peer_id, error }), + } + } + + 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(NetworkBehaviourAction::GenerateEvent(event)); + } + + let action = match ready!(self.from_transport.poll_next_unpin(cx)) { + Some(transport::TransportToBehaviourMsg::ListenReq { + relay_peer_id, + relay_addr, + to_listener, + }) => { + match self + .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: Either::Left(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, + } + } + } + } + Some(transport::TransportToBehaviourMsg::DialReq { + relay_addr, + relay_peer_id, + dst_peer_id, + send_back, + .. + }) => { + match self + .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: Either::Left(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, + } + } + } + } + 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.", + ), + }; + + return Poll::Ready(action); + } +} + +/// A [`NegotiatedSubstream`] acting as a [`RelayedConnection`]. +pub enum RelayedConnection { + InboundAccepting { + accept: BoxFuture<'static, Result>, + }, + Operational { + read_buffer: Bytes, + substream: NegotiatedSubstream, + drop_notifier: oneshot::Sender, + }, +} + +impl Unpin for RelayedConnection {} + +impl RelayedConnection { + pub(crate) fn new_inbound( + circuit: inbound_stop::Circuit, + drop_notifier: oneshot::Sender, + ) -> Self { + RelayedConnection::InboundAccepting { + accept: async { + let (substream, read_buffer) = circuit.accept().await?; + Ok(RelayedConnection::Operational { + read_buffer, + substream, + drop_notifier, + }) + } + .boxed(), + } + } + + pub(crate) fn new_outbound( + substream: NegotiatedSubstream, + read_buffer: Bytes, + drop_notifier: oneshot::Sender, + ) -> Self { + RelayedConnection::Operational { + substream, + read_buffer, + drop_notifier, + } + } +} + +impl AsyncWrite for RelayedConnection { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + loop { + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { substream, .. } => { + return Pin::new(substream).poll_write(cx, buf); + } + } + } + } + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + loop { + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { substream, .. } => { + return Pin::new(substream).poll_flush(cx); + } + } + } + } + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + loop { + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { substream, .. } => { + return Pin::new(substream).poll_close(cx); + } + } + } + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context, + bufs: &[IoSlice], + ) -> Poll> { + loop { + 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); + } + } + } + } +} + +impl AsyncRead for RelayedConnection { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + loop { + match self.deref_mut() { + RelayedConnection::InboundAccepting { accept } => { + *self = ready!(accept.poll_unpin(cx))?; + } + RelayedConnection::Operational { + 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[..]); + return Poll::Ready(Ok(n)); + } + + return Pin::new(substream).poll_read(cx, buf); + } + } + } + } +} diff --git a/protocols/relay/src/v2/client/handler.rs b/protocols/relay/src/v2/client/handler.rs new file mode 100644 index 00000000000..b9f327dd6d1 --- /dev/null +++ b/protocols/relay/src/v2/client/handler.rs @@ -0,0 +1,725 @@ +// 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::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; +use futures::stream::{FuturesUnordered, StreamExt}; +use futures_timer::Delay; +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::{ + DummyProtocolsHandler, InboundUpgradeSend, OutboundUpgradeSend, SendWrapper, +}; +use libp2p_swarm::{ + IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, ProtocolsHandlerEvent, + ProtocolsHandlerUpgrErr, SubstreamProtocol, +}; +use log::debug; +use std::collections::VecDeque; +use std::fmt; +use std::task::{Context, Poll}; +use std::time::Duration; + +pub enum In { + Reserve { + to_listener: mpsc::Sender, + }, + EstablishCircuit { + dst_peer_id: PeerId, + send_back: oneshot::Sender>, + }, +} + +impl fmt::Debug for In { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + In::Reserve { to_listener: _ } => f.debug_struct("In::Reserve").finish(), + In::EstablishCircuit { + dst_peer_id, + send_back: _, + } => f + .debug_struct("In::EstablishCircuit") + .field("dst_peer_id", dst_peer_id) + .finish(), + } + } +} + +#[derive(Debug)] +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, + error: ProtocolsHandlerUpgrErr, + }, + /// An outbound circuit has been established. + OutboundCircuitEstablished { limit: Option }, + OutboundCircuitReqFailed { + error: ProtocolsHandlerUpgrErr, + }, + /// An inbound circuit has been established. + InboundCircuitEstablished { + src_peer_id: PeerId, + limit: Option, + }, + /// 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, + error: std::io::Error, + }, +} + +pub struct Prototype { + local_peer_id: PeerId, + /// Initial [`In`] event from [`super::Client`] provided at creation time. + initial_in: Option, +} + +impl Prototype { + pub(crate) fn new(local_peer_id: PeerId, initial_in: Option) -> Self { + Self { + local_peer_id, + initial_in, + } + } +} + +impl IntoProtocolsHandler for Prototype { + type Handler = Either; + + fn into_handler(self, remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { + 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 + ); + } + + // 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) + } + + Either::Left(handler) + } + } + + fn inbound_protocol(&self) -> ::InboundProtocol { + upgrade::EitherUpgrade::A(SendWrapper(inbound_stop::Upgrade {})) + } +} + +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, + >, + >, + /// Until when to keep the connection alive. + keep_alive: KeepAlive, + + /// Queue of events to return when polled. + queued_events: VecDeque< + ProtocolsHandlerEvent< + ::OutboundProtocol, + ::OutboundOpenInfo, + ::OutEvent, + ::Error, + >, + >, + + reservation: Reservation, + + /// Tracks substreams lent 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>, + + 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>, +} + +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: inbound_stop::Circuit, + _: Self::InboundOpenInfo, + ) { + match &mut self.reservation { + 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(); + self.circuit_deny_futs.push( + inbound_circuit + .deny(Status::NoReservation) + .map(move |result| (src_peer_id, result)) + .boxed(), + ) + } + } + } + + fn inject_fully_negotiated_outbound( + &mut self, + output: >::Output, + info: Self::OutboundOpenInfo, + ) { + match (output, info) { + // Outbound reservation + ( + outbound_hop::Output::Reservation { + renewal_timeout, + addrs, + limit, + }, + OutboundOpenInfo::Reserve { to_listener }, + ) => { + let event = self.reservation.accepted( + renewal_timeout, + addrs, + to_listener, + self.local_peer_id, + limit, + ); + + self.queued_events + .push_back(ProtocolsHandlerEvent::Custom(event)); + } + + // Outbound circuit + ( + outbound_hop::Output::Circuit { + substream, + read_buffer, + limit, + }, + OutboundOpenInfo::Connect { send_back }, + ) => { + let (tx, rx) = oneshot::channel(); + match send_back.send(Ok(super::RelayedConnection::new_outbound( + substream, + read_buffer, + tx, + ))) { + 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 {:?}.", + self.remote_peer_id, + ), + } + } + + _ => 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>, + ) { + 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( + 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( + &mut self, + open_info: Self::OutboundOpenInfo, + error: ProtocolsHandlerUpgrErr<::Error>, + ) { + match open_info { + OutboundOpenInfo::Reserve { mut to_listener } => { + 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)) => { + match error { + 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::CircuitFailed(_) => { + unreachable!( + "Do not emitt `CircuitFailed` for outgoing reservation." + ) + } + } + } + }; + + 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`. + } + + let renewal = self.reservation.failed(); + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::ReservationReqFailed { + renewal, + error: non_fatal_error, + }, + )); + } + OutboundOpenInfo::Connect { send_back } => { + 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)) => { + match error { + 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::ReservationFailed(_) => { + unreachable!( + "Do not emitt `ReservationFailed` for outgoing circuit." + ) + } + } + } + }; + + let _ = send_back.send(Err(())); + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::OutboundCircuitReqFailed { + error: non_fatal_error, + }, + )); + } + } + } + + fn connection_keep_alive(&self) -> KeepAlive { + self.keep_alive + } + + 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); + } + + if let Poll::Ready(Some(protocol)) = self.reservation.poll(cx) { + return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { protocol }); + } + + // Deny incoming circuit requests. + if 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::InboundCircuitReqDenied { src_peer_id }, + )) + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::InboundCircuitReqDenyFailed { src_peer_id, error }, + )) + } + } + } + + // 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))) => {} + Poll::Ready(Some(Ok(v))) => void::unreachable(v), + Poll::Ready(None) | Poll::Pending => break, + } + } + + // Update keep-alive handling. + if matches!(self.reservation, Reservation::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 + } +} + +enum Reservation { + /// The Reservation is accepted by the relay. + Accepted { + renewal_timeout: Delay, + /// Buffer of messages to be send to the transport listener. + pending_msgs: VecDeque, + to_listener: mpsc::Sender, + }, + /// The reservation is being renewed with the relay. + Renewing { + /// Buffer of messages to be send to the transport listener. + pending_msgs: VecDeque, + }, + None, +} + +impl Reservation { + fn accepted( + &mut self, + renewal_timeout: Delay, + 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, .. } + | 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, limit } + } + + /// 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 { .. } + ); + + *self = Reservation::None; + + 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); + + // Check renewal timeout if any. + let (next_reservation, poll_val) = match std::mem::replace(self, Reservation::None) { + Reservation::Accepted { + mut renewal_timeout, + pending_msgs, + to_listener, + } => 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, + pending_msgs, + to_listener, + }, + Poll::Pending, + ), + }, + r => (r, Poll::Pending), + }; + *self = next_reservation; + + poll_val + } +} + +pub enum OutboundOpenInfo { + Reserve { + to_listener: mpsc::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..6128f25bcc7 --- /dev/null +++ b/protocols/relay/src/v2/client/transport.rs @@ -0,0 +1,398 @@ +// 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::RelayedConnection; +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}; +use libp2p_core::transport::{ListenerEvent, TransportError}; +use libp2p_core::{PeerId, Transport}; +use std::collections::VecDeque; +use std::pin::Pin; +use std::task::{Context, Poll}; +use thiserror::Error; + +/// A [`Transport`] enabling client relay capabilities. +/// +/// 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: +/// +/// 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 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_id = PeerId::random(); +/// # let destination_id = PeerId::random(); +/// let dst_addr_via_relay = Multiaddr::empty() +/// .with(Protocol::Memory(40)) // Relay address. +/// .with(Protocol::P2p(relay_id.into())) // Relay peer id. +/// .with(Protocol::P2pCircuit) // Signal to connect via relay and not directly. +/// .with(Protocol::P2p(destination_id.into())); // Destination peer id. +/// transport.dial(dst_addr_via_relay).unwrap(); +/// ``` +/// +/// 3. Listen for incoming relayed connections via specific relay. +/// +/// ``` +/// # 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 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( +/// 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(relay_id.into())) // Relay peer id. +/// .with(Protocol::P2pCircuit); // Signal to listen via remote relay node. +/// transport.listen_on(relay_addr).unwrap(); +/// ``` +#[derive(Clone)] +pub struct ClientTransport { + to_behaviour: mpsc::Sender, +} + +impl 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}; + /// # use libp2p_core::transport::memory::MemoryTransport; + /// # use libp2p_core::transport::choice::OrTransport; + /// # use libp2p_relay::v2::client; + /// let actual_transport = MemoryTransport::default(); + /// let (relay_transport, behaviour) = client::Client::new_transport_and_behaviour( + /// PeerId::random(), + /// ); + /// + /// // 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); + + (ClientTransport { to_behaviour }, from_transport) + } +} + +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> { + 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, + }) + } + + fn dial(self, addr: Multiaddr) -> Result> { + let RelayedMultiaddr { + relay_peer_id, + relay_addr, + dst_peer_id, + dst_addr, + } = parse_relayed_multiaddr(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; + 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 { + None + } +} + +#[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`]. +fn parse_relayed_multiaddr( + addr: Multiaddr, +) -> Result> { + if !addr.iter().any(|p| matches!(p, Protocol::P2pCircuit)) { + return Err(TransportError::MultiaddrNotSupported(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 { + 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() { + Err(RelayError::MalformedMultiaddr)?; + } + relayed_multiaddr.relay_peer_id = Some(peer_id) + } else { + if relayed_multiaddr.dst_peer_id.is_some() { + 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(relayed_multiaddr) +} + +pub struct RelayListener { + queued_new_addresses: VecDeque, + from_behaviour: mpsc::Receiver, + msg_to_behaviour: Option>>, +} + +impl Unpin for RelayListener {} + +impl Stream for RelayListener { + type Item = + Result>, RelayError>, RelayError>; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + 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)))); + } + + 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); + } + }; + + 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; + } + 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)); + } + } +} + +pub type RelayedDial = BoxFuture<'static, Result>; + +/// Error that occurred during relay connection setup. +#[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, + #[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, +} + +impl From for TransportError { + fn from(error: RelayError) -> Self { + TransportError::Other(error) + } +} + +/// Message from the [`ClientTransport`] to the [`Relay`](crate::v2::relay::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, + }, +} + +#[allow(clippy::large_enum_variant)] +pub enum ToListenerMsg { + Reservation(Result), + IncomingRelayedConnection { + stream: RelayedConnection, + src_peer_id: PeerId, + relay_peer_id: PeerId, + relay_addr: Multiaddr, + }, +} + +pub struct Reservation { + pub(crate) addrs: Vec, +} diff --git a/protocols/relay/src/v2/copy_future.rs b/protocols/relay/src/v2/copy_future.rs new file mode 100644 index 00000000000..47652c92ed7 --- /dev/null +++ b/protocols/relay/src/v2/copy_future.rs @@ -0,0 +1,291 @@ +// 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."))) +} + +#[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); + } +} diff --git a/protocols/relay/src/v2/message.proto b/protocols/relay/src/v2/message.proto new file mode 100644 index 00000000000..0a42ce3d773 --- /dev/null +++ b/protocols/relay/src/v2/message.proto @@ -0,0 +1,59 @@ +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 { + 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 +} + +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; +} \ No newline at end of file diff --git a/protocols/relay/src/v2/protocol.rs b/protocols/relay/src/v2/protocol.rs new file mode 100644 index 00000000000..ab2dc487b6f --- /dev/null +++ b/protocols/relay/src/v2/protocol.rs @@ -0,0 +1,47 @@ +// 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 std::time::Duration; + +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"; +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_hop.rs b/protocols/relay/src/v2/protocol/inbound_hop.rs new file mode 100644 index 00000000000..878d85cc4e0 --- /dev/null +++ b/protocols/relay/src/v2/protocol/inbound_hop.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. + +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; +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::iter; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use thiserror::Error; +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 { + 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_else(|| 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(FatalUpgradeError::ParseTypeField)?; + 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(FatalUpgradeError::MissingPeer)?.id) + .map_err(|_| FatalUpgradeError::ParsePeerId)?; + Req::Connect(CircuitReq { dst, substream }) + } + hop_message::Type::Status => Err(FatalUpgradeError::UnexpectedTypeStatus)?, + }; + + Ok(req) + } + .boxed() + } +} + +#[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] + #[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, +} + +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: (SystemTime::now() + self.reservation_duration) + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + voucher: None, + }), + limit: Some(Limit { + 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()), + }; + + 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 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?; + + 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 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 new file mode 100644 index 00000000000..d38e72c1627 --- /dev/null +++ b/protocols/relay/src/v2/protocol/inbound_stop.rs @@ -0,0 +1,185 @@ +// 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::{self, 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::io::Cursor; +use std::iter; +use thiserror::Error; +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_else(|| 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(FatalUpgradeError::ParseTypeField)?; + match r#type { + stop_message::Type::Connect => { + let src_peer_id = + 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(FatalUpgradeError::UnexpectedTypeStatus)?, + } + } + .boxed() + } +} + +#[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] + #[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, +} + +pub struct Circuit { + substream: Framed>>>, + src_peer_id: PeerId, + limit: Option, +} + +impl Circuit { + pub fn src_peer_id(&self) -> PeerId { + 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(), + 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())) + } + + 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 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 new file mode 100644 index 00000000000..bbd2e0735ee --- /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 crate::v2::protocol::{Limit, HOP_PROTOCOL_NAME, MAX_MESSAGE_SIZE}; +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; +use std::convert::TryFrom; +use std::io::Cursor; +use std::iter; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use thiserror::Error; +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::with_capacity(msg.encoded_len()); + msg.encode(&mut encoded_msg) + .expect("Vec to have sufficient capacity."); + + let mut codec = UviBytes::default(); + codec.set_max_len(MAX_MESSAGE_SIZE); + let mut substream = Framed::new(substream, codec); + + async move { + substream.send(Cursor::new(encoded_msg)).await?; + let msg: bytes::BytesMut = substream + .next() + .await + .ok_or_else(|| 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(FatalUpgradeError::ParseTypeField)?; + match r#type { + 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(FatalUpgradeError::MissingStatusField)?) + .ok_or(FatalUpgradeError::ParseStatusField)?; + + let limit = limit.map(Into::into); + + let output = match self { + Upgrade::Reserve => { + 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)?; + + if reservation.addrs.is_empty() { + Err(FatalUpgradeError::NoAddressesInReservation)?; + } + + let addrs = reservation + .addrs + .into_iter() + .map(TryFrom::try_from) + .collect::, _>>() + .map_err(|_| FatalUpgradeError::InvalidReservationAddrs)?; + + let renewal_timeout = reservation + .expire + .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(FatalUpgradeError::InvalidReservationExpiration)?; + + substream.close().await?; + + Output::Reservation { + renewal_timeout, + addrs, + limit, + } + } + 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, + 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(), + limit, + } + } + }; + + Ok(output) + } + .boxed() + } +} + +#[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] + #[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), +} + +pub enum Output { + Reservation { + renewal_timeout: Delay, + addrs: Vec, + limit: Option, + }, + Circuit { + substream: NegotiatedSubstream, + read_buffer: Bytes, + limit: Option, + }, +} 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..ae93dbe92e5 --- /dev/null +++ b/protocols/relay/src/v2/protocol/outbound_stop.rs @@ -0,0 +1,178 @@ +// 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::iter; +use std::time::Duration; +use thiserror::Error; +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 { + 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, + }; + + let mut encoded_msg = Vec::with_capacity(msg.encoded_len()); + msg.encode(&mut encoded_msg) + .expect("Vec to have sufficient capacity."); + + 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_else(|| 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(FatalUpgradeError::ParseTypeField)?; + match r#type { + stop_message::Type::Connect => Err(FatalUpgradeError::UnexpectedTypeConnect)?, + stop_message::Type::Status => {} + } + + let status = Status::from_i32(status.ok_or(FatalUpgradeError::MissingStatusField)?) + .ok_or(FatalUpgradeError::ParseStatusField)?; + match status { + Status::Ok => {} + Status::ResourceLimitExceeded => Err(CircuitFailedReason::ResourceLimitExceeded)?, + Status::PermissionDenied => Err(CircuitFailedReason::PermissionDenied)?, + s => Err(FatalUpgradeError::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, 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] + #[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), +} diff --git a/protocols/relay/src/v2/relay.rs b/protocols/relay/src/v2/relay.rs new file mode 100644 index 00000000000..eae734f3c3e --- /dev/null +++ b/protocols/relay/src/v2/relay.rs @@ -0,0 +1,731 @@ +// 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 **relay**. + +mod handler; +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, +}; +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 +/// +/// [`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>, + + 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>, +} + +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 { + 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 { + limit: NonZeroU32::new(60).expect("60 > 0"), + interval: Duration::from_secs(60), + }), + ]; + + 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 { + 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 { + limit: NonZeroU32::new(60).expect("60 > 0"), + interval: Duration::from_secs(60), + }), + ]; + + Config { + max_reservations: 128, + max_reservations_per_peer: 4, + reservation_duration: Duration::from_secs(60 * 60), + 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, + } + } +} + +/// The events produced by the [`Relay`] behaviour. +#[derive(Debug)] +pub enum Event { + /// 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 }, + CircuitReqReceiveFailed { + src_peer_id: PeerId, + error: ProtocolsHandlerUpgrErr, + }, + /// 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, + }, + /// 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, + dst_peer_id: PeerId, + error: std::io::Error, + }, + /// An inbound circuit has closed. + CircuitClosed { + src_peer_id: PeerId, + dst_peer_id: PeerId, + error: Option, + }, +} + +/// [`Relay`] is a [`NetworkBehaviour`] that implements the relay server +/// functionality of the circuit relay v2 protocol. +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 = handler::Prototype; + type OutEvent = Event; + + fn new_handler(&mut self) -> Self::ProtocolsHandler { + handler::Prototype { + 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 inject_connection_closed( + &mut self, + peer: &PeerId, + connection: &ConnectionId, + _: &ConnectedPoint, + _handler: Either, + ) { + if let Some(connections) = self.reservations.get_mut(peer) { + connections.remove(&connection); + } + + 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(Event::CircuitClosed { + src_peer_id: circuit.src_peer_id, + dst_peer_id: circuit.dst_peer_id, + error: Some(std::io::ErrorKind::ConnectionAborted.into()), + }) + .into(), + ); + } + } + + fn inject_event( + &mut self, + event_source: PeerId, + connection: ConnectionId, + event: Either, + ) { + let event = match event { + Either::Left(e) => e, + Either::Right(v) => void::unreachable(v), + }; + + match event { + handler::Event::ReservationReqReceived { + inbound_reservation_req, + endpoint, + renewed, + } => { + let now = Instant::now(); + + 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 + .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 + .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: Either::Left(handler::In::DenyReservationReq { + inbound_reservation_req, + status: message_proto::Status::ResourceLimitExceeded, + }), + } + .into() + } else { + // Accept reservation. + self.reservations + .entry(event_source) + .or_default() + .insert(connection); + + Action::AcceptReservationPrototype { + handler: NotifyHandler::One(connection), + peer_id: event_source, + inbound_reservation_req, + } + }; + + self.queued_actions.push_back(action); + } + handler::Event::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(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, + }) + .into(), + ); + } + handler::Event::ReservationReqDenied {} => { + 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, + }) + .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, + }) + .into(), + ); + } + handler::Event::CircuitReqReceived { + inbound_circuit_req, + endpoint, + } => { + let now = Instant::now(); + + 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 + .config + .circuit_src_rate_limiters + .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: Either::Left(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()) + .map(|cs| cs.iter().next()) + .flatten() + { + // Accept circuit request if reservation present. + 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, + }); + + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(*dst_conn), + peer_id: event_source, + 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: Either::Left(handler::In::DenyCircuitReq { + circuit_id: None, + inbound_circuit_req, + status: message_proto::Status::NoReservation, + }), + } + }; + 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, + } => { + if let Some(circuit_id) = circuit_id { + self.circuits.remove(circuit_id); + } + + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitReqDenied { + src_peer_id: event_source, + dst_peer_id, + }) + .into(), + ); + } + handler::Event::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(Event::CircuitReqDenyFailed { + src_peer_id: event_source, + dst_peer_id, + error, + }) + .into(), + ); + } + handler::Event::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: Either::Left(handler::In::AcceptAndDriveCircuit { + circuit_id, + dst_peer_id: event_source, + inbound_circuit_req, + dst_handler_notifier, + dst_stream, + dst_pending_data, + }), + } + .into(), + ); + } + handler::Event::OutboundConnectNegotiationFailed { + circuit_id, + src_peer_id, + src_connection_id, + inbound_circuit_req, + status, + error, + } => { + self.queued_actions.push_back( + NetworkBehaviourAction::NotifyHandler { + handler: NotifyHandler::One(src_connection_id), + peer_id: src_peer_id, + event: Either::Left(handler::In::DenyCircuitReq { + circuit_id: Some(circuit_id), + inbound_circuit_req, + status, + }), + } + .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, + circuit_id, + } => { + self.circuits.accepted(circuit_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, + circuit_id, + error, + } => { + self.circuits.remove(circuit_id); + 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, + circuit_id, + error, + } => { + self.circuits.remove(circuit_id); + + self.queued_actions.push_back( + NetworkBehaviourAction::GenerateEvent(Event::CircuitClosed { + src_peer_id: event_source, + dst_peer_id, + error, + }) + .into(), + ); + } + } + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + poll_parameters: &mut impl PollParameters, + ) -> Poll> { + if let Some(action) = self.queued_actions.pop_front() { + return Poll::Ready(action.build(poll_parameters)); + } + + 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) { + if let Some(c) = self.circuits.get_mut(&circuit_id) { + 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 + } + + 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)] +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, Debug, Hash, Eq, PartialEq)] +pub struct CircuitId(u64); + +impl Add for CircuitId { + type Output = CircuitId; + + fn add(self, rhs: u64) -> Self { + CircuitId(self.0 + rhs) + } +} + +/// 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 { + 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: Either::Left(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(), + }), + }, + } + } +} diff --git a/protocols/relay/src/v2/relay/handler.rs b/protocols/relay/src/v2/relay/handler.rs new file mode 100644 index 00000000000..547469797dd --- /dev/null +++ b/protocols/relay/src/v2/relay/handler.rs @@ -0,0 +1,911 @@ +// 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::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 either::Either; +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 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, + ProtocolsHandlerUpgrErr, SubstreamProtocol, +}; +use std::collections::VecDeque; +use std::fmt; +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 In { + AcceptReservationReq { + inbound_reservation_req: inbound_hop::ReservationReq, + addrs: Vec, + }, + DenyReservationReq { + inbound_reservation_req: inbound_hop::ReservationReq, + status: Status, + }, + DenyCircuitReq { + circuit_id: Option, + inbound_circuit_req: inbound_hop::CircuitReq, + status: 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, + }, +} + +impl fmt::Debug for In { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + In::AcceptReservationReq { + inbound_reservation_req: _, + addrs, + } => f + .debug_struct("In::AcceptReservationReq") + .field("addrs", addrs) + .finish(), + In::DenyReservationReq { + inbound_reservation_req: _, + status, + } => f + .debug_struct("In::DenyReservationReq") + .field("status", status) + .finish(), + In::DenyCircuitReq { + circuit_id, + inbound_circuit_req: _, + status, + } => f + .debug_struct("In::DenyCircuitReq") + .field("circuit_id", circuit_id) + .field("status", status) + .finish(), + In::NegotiateOutboundConnect { + circuit_id, + inbound_circuit_req: _, + relay_peer_id, + src_peer_id, + src_connection_id, + } => f + .debug_struct("In::NegotiateOutboundConnect") + .field("circuit_id", circuit_id) + .field("relay_peer_id", relay_peer_id) + .field("src_peer_id", src_peer_id) + .field("src_connection_id", src_connection_id) + .finish(), + In::AcceptAndDriveCircuit { + circuit_id, + inbound_circuit_req: _, + dst_peer_id, + dst_handler_notifier: _, + dst_stream: _, + dst_pending_data: _, + } => f + .debug_struct("In::AcceptAndDriveCircuit") + .field("circuit_id", circuit_id) + .field("dst_peer_id", dst_peer_id) + .finish(), + } + } +} + +/// The events produced by the [`Handler`]. +#[allow(clippy::large_enum_variant)] +pub enum Event { + /// An inbound reservation request has been received. + 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 { + /// Indicates whether the request replaces an existing reservation. + renewed: bool, + }, + /// Accepting an inbound reservation request failed. + ReservationReqAcceptFailed { error: std::io::Error }, + /// An inbound reservation request has been denied. + ReservationReqDenied {}, + /// 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_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, + 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, + src_connection_id: ConnectionId, + inbound_circuit_req: inbound_hop::CircuitReq, + dst_handler_notifier: oneshot::Sender<()>, + 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, + src_connection_id: ConnectionId, + inbound_circuit_req: inbound_hop::CircuitReq, + status: Status, + error: ProtocolsHandlerUpgrErr, + }, + /// An inbound circuit has closed. + CircuitClosed { + circuit_id: CircuitId, + dst_peer_id: PeerId, + error: Option, + }, +} + +impl fmt::Debug for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + 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") + .field("renewed", renewed) + .finish(), + Event::ReservationReqAcceptFailed { error } => f + .debug_struct("Event::ReservationReqAcceptFailed") + .field("error", error) + .finish(), + Event::ReservationReqDenied {} => { + f.debug_struct("Event::ReservationReqDenied").finish() + } + Event::ReservationReqDenyFailed { error } => f + .debug_struct("Event::ReservationReqDenyFailed") + .field("error", error) + .finish(), + Event::ReservationTimedOut {} => f.debug_struct("Event::ReservationTimedOut").finish(), + Event::CircuitReqReceived { + endpoint, + inbound_circuit_req: _, + } => f + .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, + } => f + .debug_struct("Event::CircuitReqDenied") + .field("circuit_id", circuit_id) + .field("dst_peer_id", dst_peer_id) + .finish(), + Event::CircuitReqDenyFailed { + circuit_id, + dst_peer_id, + error, + } => f + .debug_struct("Event::CircuitReqDenyFailed") + .field("circuit_id", circuit_id) + .field("dst_peer_id", dst_peer_id) + .field("error", error) + .finish(), + Event::CircuitReqAccepted { + circuit_id, + dst_peer_id, + } => f + .debug_struct("Event::CircuitReqAccepted") + .field("circuit_id", circuit_id) + .field("dst_peer_id", dst_peer_id) + .finish(), + Event::CircuitReqAcceptFailed { + circuit_id, + dst_peer_id, + error, + } => f + .debug_struct("Event::CircuitReqAcceptFailed") + .field("circuit_id", circuit_id) + .field("dst_peer_id", dst_peer_id) + .field("error", error) + .finish(), + Event::OutboundConnectNegotiated { + circuit_id, + src_peer_id, + src_connection_id, + inbound_circuit_req: _, + dst_handler_notifier: _, + dst_stream: _, + dst_pending_data: _, + } => f + .debug_struct("Event::OutboundConnectNegotiated") + .field("circuit_id", circuit_id) + .field("src_peer_id", src_peer_id) + .field("src_connection_id", src_connection_id) + .finish(), + Event::OutboundConnectNegotiationFailed { + circuit_id, + src_peer_id, + 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, + dst_peer_id, + error, + } => f + .debug_struct("Event::CircuitClosed") + .field("circuit_id", circuit_id) + .field("dst_peer_id", dst_peer_id) + .field("error", error) + .finish(), + } + } +} + +pub struct Prototype { + pub config: Config, +} + +impl IntoProtocolsHandler for Prototype { + type Handler = Either; + + fn into_handler(self, _remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler { + 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 { + 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, + })) + } +} + +/// [`ProtocolsHandler`] that manages substreams for a relay on a single +/// connection with a peer. +pub struct Handler { + endpoint: ConnectedPoint, + + /// Static [`Handler`] [`Config`]. + 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, + >, + >, + + /// Until when to keep the connection alive. + keep_alive: KeepAlive, + + /// 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 [`Handler`]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>, + /// Futures relaying data for circuit between two peers. + circuits: Futures<(CircuitId, PeerId, Result<(), std::io::Error>)>, +} + +type Futures = FuturesUnordered>; + +impl ProtocolsHandler for Handler { + type InEvent = In; + type OutEvent = Event; + 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, + _: Self::InboundOpenInfo, + ) { + match request { + inbound_hop::Req::Reserve(inbound_reservation_req) => { + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::ReservationReqReceived { + inbound_reservation_req, + endpoint: self.endpoint.clone(), + renewed: self.active_reservation.is_some(), + }, + )); + } + inbound_hop::Req::Connect(inbound_circuit_req) => { + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::CircuitReqReceived { + inbound_circuit_req, + endpoint: self.endpoint.clone(), + }, + )); + } + } + } + + 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( + Event::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 { + In::AcceptReservationReq { + inbound_reservation_req, + addrs, + } => { + self.reservation_accept_futures + .push(inbound_reservation_req.accept(addrs).boxed()); + } + In::DenyReservationReq { + inbound_reservation_req, + status, + } => { + self.reservation_deny_futures + .push(inbound_reservation_req.deny(status).boxed()); + } + In::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, + }, + ), + }); + } + In::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(), + ); + } + In::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>, + ) { + 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( + 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( + &mut self, + open_info: Self::OutboundOpenInfo, + error: ProtocolsHandlerUpgrErr<::Error>, + ) { + 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, + )) => { + // 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), + )); + return; + } + 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)) => 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 + } + outbound_stop::CircuitFailedReason::PermissionDenied => { + Status::PermissionDenied + } + }; + ( + ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)), + status, + ) + } + }, + }; + + let OutboundOpenInfo { + circuit_id, + inbound_circuit_req, + src_peer_id, + src_connection_id, + } = open_info; + + self.queued_events.push_back(ProtocolsHandlerEvent::Custom( + Event::OutboundConnectNegotiationFailed { + circuit_id, + src_peer_id, + src_connection_id, + inbound_circuit_req, + status, + error: non_fatal_error, + }, + )); + } + + fn connection_keep_alive(&self) -> KeepAlive { + self.keep_alive + } + + 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); + } + + if let Poll::Ready(Some((circuit_id, dst_peer_id, result))) = + self.circuits.poll_next_unpin(cx) + { + match result { + Ok(()) => { + 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), + })) + } + } + } + + if 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( + Event::ReservationReqAccepted { renewed }, + )); + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::ReservationReqAcceptFailed { error }, + )); + } + } + } + + if let Poll::Ready(Some(result)) = self.reservation_deny_futures.poll_next_unpin(cx) { + match result { + Ok(()) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::ReservationReqDenied {}, + )) + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::ReservationReqDenyFailed { error }, + )); + } + } + } + + if 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(Event::CircuitReqAccepted { + circuit_id, + dst_peer_id, + })); + } + Err((circuit_id, dst_peer_id, error)) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::CircuitReqAcceptFailed { + circuit_id, + dst_peer_id, + error, + }, + )); + } + } + } + + if 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(Event::CircuitReqDenied { + circuit_id, + dst_peer_id, + })); + } + Err(error) => { + return Poll::Ready(ProtocolsHandlerEvent::Custom( + Event::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(Event::ReservationTimedOut {})); + } + + 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!("Handler never sets KeepAlive::No."), + } + } else { + self.keep_alive = KeepAlive::Yes; + } + + 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/relay/rate_limiter.rs b/protocols/relay/src/v2/relay/rate_limiter.rs new file mode 100644 index 00000000000..c89a6c0761b --- /dev/null +++ b/protocols/relay/src/v2/relay/rate_limiter.rs @@ -0,0 +1,315 @@ +// 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 use generic::{ + RateLimiter as GenericRateLimiter, RateLimiterConfig as GenericRateLimiterConfig, +}; +use instant::Instant; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +use libp2p_core::PeerId; +use std::net::IpAddr; + +/// Allows rate limiting access to some resource based on the [`PeerId`] and +/// [`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 autonomous system +/// number of a peers IP address. +pub trait RateLimiter: Send { + fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool; +} + +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)) +} + +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 bool + Send> RateLimiter for T { + fn try_next(&mut self, peer: PeerId, addr: &Multiaddr, now: Instant) -> bool { + self(peer, addr, now) + } +} + +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 instant::Instant; + use std::collections::{HashMap, VecDeque}; + use std::convert::TryInto; + use std::hash::Hash; + use std::num::NonZeroU32; + use std::time::Duration; + + /// 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, + } + + impl RateLimiter { + pub(crate) fn new(config: RateLimiterConfig) -> 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 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 + } + } + } + + 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; + + #[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 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)); + } + + 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), + }); + + 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(RateLimiterConfig { + 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(RateLimiterConfig { + 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(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); + } + + 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); + + TestResult::passed() + } + + QuickCheck::new().quickcheck(prop as fn(_, _, _) -> _) + } + } +} diff --git a/protocols/relay/tests/lib.rs b/protocols/relay/tests/v1.rs similarity index 99% rename from protocols/relay/tests/lib.rs rename to protocols/relay/tests/v1.rs index c316f06a8fc..fe4ca9f29b5 100644 --- a/protocols/relay/tests/lib.rs +++ b/protocols/relay/tests/v1.rs @@ -33,10 +33,10 @@ use libp2p_identify::{Identify, IdentifyConfig, IdentifyEvent, IdentifyInfo}; use libp2p_kad::{GetClosestPeersOk, Kademlia, KademliaEvent, QueryResult}; use libp2p_ping as ping; 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; use libp2p_swarm::{ - dial_opts::DialOpts, DialError, DummyBehaviour, NetworkBehaviour, NetworkBehaviourAction, + DialError, DummyBehaviour, NetworkBehaviour, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters, Swarm, SwarmEvent, }; use std::task::{Context, Poll}; @@ -1284,7 +1284,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() @@ -1326,7 +1326,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) diff --git a/protocols/relay/tests/v2.rs b/protocols/relay/tests/v2.rs new file mode 100644 index 00000000000..ea4b8b8fb3c --- /dev/null +++ b/protocols/relay/tests/v2.rs @@ -0,0 +1,451 @@ +// 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::choice::OrTransport; +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}; +use std::time::Duration; + +#[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(); + + // 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, + 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. + )); +} + +#[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 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, + 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(); + 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(); + + assert!(pool.run_until(wait_for_dial(&mut dst, relay_peer_id))); + + pool.run_until(wait_for_reservation( + &mut dst, + dst_addr.clone(), + relay_peer_id, + false, // No renewal. + )); + spawn_swarm_on_pool(&pool, dst); + + let mut src = build_client(); + + src.dial(dst_addr).unwrap(); + + pool.run_until(async { + loop { + match src.select_next_some().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::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 => { + break + } + e => panic!("{:?}", e), + } + } + }) +} + +#[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(); + assert!(!pool.run_until(wait_for_dial(&mut client, relay_peer_id))); +} + +#[test] +fn reuse_connection() { + 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.dial(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, + false, // No renewal. + )); +} + +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().to_peer_id(); + + let transport = upgrade_transport(MemoryTransport::default().boxed(), local_public_key); + + Swarm::new( + transport, + Relay { + ping: Ping::new(PingConfig::new()), + relay: relay::Relay::new( + local_peer_id, + relay::Config { + reservation_duration: Duration::from_secs(2), + ..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().to_peer_id(); + + 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, + Client { + ping: Ping::new(PingConfig::new()), + relay: behaviour, + }, + local_peer_id, + ) +} + +fn upgrade_transport( + transport: Boxed, + local_public_key: PublicKey, +) -> Boxed<(PeerId, StreamMuxerBox)> +where + StreamSink: AsyncRead + AsyncWrite + Send + Unpin + 'static, +{ + transport + .upgrade(upgrade::Version::V1) + .authenticate(PlainText2Config { local_public_key }) + .multiplex(libp2p_yamux::YamuxConfig::default()) + .boxed() +} + +#[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, + 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 => { + 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(_)) => {} + e => panic!("{:?}", e), + } + } +} + +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 == 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), + } + } +}