Rework the transport upgrade API. (#1240)

* Rework the transport upgrade API.

ALthough transport upgrades must follow a specific pattern
in order fot the resulting transport to be usable with a
`Network` or `Swarm`, that pattern is currently not well
reflected in the transport upgrade API. Rather, transport
upgrades are rather laborious and involve non-trivial code
duplication.

This commit introduces a `transport::upgrade::Builder` that is
obtained from `Transport::upgrade`. The `Builder` encodes the
previously implicit rules for transport upgrades:

  1. Authentication upgrades must happen first.
  2. Any number of upgrades may follow.
  3. A multiplexer upgrade must happen last.

Since multiplexing is the last (regular) transport upgrade (because
that upgrade yields a `StreamMuxer` which is no longer a `AsyncRead`
/ `AsyncWrite` resource, which the upgrade process is based on),
the upgrade starts with `Transport::upgrade` and ends with
`Builder::multiplex`, which drops back down to the `Transport`,
providing a fluent API.

Authentication and multiplexer upgrades must furthermore adhere
to a minimal contract w.r.t their outputs:

  1. An authentication upgrade is given an (async) I/O resource `C`
     and must produce a pair `(I, D)` where `I: ConnectionInfo` and
     `D` is a new (async) I/O resource `D`.
  2. A multiplexer upgrade is given an (async) I/O resource `C`
     and must produce a `M: StreamMuxer`.

To that end, two changes to the `secio` and `noise` protocols have been
made:

  1. The `secio` upgrade now outputs a pair of `(PeerId, SecioOutput)`.
     The former implements `ConnectionInfo` and the latter `AsyncRead` /
     `AsyncWrite`, fulfilling the `Builder` contract.

  2. A new `NoiseAuthenticated` upgrade has been added that wraps around
     any noise upgrade (i.e. `NoiseConfig`) and has an output of
     `(PeerId, NoiseOutput)`, i.e. it checks if the `RemoteIdentity` from
     the handshake output is an `IdentityKey`, failing if that is not the
     case. This is the standard upgrade procedure one wants for integrating
     noise with libp2p-core/swarm.

* Cleanup

* Add a new integration test.

* Add missing license.
This commit is contained in:
Roman Borschel
2019-09-10 15:42:45 +02:00
committed by GitHub
parent e177486ca8
commit 8c119269d6
20 changed files with 734 additions and 357 deletions

View File

@@ -21,65 +21,54 @@
//! The `secio` protocol is a middleware that will encrypt and decrypt communications going
//! through a socket (or anything that implements `AsyncRead + AsyncWrite`).
//!
//! # Connection upgrade
//! # Usage
//!
//! The `SecioConfig` struct implements the `ConnectionUpgrade` trait. You can apply it over a
//! `Transport` by using the `with_upgrade` method. The returned object will also implement
//! `Transport` and will automatically apply the secio protocol over any connection that is opened
//! through it.
//! The `SecioConfig` implements [`InboundUpgrade`] and [`OutboundUpgrade`] and thus
//! serves as a connection upgrade for authentication of a transport.
//! See [`authenticate`](libp2p_core::transport::upgrade::builder::Builder::authenticate).
//!
//! ```no_run
//! # fn main() {
//! use futures::Future;
//! use libp2p_secio::{SecioConfig, SecioOutput};
//! use libp2p_core::{Multiaddr, identity, upgrade::apply_inbound};
//! use libp2p_core::{PeerId, Multiaddr, identity};
//! use libp2p_core::transport::Transport;
//! use libp2p_mplex::MplexConfig;
//! use libp2p_tcp::TcpConfig;
//! use tokio_io::io::write_all;
//! use tokio::runtime::current_thread::Runtime;
//!
//! let dialer = TcpConfig::new()
//! .with_upgrade({
//! # let private_key = &mut [];
//! // See the documentation of `identity::Keypair`.
//! let keypair = identity::Keypair::rsa_from_pkcs8(private_key).unwrap();
//! SecioConfig::new(keypair)
//! })
//! .map(|out: SecioOutput<_>, _| out.stream);
//! // Create a local peer identity.
//! let local_keys = identity::Keypair::generate_ed25519();
//!
//! let future = dialer.dial("/ip4/127.0.0.1/tcp/12345".parse::<Multiaddr>().unwrap())
//! .unwrap()
//! .map_err(|e| panic!("error: {:?}", e))
//! .and_then(|connection| {
//! // Sends "hello world" on the connection, will be encrypted.
//! write_all(connection, "hello world")
//! })
//! .map_err(|e| panic!("error: {:?}", e));
//! // Create a `Transport`.
//! let transport = TcpConfig::new()
//! .upgrade()
//! .authenticate(SecioConfig::new(local_keys.clone()))
//! .multiplex(MplexConfig::default());
//!
//! let mut rt = Runtime::new().unwrap();
//! let _ = rt.block_on(future).unwrap();
//! // The transport can be used with a `Network` from `libp2p-core`, or a
//! // `Swarm` from from `libp2p-swarm`. See the documentation of these
//! // crates for mode details.
//!
//! // let network = Network::new(transport, local_keys.public().into_peer_id());
//! // let swarm = Swarm::new(transport, behaviour, local_keys.public().into_peer_id());
//! # }
//! ```
//!
//! # Manual usage
//!
//! > **Note**: You are encouraged to use `SecioConfig` as described above.
//!
//! You can add the `secio` layer over a socket by calling `SecioMiddleware::handshake()`. This
//! method will perform a handshake with the host, and return a future that corresponds to the
//! moment when the handshake succeeds or errored. On success, the future produces a
//! `SecioMiddleware` that implements `Sink` and `Stream` and can be used to send packets of data.
//!
pub use self::error::SecioError;
use bytes::BytesMut;
use futures::stream::MapErr as StreamMapErr;
use futures::{Future, Poll, Sink, StartSend, Stream};
use libp2p_core::{PublicKey, identity, upgrade::{UpgradeInfo, InboundUpgrade, OutboundUpgrade, Negotiated}};
use libp2p_core::{
PeerId,
PublicKey,
identity,
upgrade::{UpgradeInfo, InboundUpgrade, OutboundUpgrade, Negotiated}
};
use log::debug;
use rw_stream_sink::RwStreamSink;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use std::io;
use std::iter;
use tokio_io::{AsyncRead, AsyncWrite};
@@ -145,7 +134,7 @@ impl SecioConfig {
self
}
fn handshake<T>(self, socket: T) -> impl Future<Item=SecioOutput<T>, Error=SecioError>
fn handshake<T>(self, socket: T) -> impl Future<Item=(PeerId, SecioOutput<T>), Error=SecioError>
where
T: AsyncRead + AsyncWrite + Send + 'static
{
@@ -153,11 +142,13 @@ impl SecioConfig {
SecioMiddleware::handshake(socket, self)
.map(|(stream_sink, pubkey, ephemeral)| {
let mapped = stream_sink.map_err(map_err as fn(_) -> _);
SecioOutput {
let peer = pubkey.clone().into_peer_id();
let io = SecioOutput {
stream: RwStreamSink::new(mapped),
remote_key: pubkey,
ephemeral_public_key: ephemeral
}
};
(peer, io)
})
}
}
@@ -168,7 +159,7 @@ where
S: AsyncRead + AsyncWrite,
{
/// The encrypted stream.
pub stream: RwStreamSink<StreamMapErr<SecioMiddleware<S>, fn(SecioError) -> IoError>>,
pub stream: RwStreamSink<StreamMapErr<SecioMiddleware<S>, fn(SecioError) -> io::Error>>,
/// The public key of the remote.
pub remote_key: PublicKey,
/// Ephemeral public key used during the negotiation.
@@ -188,7 +179,7 @@ impl<T> InboundUpgrade<T> for SecioConfig
where
T: AsyncRead + AsyncWrite + Send + 'static
{
type Output = SecioOutput<Negotiated<T>>;
type Output = (PeerId, SecioOutput<Negotiated<T>>);
type Error = SecioError;
type Future = Box<dyn Future<Item = Self::Output, Error = Self::Error> + Send>;
@@ -201,7 +192,7 @@ impl<T> OutboundUpgrade<T> for SecioConfig
where
T: AsyncRead + AsyncWrite + Send + 'static
{
type Output = SecioOutput<Negotiated<T>>;
type Output = (PeerId, SecioOutput<Negotiated<T>>);
type Error = SecioError;
type Future = Box<dyn Future<Item = Self::Output, Error = Self::Error> + Send>;
@@ -210,10 +201,37 @@ where
}
}
#[inline]
fn map_err(err: SecioError) -> IoError {
impl<S: AsyncRead + AsyncWrite> io::Read for SecioOutput<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream.read(buf)
}
}
impl<S: AsyncRead + AsyncWrite> AsyncRead for SecioOutput<S> {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.stream.prepare_uninitialized_buffer(buf)
}
}
impl<S: AsyncRead + AsyncWrite> io::Write for SecioOutput<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stream.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stream.flush()
}
}
impl<S: AsyncRead + AsyncWrite> AsyncWrite for SecioOutput<S> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.stream.shutdown()
}
}
fn map_err(err: SecioError) -> io::Error {
debug!("error during secio handshake {:?}", err);
IoError::new(IoErrorKind::InvalidData, err)
io::Error::new(io::ErrorKind::InvalidData, err)
}
/// Wraps around an object that implements `AsyncRead` and `AsyncWrite`.
@@ -247,7 +265,7 @@ where
S: AsyncRead + AsyncWrite,
{
type SinkItem = BytesMut;
type SinkError = IoError;
type SinkError = io::Error;
#[inline]
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {