749 lines
32 KiB
Rust
Raw Normal View History

protocols/relay: Implement circuit relay specification (#1838) This commit implements the [libp2p circuit relay](https://github.com/libp2p/specs/tree/master/relay) specification. It is based on previous work from https://github.com/libp2p/rust-libp2p/pull/1134. Instead of altering the `Transport` trait, the approach taken in this commit is to wrap an existing implementation of `Transport` allowing one to: - Intercept `dial` requests with a relayed address. - Inject incoming relayed connections with the local node being the destination. - Intercept `listen_on` requests pointing to a relay, ensuring to keep a constant connection to the relay, waiting for incoming requests with the local node being the destination. More concretely one would wrap an existing `Transport` implementation as seen below, allowing the `Relay` behaviour and the `RelayTransport` to communicate via channels. ### Example ```rust 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(&mut swarm, relay_addr).unwrap(); // Dial node (5678) via relay node (1234). Swarm::dial_addr(&mut swarm, dst_addr).unwrap(); ``` Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com> Co-authored-by: Roman Borschel <romanb@users.noreply.github.com> Co-authored-by: David Craven <david@craven.ch>
2021-03-11 16:07:59 +01:00
// 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 crate::message_proto::circuit_relay;
use crate::protocol;
use crate::RequestId;
use futures::channel::oneshot::{self, Canceled};
use futures::future::BoxFuture;
use futures::prelude::*;
use futures::stream::FuturesUnordered;
use libp2p_core::connection::ConnectionId;
use libp2p_core::either::{EitherError, EitherOutput};
use libp2p_core::{upgrade, ConnectedPoint, Multiaddr, PeerId};
use libp2p_swarm::{
IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, ProtocolsHandlerEvent,
ProtocolsHandlerUpgrErr, SubstreamProtocol,
};
use log::warn;
use std::task::{Context, Poll};
use std::time::Duration;
use wasm_timer::Instant;
pub struct RelayHandlerConfig {
pub connection_idle_timeout: Duration,
}
pub struct RelayHandlerProto {
pub config: RelayHandlerConfig,
}
impl IntoProtocolsHandler for RelayHandlerProto {
type Handler = RelayHandler;
fn into_handler(self, remote_peer_id: &PeerId, endpoint: &ConnectedPoint) -> Self::Handler {
RelayHandler::new(
self.config,
*remote_peer_id,
endpoint.get_remote_address().clone(),
)
}
fn inbound_protocol(&self) -> <Self::Handler as ProtocolsHandler>::InboundProtocol {
protocol::RelayListen::new()
}
}
/// Protocol handler that handles the relay protocol.
///
/// There are four possible situations in play here:
///
/// - The handler emits [`RelayHandlerEvent::IncomingRelayReq`] if the node we handle asks us to act
/// as a relay. You must send a [`RelayHandlerIn::OutgoingDstReq`] to another handler, or send
/// back a [`RelayHandlerIn::DenyIncomingRelayReq`].
///
/// - The handler emits [`RelayHandlerEvent::IncomingDstReq`] if the node we handle asks us to act
/// as a destination. You must either send back a [`RelayHandlerIn::AcceptDstReq`]`, or send back
/// a [`RelayHandlerIn::DenyDstReq`].
///
/// - Send a [`RelayHandlerIn::OutgoingRelayReq`] if the node we handle must act as a relay to a
/// destination. The handler will either send back a
/// [`RelayHandlerEvent::OutgoingRelayReqSuccess`] containing the stream to the destination, or a
/// [`RelayHandlerEvent::OutgoingRelayReqError`].
///
/// - Send a [`RelayHandlerIn::OutgoingDstReq`] if the node we handle must act as a destination. The
/// handler will automatically notify the source whether the request was accepted or denied.
pub struct RelayHandler {
config: RelayHandlerConfig,
/// Specifies whether the handled connection is used to listen for incoming relayed connections.
used_for_listening: bool,
remote_address: Multiaddr,
remote_peer_id: PeerId,
/// Futures that send back negative responses.
deny_futures: FuturesUnordered<BoxFuture<'static, Result<(), std::io::Error>>>,
/// Futures that send back an accept response to a relay.
accept_dst_futures: FuturesUnordered<
BoxFuture<
'static,
Result<
(PeerId, protocol::Connection, oneshot::Receiver<()>),
protocol::IncomingDstReqError,
>,
>,
>,
/// Futures that copy from a source to a destination.
copy_futures: FuturesUnordered<BoxFuture<'static, Result<(), protocol::IncomingRelayReqError>>>,
/// Requests asking the remote to become a relay.
outgoing_relay_reqs: Vec<OutgoingRelayReq>,
/// Requests asking the remote to become a destination.
outgoing_dst_reqs: Vec<OutgoingDstReq>,
/// Queue of events to return when polled.
queued_events: Vec<RelayHandlerEvent>,
/// Tracks substreams lend out to other [`RelayHandler`]s or as
/// [`Connection`](protocol::Connection) to the
/// [`RelayTransport`](crate::RelayTransport).
///
/// For each substream to the peer of this handler, there is a future in here that resolves once
/// the given 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<oneshot::Receiver<()>>,
/// The current connection keep-alive.
keep_alive: KeepAlive,
/// A pending fatal error that results in the connection being closed.
pending_error: Option<
ProtocolsHandlerUpgrErr<
EitherError<
protocol::RelayListenError,
EitherError<protocol::OutgoingRelayReqError, protocol::OutgoingDstReqError>,
>,
>,
>,
}
struct OutgoingRelayReq {
src_peer_id: PeerId,
dst_peer_id: PeerId,
request_id: RequestId,
/// Addresses of the destination.
dst_addr: Option<Multiaddr>,
}
struct OutgoingDstReq {
src_peer_id: PeerId,
src_addr: Multiaddr,
src_connection_id: ConnectionId,
request_id: RequestId,
incoming_relay_req: protocol::IncomingRelayReq,
}
/// Event produced by the relay handler.
pub enum RelayHandlerEvent {
/// The remote wants us to relay communications to a third party. You must either send back a
/// [`RelayHandlerIn::DenyIncomingRelayReq`], or an [`RelayHandlerIn::OutgoingDstReq`] to any
/// connection handler for the destination peer, providing the [`protocol::IncomingRelayReq`].
IncomingRelayReq {
request_id: RequestId,
src_addr: Multiaddr,
req: protocol::IncomingRelayReq,
},
/// The remote is a relay and is relaying a connection to us. In other words, we are used as
/// a destination. The behaviour can accept or deny the request via
/// [`AcceptDstReq`](RelayHandlerIn::AcceptDstReq) or
/// [`DenyDstReq`](RelayHandlerIn::DenyDstReq).
IncomingDstReq(protocol::IncomingDstReq),
/// A `RelayReq` that has previously been sent has been accepted by the remote. Contains
/// a substream that communicates with the requested destination.
///
/// > **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),
/// The local node has accepted an incoming destination request. Contains a substream that
/// communicates with the source.
///
/// > **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.
IncomingDstReqSuccess {
stream: protocol::Connection,
src_peer_id: PeerId,
relay_peer_id: PeerId,
relay_addr: Multiaddr,
},
/// A `RelayReq` that has previously been sent by the local node has failed.
OutgoingRelayReqError(PeerId, RequestId),
/// A destination request that has previously been sent by the local node has failed.
///
/// Includes the incoming relay request, which is yet to be denied due to the failure.
OutgoingDstReqError {
src_connection_id: ConnectionId,
incoming_relay_req_deny_fut: BoxFuture<'static, Result<(), std::io::Error>>,
},
}
/// Event that can be sent to the relay handler.
pub enum RelayHandlerIn {
/// Tell the handler whether it is handling a connection used to listen for incoming relayed
/// connections.
UsedForListening(bool),
/// Denies a relay request sent by the node we talk to acting as a source.
DenyIncomingRelayReq(BoxFuture<'static, Result<(), std::io::Error>>),
/// Accepts a destination request sent by the node we talk to.
AcceptDstReq(protocol::IncomingDstReq),
/// Denies a destination request sent by the node we talk to.
DenyDstReq(protocol::IncomingDstReq),
/// Opens a new substream to the remote and asks it to relay communications to a third party.
OutgoingRelayReq {
src_peer_id: PeerId,
dst_peer_id: PeerId,
request_id: RequestId,
/// Addresses known for this peer to transmit to the remote.
dst_addr: Option<Multiaddr>,
},
/// Asks the node to be used as a destination for a relayed connection.
///
/// The positive or negative response will be written to `substream`.
OutgoingDstReq {
/// Peer id of the node whose communications are being relayed.
src_peer_id: PeerId,
/// Address of the node whose communications are being relayed.
src_addr: Multiaddr,
src_connection_id: ConnectionId,
request_id: RequestId,
/// Incoming relay request from the source node.
incoming_relay_req: protocol::IncomingRelayReq,
},
}
impl RelayHandler {
/// Builds a new `RelayHandler`.
pub fn new(
config: RelayHandlerConfig,
remote_peer_id: PeerId,
remote_address: Multiaddr,
) -> Self {
RelayHandler {
config,
used_for_listening: false,
remote_address,
remote_peer_id,
deny_futures: Default::default(),
accept_dst_futures: Default::default(),
copy_futures: Default::default(),
outgoing_relay_reqs: Default::default(),
outgoing_dst_reqs: Default::default(),
queued_events: Default::default(),
alive_lend_out_substreams: Default::default(),
keep_alive: KeepAlive::Yes,
pending_error: None,
}
}
}
impl ProtocolsHandler for RelayHandler {
type InEvent = RelayHandlerIn;
type OutEvent = RelayHandlerEvent;
type Error = ProtocolsHandlerUpgrErr<
EitherError<
protocol::RelayListenError,
EitherError<protocol::OutgoingRelayReqError, protocol::OutgoingDstReqError>,
>,
>;
type InboundProtocol = protocol::RelayListen;
type OutboundProtocol =
upgrade::EitherUpgrade<protocol::OutgoingRelayReq, protocol::OutgoingDstReq>;
type OutboundOpenInfo = RelayOutboundOpenInfo;
type InboundOpenInfo = RequestId;
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
SubstreamProtocol::new(protocol::RelayListen::new(), RequestId::new())
}
fn inject_fully_negotiated_inbound(
&mut self,
protocol: <Self::InboundProtocol as upgrade::InboundUpgrade<NegotiatedSubstream>>::Output,
request_id: Self::InboundOpenInfo,
) {
match protocol {
// We have been asked to act as a relay.
protocol::RelayRemoteReq::RelayReq((incoming_relay_request, notifyee)) => {
self.alive_lend_out_substreams.push(notifyee);
self.queued_events
.push(RelayHandlerEvent::IncomingRelayReq {
request_id,
src_addr: self.remote_address.clone(),
req: incoming_relay_request,
});
}
// We have been asked to become a destination.
protocol::RelayRemoteReq::DstReq(dst_request) => {
self.queued_events
.push(RelayHandlerEvent::IncomingDstReq(dst_request));
}
}
}
fn inject_fully_negotiated_outbound(
&mut self,
protocol: <Self::OutboundProtocol as upgrade::OutboundUpgrade<NegotiatedSubstream>>::Output,
open_info: Self::OutboundOpenInfo,
) {
match protocol {
// We have successfully negotiated a substream towards a relay.
EitherOutput::First((substream_to_dest, notifyee)) => {
let (dst_peer_id, request_id) = match open_info {
RelayOutboundOpenInfo::Relay {
dst_peer_id,
request_id,
} => (dst_peer_id, request_id),
RelayOutboundOpenInfo::Destination { .. } => unreachable!(
"Can not successfully dial a relay when actually dialing a destination."
),
};
self.alive_lend_out_substreams.push(notifyee);
self.queued_events
.push(RelayHandlerEvent::OutgoingRelayReqSuccess(
dst_peer_id,
request_id,
substream_to_dest,
));
}
// We have successfully asked the node to be a destination.
EitherOutput::Second((to_dest_substream, from_dst_read_buffer)) => {
let incoming_relay_req = match open_info {
RelayOutboundOpenInfo::Destination {
incoming_relay_req, ..
} => incoming_relay_req,
RelayOutboundOpenInfo::Relay { .. } => unreachable!(
"Can not successfully dial a destination when actually dialing a relay."
),
};
self.copy_futures
.push(incoming_relay_req.fulfill(to_dest_substream, from_dst_read_buffer));
}
}
}
fn inject_event(&mut self, event: Self::InEvent) {
match event {
RelayHandlerIn::UsedForListening(s) => self.used_for_listening = s,
// Deny a relay request from the node we handle.
RelayHandlerIn::DenyIncomingRelayReq(req) => {
self.deny_futures.push(req);
}
RelayHandlerIn::AcceptDstReq(request) => self.accept_dst_futures.push(request.accept()),
RelayHandlerIn::DenyDstReq(request) => self.deny_futures.push(request.deny()),
// Ask the node we handle to act as a relay.
RelayHandlerIn::OutgoingRelayReq {
src_peer_id,
dst_peer_id,
request_id,
dst_addr,
} => {
self.outgoing_relay_reqs.push(OutgoingRelayReq {
src_peer_id,
dst_peer_id,
request_id,
dst_addr,
});
}
// Ask the node we handle to act as a destination.
RelayHandlerIn::OutgoingDstReq {
src_peer_id,
src_addr,
src_connection_id,
request_id,
incoming_relay_req,
} => {
self.outgoing_dst_reqs.push(OutgoingDstReq {
src_peer_id,
src_addr,
src_connection_id,
request_id,
incoming_relay_req,
});
}
}
}
fn inject_listen_upgrade_error(
&mut self,
_: Self::InboundOpenInfo,
error: ProtocolsHandlerUpgrErr<protocol::RelayListenError>,
) {
match error {
ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select(
upgrade::NegotiationError::Failed,
)) => {}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select(
upgrade::NegotiationError::ProtocolError(e),
)) => {
self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Select(upgrade::NegotiationError::ProtocolError(e)),
));
}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(error)) => {
self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Apply(EitherError::A(error)),
))
}
}
}
fn inject_dial_upgrade_error(
&mut self,
open_info: Self::OutboundOpenInfo,
error: ProtocolsHandlerUpgrErr<
EitherError<protocol::OutgoingRelayReqError, protocol::OutgoingDstReqError>,
>,
) {
match open_info {
RelayOutboundOpenInfo::Relay {
dst_peer_id,
request_id,
} => {
match error {
ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select(
upgrade::NegotiationError::Failed,
)) => {}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select(
upgrade::NegotiationError::ProtocolError(e),
)) => {
self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Select(
upgrade::NegotiationError::ProtocolError(e),
),
));
}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(
EitherError::A(error),
)) => match error {
protocol::OutgoingRelayReqError::Decode(_)
| protocol::OutgoingRelayReqError::Io(_)
| protocol::OutgoingRelayReqError::ParseTypeField
| protocol::OutgoingRelayReqError::ParseStatusField
| protocol::OutgoingRelayReqError::UnexpectedSrcPeerWithStatusType
| protocol::OutgoingRelayReqError::UnexpectedDstPeerWithStatusType
| protocol::OutgoingRelayReqError::ExpectedStatusType(_) => {
self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Apply(EitherError::B(EitherError::A(error))),
));
}
protocol::OutgoingRelayReqError::ExpectedSuccessStatus(status) => {
match status {
circuit_relay::Status::Success => {
unreachable!("Status success was explicitly expected earlier.")
}
// With either status below there is no reason to stay connected.
// Thus terminate the connection.
circuit_relay::Status::HopSrcAddrTooLong
| circuit_relay::Status::HopSrcMultiaddrInvalid
| circuit_relay::Status::MalformedMessage => {
self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Apply(EitherError::B(
EitherError::A(error),
)),
));
}
// While useless for reaching this particular destination, the
// connection to the relay might still proof helpful for other
// destinations. Thus do not terminate the connection.
circuit_relay::Status::StopSrcAddrTooLong
| circuit_relay::Status::StopDstAddrTooLong
| circuit_relay::Status::StopSrcMultiaddrInvalid
| circuit_relay::Status::StopDstMultiaddrInvalid
| circuit_relay::Status::StopRelayRefused
| circuit_relay::Status::HopDstAddrTooLong
| circuit_relay::Status::HopDstMultiaddrInvalid
| circuit_relay::Status::HopNoConnToDst
| circuit_relay::Status::HopCantDialDst
| circuit_relay::Status::HopCantOpenDstStream
| circuit_relay::Status::HopCantSpeakRelay
| circuit_relay::Status::HopCantRelayToSelf => {}
}
}
},
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(
EitherError::B(_),
)) => {
unreachable!("Can not receive an OutgoingDstReqError when dialing a relay.")
}
}
self.queued_events
.push(RelayHandlerEvent::OutgoingRelayReqError(
dst_peer_id,
request_id,
));
}
RelayOutboundOpenInfo::Destination {
src_connection_id,
incoming_relay_req,
..
} => {
let err_code = match error {
ProtocolsHandlerUpgrErr::Timeout | ProtocolsHandlerUpgrErr::Timer => {
circuit_relay::Status::HopCantOpenDstStream
}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select(
upgrade::NegotiationError::Failed,
)) => circuit_relay::Status::HopCantSpeakRelay,
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Select(
upgrade::NegotiationError::ProtocolError(e),
)) => {
self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Select(
upgrade::NegotiationError::ProtocolError(e),
),
));
circuit_relay::Status::HopCantSpeakRelay
}
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(
EitherError::A(_),
)) => unreachable!(
"Can not receive an OutgoingRelayReqError when dialing a destination."
),
ProtocolsHandlerUpgrErr::Upgrade(upgrade::UpgradeError::Apply(
EitherError::B(error),
)) => {
match error {
protocol::OutgoingDstReqError::Decode(_)
| protocol::OutgoingDstReqError::Io(_)
| protocol::OutgoingDstReqError::ParseTypeField
| protocol::OutgoingDstReqError::ParseStatusField
| protocol::OutgoingDstReqError::UnexpectedSrcPeerWithStatusType
| protocol::OutgoingDstReqError::UnexpectedDstPeerWithStatusType
| protocol::OutgoingDstReqError::ExpectedStatusType(_) => {
self.pending_error = Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Apply(EitherError::B(EitherError::B(
error,
))),
));
circuit_relay::Status::HopCantOpenDstStream
}
protocol::OutgoingDstReqError::ExpectedSuccessStatus(status) => {
match status {
circuit_relay::Status::Success => {
unreachable!(
"Status success was explicitly expected earlier."
)
}
// A destination node returning `Hop.*` status is a protocol
// violation. Thus terminate the connection.
circuit_relay::Status::HopDstAddrTooLong
| circuit_relay::Status::HopDstMultiaddrInvalid
| circuit_relay::Status::HopNoConnToDst
| circuit_relay::Status::HopCantDialDst
| circuit_relay::Status::HopCantOpenDstStream
| circuit_relay::Status::HopCantSpeakRelay
| circuit_relay::Status::HopCantRelayToSelf
| circuit_relay::Status::HopSrcAddrTooLong
| circuit_relay::Status::HopSrcMultiaddrInvalid => {
self.pending_error =
Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Apply(EitherError::B(
EitherError::B(error),
)),
));
}
// With either status below there is no reason to stay connected.
// Thus terminate the connection.
circuit_relay::Status::StopDstAddrTooLong
| circuit_relay::Status::StopDstMultiaddrInvalid
| circuit_relay::Status::MalformedMessage => {
self.pending_error =
Some(ProtocolsHandlerUpgrErr::Upgrade(
upgrade::UpgradeError::Apply(EitherError::B(
EitherError::B(error),
)),
));
}
// While useless for reaching this particular destination, the
// connection to the relay might still proof helpful for other
// destinations. Thus do not terminate the connection.
circuit_relay::Status::StopSrcAddrTooLong
| circuit_relay::Status::StopSrcMultiaddrInvalid
| circuit_relay::Status::StopRelayRefused => {}
}
status
}
}
}
};
self.queued_events
.push(RelayHandlerEvent::OutgoingDstReqError {
src_connection_id,
incoming_relay_req_deny_fut: incoming_relay_req.deny(err_code),
});
}
}
}
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));
}
// Request the remote to act as a relay.
if !self.outgoing_relay_reqs.is_empty() {
let OutgoingRelayReq {
src_peer_id,
dst_peer_id,
request_id,
dst_addr,
} = self.outgoing_relay_reqs.remove(0);
self.outgoing_relay_reqs.shrink_to_fit();
return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(
upgrade::EitherUpgrade::A(protocol::OutgoingRelayReq::new(
src_peer_id,
dst_peer_id,
dst_addr,
)),
RelayOutboundOpenInfo::Relay {
dst_peer_id,
request_id,
},
),
});
}
// Request the remote to act as destination.
if !self.outgoing_dst_reqs.is_empty() {
let OutgoingDstReq {
src_peer_id,
src_addr,
src_connection_id,
request_id,
incoming_relay_req,
} = self.outgoing_dst_reqs.remove(0);
self.outgoing_dst_reqs.shrink_to_fit();
return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(
upgrade::EitherUpgrade::B(protocol::OutgoingDstReq::new(
src_peer_id,
src_addr,
incoming_relay_req.dst_peer().clone(),
)),
RelayOutboundOpenInfo::Destination {
src_peer_id,
request_id,
src_connection_id,
incoming_relay_req,
},
),
});
}
match self.accept_dst_futures.poll_next_unpin(cx) {
Poll::Ready(Some(Ok((src_peer_id, substream, notifyee)))) => {
self.alive_lend_out_substreams.push(notifyee);
let event = RelayHandlerEvent::IncomingDstReqSuccess {
stream: substream,
src_peer_id,
relay_peer_id: self.remote_peer_id,
relay_addr: self.remote_address.clone(),
};
return Poll::Ready(ProtocolsHandlerEvent::Custom(event));
}
Poll::Ready(Some(Err(e))) => {
log::debug!("Failed to accept destination future: {:?}", e);
}
Poll::Ready(None) => {}
Poll::Pending => {}
}
while let Poll::Ready(Some(result)) = self.copy_futures.poll_next_unpin(cx) {
if let Err(e) = result {
warn!("Incoming relay request failed: {:?}", e);
}
}
while let Poll::Ready(Some(result)) = self.deny_futures.poll_next_unpin(cx) {
if let Err(e) = result {
warn!("Denying request failed: {:?}", e);
}
}
// Report the queued events.
if !self.queued_events.is_empty() {
let event = self.queued_events.remove(0);
return Poll::Ready(ProtocolsHandlerEvent::Custom(event));
}
while let Poll::Ready(Some(Err(Canceled))) =
self.alive_lend_out_substreams.poll_next_unpin(cx)
{}
if self.used_for_listening
|| !self.deny_futures.is_empty()
|| !self.accept_dst_futures.is_empty()
|| !self.copy_futures.is_empty()
|| !self.alive_lend_out_substreams.is_empty()
{
// Protocol handler is busy.
self.keep_alive = KeepAlive::Yes;
} else {
// Protocol handler is idle.
if matches!(self.keep_alive, KeepAlive::Yes) {
self.keep_alive =
KeepAlive::Until(Instant::now() + self.config.connection_idle_timeout);
}
}
Poll::Pending
}
}
#[allow(clippy::large_enum_variant)]
protocols/relay: Implement circuit relay specification (#1838) This commit implements the [libp2p circuit relay](https://github.com/libp2p/specs/tree/master/relay) specification. It is based on previous work from https://github.com/libp2p/rust-libp2p/pull/1134. Instead of altering the `Transport` trait, the approach taken in this commit is to wrap an existing implementation of `Transport` allowing one to: - Intercept `dial` requests with a relayed address. - Inject incoming relayed connections with the local node being the destination. - Intercept `listen_on` requests pointing to a relay, ensuring to keep a constant connection to the relay, waiting for incoming requests with the local node being the destination. More concretely one would wrap an existing `Transport` implementation as seen below, allowing the `Relay` behaviour and the `RelayTransport` to communicate via channels. ### Example ```rust 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(&mut swarm, relay_addr).unwrap(); // Dial node (5678) via relay node (1234). Swarm::dial_addr(&mut swarm, dst_addr).unwrap(); ``` Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com> Co-authored-by: Roman Borschel <romanb@users.noreply.github.com> Co-authored-by: David Craven <david@craven.ch>
2021-03-11 16:07:59 +01:00
pub enum RelayOutboundOpenInfo {
Relay {
dst_peer_id: PeerId,
request_id: RequestId,
},
Destination {
src_peer_id: PeerId,
src_connection_id: ConnectionId,
request_id: RequestId,
incoming_relay_req: protocol::IncomingRelayReq,
},
}