From e0e5dfbe50e4e75900c83ceaf4b2585e39befb32 Mon Sep 17 00:00:00 2001 From: Roman Borschel Date: Mon, 3 Aug 2020 14:52:34 +0200 Subject: [PATCH] [libp2p-noise] Disable sending of legacy handshake payloads (by default). (#1669) * [libp2p-noise] Disable sending of legacy handshake payloads. * Update build_development_transport() to use libp2p-noise. * Update feature flags. * Replace feature flag by config options. * Cleanup * Cleanup * Cleanup --- CHANGELOG.md | 7 +++++ Cargo.toml | 2 +- protocols/noise/CHANGELOG.md | 18 +++++++++++ protocols/noise/Cargo.toml | 2 +- protocols/noise/src/io/handshake.rs | 44 +++++++++++++++++++-------- protocols/noise/src/lib.rs | 47 +++++++++++++++++++++++++---- src/lib.rs | 38 ++++++++++++++++++++--- 7 files changed, 133 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9aeb8b4..ab2cdb65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,13 @@ # Version 0.23.0 (2020-??-??) +**NOTE**: For a smooth upgrade path from `0.21` to `> 0.22` +on an existing deployment, this version must not be skipped +or the provided legacy configuration for `libp2p-noise` used! + +- Bump `libp2p-noise` dependency to `0.22`. See the `libp2p-noise` +changelog for details about the `LegacyConfig`. + - Refactored bandwidth logging ([PR 1670](https://github.com/libp2p/rust-libp2p/pull/1670)). # Version 0.22.0 (2020-07-17) diff --git a/Cargo.toml b/Cargo.toml index 6c1c6241..19259250 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ libp2p-gossipsub = { version = "0.20.0", path = "./protocols/gossipsub", optiona libp2p-identify = { version = "0.20.0", path = "protocols/identify", optional = true } libp2p-kad = { version = "0.21.0", path = "protocols/kad", optional = true } libp2p-mplex = { version = "0.20.0", path = "muxers/mplex", optional = true } -libp2p-noise = { version = "0.21.0", path = "protocols/noise", optional = true } +libp2p-noise = { version = "0.22.0", path = "protocols/noise", optional = true } libp2p-ping = { version = "0.20.0", path = "protocols/ping", optional = true } libp2p-plaintext = { version = "0.20.0", path = "protocols/plaintext", optional = true } libp2p-pnet = { version = "0.19.1", path = "protocols/pnet", optional = true } diff --git a/protocols/noise/CHANGELOG.md b/protocols/noise/CHANGELOG.md index 6c19f01c..5784cbcd 100644 --- a/protocols/noise/CHANGELOG.md +++ b/protocols/noise/CHANGELOG.md @@ -1,3 +1,21 @@ +# 0.22.0 [2020-??-??] + +**NOTE**: For a smooth upgrade path from `0.20` to `> 0.21` +on an existing deployment, this version must not be skipped +or the provided `LegacyConfig` used! + +- Stop sending length-prefixed protobuf frames in handshake +payloads by default. See [issue 1631](https://github.com/libp2p/rust-libp2p/issues/1631). +The new `LegacyConfig` is provided to optionally +configure sending the legacy handshake. Note: This release +always supports receiving legacy handshake payloads. A future +release will also move receiving legacy handshake payloads +into a `LegacyConfig` option. However, all legacy configuration +options will eventually be removed, so this is primarily to allow +delaying the handshake upgrade or keeping compatibility with a network +whose peers are slow to upgrade, without having to freeze the +version of `libp2p-noise` altogether in these projects. + # 0.21.0 [2020-07-17] **NOTE**: For a smooth upgrade path from `0.20` to `> 0.21` diff --git a/protocols/noise/Cargo.toml b/protocols/noise/Cargo.toml index 7f513482..d3bef686 100644 --- a/protocols/noise/Cargo.toml +++ b/protocols/noise/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libp2p-noise" description = "Cryptographic handshake protocol using the noise framework." -version = "0.21.0" +version = "0.22.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" diff --git a/protocols/noise/src/io/handshake.rs b/protocols/noise/src/io/handshake.rs index affd38a3..003aec2a 100644 --- a/protocols/noise/src/io/handshake.rs +++ b/protocols/noise/src/io/handshake.rs @@ -25,6 +25,7 @@ mod payload_proto { } use bytes::Bytes; +use crate::LegacyConfig; use crate::error::NoiseError; use crate::protocol::{Protocol, PublicKey, KeypairIdentity}; use crate::io::{NoiseOutput, framed::NoiseFramed}; @@ -125,14 +126,15 @@ pub fn rt1_initiator( io: T, session: Result, identity: KeypairIdentity, - identity_x: IdentityExchange + identity_x: IdentityExchange, + legacy: LegacyConfig, ) -> Handshake where T: AsyncWrite + AsyncRead + Send + Unpin + 'static, C: Protocol + AsRef<[u8]> { Handshake(Box::pin(async move { - let mut state = State::new(io, session, identity, identity_x)?; + let mut state = State::new(io, session, identity, identity_x, legacy)?; send_identity(&mut state).await?; recv_identity(&mut state).await?; state.finish() @@ -160,13 +162,14 @@ pub fn rt1_responder( session: Result, identity: KeypairIdentity, identity_x: IdentityExchange, + legacy: LegacyConfig, ) -> Handshake where T: AsyncWrite + AsyncRead + Send + Unpin + 'static, C: Protocol + AsRef<[u8]> { Handshake(Box::pin(async move { - let mut state = State::new(io, session, identity, identity_x)?; + let mut state = State::new(io, session, identity, identity_x, legacy)?; recv_identity(&mut state).await?; send_identity(&mut state).await?; state.finish() @@ -195,14 +198,15 @@ pub fn rt15_initiator( io: T, session: Result, identity: KeypairIdentity, - identity_x: IdentityExchange + identity_x: IdentityExchange, + legacy: LegacyConfig, ) -> Handshake where T: AsyncWrite + AsyncRead + Unpin + Send + 'static, C: Protocol + AsRef<[u8]> { Handshake(Box::pin(async move { - let mut state = State::new(io, session, identity, identity_x)?; + let mut state = State::new(io, session, identity, identity_x, legacy)?; send_empty(&mut state).await?; recv_identity(&mut state).await?; send_identity(&mut state).await?; @@ -232,14 +236,15 @@ pub fn rt15_responder( io: T, session: Result, identity: KeypairIdentity, - identity_x: IdentityExchange + identity_x: IdentityExchange, + legacy: LegacyConfig, ) -> Handshake where T: AsyncWrite + AsyncRead + Unpin + Send + 'static, C: Protocol + AsRef<[u8]> { Handshake(Box::pin(async move { - let mut state = State::new(io, session, identity, identity_x)?; + let mut state = State::new(io, session, identity, identity_x, legacy)?; recv_empty(&mut state).await?; send_identity(&mut state).await?; recv_identity(&mut state).await?; @@ -263,6 +268,8 @@ struct State { id_remote_pubkey: Option, /// Whether to send the public identity key of the local node to the remote. send_identity: bool, + /// Legacy configuration parameters. + legacy: LegacyConfig, } impl State { @@ -275,7 +282,8 @@ impl State { io: T, session: Result, identity: KeypairIdentity, - identity_x: IdentityExchange + identity_x: IdentityExchange, + legacy: LegacyConfig, ) -> Result { let (id_remote_pubkey, send_identity) = match identity_x { IdentityExchange::Mutual => (None, true), @@ -289,7 +297,8 @@ impl State { io: NoiseFramed::new(io, s), dh_remote_pubkey_sig: None, id_remote_pubkey, - send_identity + send_identity, + legacy, } ) } @@ -424,17 +433,26 @@ where T: AsyncWrite + Unpin, { let mut pb = payload_proto::NoiseHandshakePayload::default(); + if state.send_identity { pb.identity_key = state.identity.public.clone().into_protobuf_encoding() } + if let Some(ref sig) = state.identity.signature { pb.identity_sig = sig.clone() } - // NOTE: We temporarily need to continue sending the (legacy) length prefix - // for a short while to permit migration. - let mut msg = Vec::with_capacity(pb.encoded_len() + 2); - msg.extend_from_slice(&(pb.encoded_len() as u16).to_be_bytes()); + + let mut msg = + if state.legacy.send_legacy_handshake { + let mut msg = Vec::with_capacity(2 + pb.encoded_len()); + msg.extend_from_slice(&(pb.encoded_len() as u16).to_be_bytes()); + msg + } else { + Vec::with_capacity(pb.encoded_len()) + }; + pb.encode(&mut msg).expect("Vec provides capacity as needed"); state.io.send(&msg).await?; + Ok(()) } diff --git a/protocols/noise/src/lib.rs b/protocols/noise/src/lib.rs index f73a9e9a..a10ab572 100644 --- a/protocols/noise/src/lib.rs +++ b/protocols/noise/src/lib.rs @@ -76,6 +76,7 @@ use zeroize::Zeroize; pub struct NoiseConfig { dh_keys: AuthenticKeypair, params: ProtocolParams, + legacy: LegacyConfig, remote: R, _marker: std::marker::PhantomData

} @@ -86,6 +87,12 @@ impl NoiseConfig { pub fn into_authenticated(self) -> NoiseAuthenticated { NoiseAuthenticated { config: self } } + + /// Sets the legacy configuration options to use, if any. + pub fn set_legacy_config(&mut self, cfg: LegacyConfig) -> &mut Self { + self.legacy = cfg; + self + } } impl NoiseConfig @@ -97,6 +104,7 @@ where NoiseConfig { dh_keys, params: C::params_ix(), + legacy: LegacyConfig::default(), remote: (), _marker: std::marker::PhantomData } @@ -112,6 +120,7 @@ where NoiseConfig { dh_keys, params: C::params_xx(), + legacy: LegacyConfig::default(), remote: (), _marker: std::marker::PhantomData } @@ -130,6 +139,7 @@ where NoiseConfig { dh_keys, params: C::params_ik(), + legacy: LegacyConfig::default(), remote: (), _marker: std::marker::PhantomData } @@ -152,6 +162,7 @@ where NoiseConfig { dh_keys, params: C::params_ik(), + legacy: LegacyConfig::default(), remote: (remote_dh, remote_id), _marker: std::marker::PhantomData } @@ -177,7 +188,8 @@ where .map_err(NoiseError::from); handshake::rt1_responder(socket, session, self.dh_keys.into_identity(), - IdentityExchange::Mutual) + IdentityExchange::Mutual, + self.legacy) } } @@ -198,7 +210,8 @@ where .map_err(NoiseError::from); handshake::rt1_initiator(socket, session, self.dh_keys.into_identity(), - IdentityExchange::Mutual) + IdentityExchange::Mutual, + self.legacy) } } @@ -221,7 +234,8 @@ where .map_err(NoiseError::from); handshake::rt15_responder(socket, session, self.dh_keys.into_identity(), - IdentityExchange::Mutual) + IdentityExchange::Mutual, + self.legacy) } } @@ -242,7 +256,8 @@ where .map_err(NoiseError::from); handshake::rt15_initiator(socket, session, self.dh_keys.into_identity(), - IdentityExchange::Mutual) + IdentityExchange::Mutual, + self.legacy) } } @@ -265,7 +280,8 @@ where .map_err(NoiseError::from); handshake::rt1_responder(socket, session, self.dh_keys.into_identity(), - IdentityExchange::Receive) + IdentityExchange::Receive, + self.legacy) } } @@ -287,7 +303,8 @@ where .map_err(NoiseError::from); handshake::rt1_initiator(socket, session, self.dh_keys.into_identity(), - IdentityExchange::Send { remote: self.remote.1 }) + IdentityExchange::Send { remote: self.remote.1 }, + self.legacy) } } @@ -365,3 +382,21 @@ where })) } } + +/// Legacy configuration options. +#[derive(Clone)] +pub struct LegacyConfig { + /// Whether to continue sending legacy handshake payloads, + /// i.e. length-prefixed protobuf payloads inside a length-prefixed + /// noise frame. These payloads are not interoperable with other + /// libp2p implementations. + pub send_legacy_handshake: bool, +} + +impl Default for LegacyConfig { + fn default() -> Self { + Self { + send_legacy_handshake: false, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6f019e19..1f541aff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,19 +272,49 @@ pub use self::transport_ext::TransportExt; /// > **Note**: This `Transport` is not suitable for production usage, as its implementation /// > reserves the right to support additional protocols or remove deprecated protocols. #[cfg(all(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")), any(feature = "tcp-async-std", feature = "tcp-tokio"), feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))] -#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")), any(feature = "tcp-async-std", feature = "tcp-tokio"), feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))))] +#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")), any(feature = "tcp-async-std", feature = "tcp-tokio"), feature = "websocket", feature = "noise", feature = "mplex", feature = "yamux"))))] pub fn build_development_transport(keypair: identity::Keypair) -> std::io::Result> + Send + Sync), Error = impl std::error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone> { - build_tcp_ws_secio_mplex_yamux(keypair) + build_tcp_ws_noise_mplex_yamux(keypair) } +/// Builds an implementation of `Transport` that is suitable for usage with the `Swarm`. +/// +/// The implementation supports TCP/IP, WebSockets over TCP/IP, noise as the encryption layer, +/// and mplex or yamux as the multiplexing layer. +#[cfg(all(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")), any(feature = "tcp-async-std", feature = "tcp-tokio"), feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))] +#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")), any(feature = "tcp-async-std", feature = "tcp-tokio"), feature = "websocket", feature = "noise", feature = "mplex", feature = "yamux"))))] +pub fn build_tcp_ws_noise_mplex_yamux(keypair: identity::Keypair) + -> std::io::Result> + Send + Sync), Error = impl std::error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone> +{ + let transport = { + #[cfg(feature = "tcp-async-std")] + let tcp = tcp::TcpConfig::new().nodelay(true); + #[cfg(feature = "tcp-tokio")] + let tcp = tcp::TokioTcpConfig::new().nodelay(true); + let transport = dns::DnsConfig::new(tcp)?; + let trans_clone = transport.clone(); + transport.or_transport(websocket::WsConfig::new(trans_clone)) + }; + + let noise_keys = noise::Keypair::::new() + .into_authentic(&keypair) + .expect("Signing libp2p-noise static DH keypair failed."); + + Ok(transport + .upgrade(core::upgrade::Version::V1) + .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) + .multiplex(core::upgrade::SelectUpgrade::new(yamux::Config::default(), mplex::MplexConfig::new())) + .map(|(peer, muxer), _| (peer, core::muxing::StreamMuxerBox::new(muxer))) + .timeout(std::time::Duration::from_secs(20))) +} + + /// Builds an implementation of `Transport` that is suitable for usage with the `Swarm`. /// /// The implementation supports TCP/IP, WebSockets over TCP/IP, secio as the encryption layer, /// and mplex or yamux as the multiplexing layer. -/// -/// > **Note**: If you ever need to express the type of this `Transport`. #[cfg(all(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")), any(feature = "tcp-async-std", feature = "tcp-tokio"), feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))] #[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")), any(feature = "tcp-async-std", feature = "tcp-tokio"), feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))))] pub fn build_tcp_ws_secio_mplex_yamux(keypair: identity::Keypair)