fix(identify): handle partial push messages

According to the [spec], all fields within push messages are optional. To handle missing fields, we deserialize push messages into a different struct, patch the previously received, full identify message with it and emit this as the new info.

Previously, we failed to parse the message which causes incompatibility with js-libp2p nodes as they don't send the public key in push messages. See 88c47f51f9/packages/libp2p/src/identify/identify.ts (L205-L210).

[spec]: https://github.com/libp2p/specs/tree/master/identify#identifypush

Pull-Request: #4495.
This commit is contained in:
Marcel Gleeson 2023-09-24 13:36:58 +02:00 committed by GitHub
parent 95890b550b
commit ad45d23d94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 172 additions and 59 deletions

2
Cargo.lock generated
View File

@ -2610,7 +2610,7 @@ dependencies = [
[[package]] [[package]]
name = "libp2p-identify" name = "libp2p-identify"
version = "0.43.0" version = "0.43.1"
dependencies = [ dependencies = [
"async-std", "async-std",
"asynchronous-codec", "asynchronous-codec",

View File

@ -81,7 +81,7 @@ libp2p-deflate = { version = "0.40.0", path = "transports/deflate" }
libp2p-dns = { version = "0.40.1", path = "transports/dns" } libp2p-dns = { version = "0.40.1", path = "transports/dns" }
libp2p-floodsub = { version = "0.43.0", path = "protocols/floodsub" } libp2p-floodsub = { version = "0.43.0", path = "protocols/floodsub" }
libp2p-gossipsub = { version = "0.45.1", path = "protocols/gossipsub" } libp2p-gossipsub = { version = "0.45.1", path = "protocols/gossipsub" }
libp2p-identify = { version = "0.43.0", path = "protocols/identify" } libp2p-identify = { version = "0.43.1", path = "protocols/identify" }
libp2p-identity = { version = "0.2.3" } libp2p-identity = { version = "0.2.3" }
libp2p-kad = { version = "0.44.5", path = "protocols/kad" } libp2p-kad = { version = "0.44.5", path = "protocols/kad" }
libp2p-mdns = { version = "0.44.0", path = "protocols/mdns" } libp2p-mdns = { version = "0.44.0", path = "protocols/mdns" }

View File

@ -1,3 +1,11 @@
## 0.43.1 - unreleased
- Handle partial push messages.
Previously, push messages with partial information were ignored.
See [PR 4495].
[PR 4495]: https://github.com/libp2p/rust-libp2p/pull/4495
## 0.43.0 ## 0.43.0
- Observed addresses (aka. external address candidates) of the local node, reported by a remote node via `libp2p-identify`, are no longer automatically considered confirmed external addresses, in other words they are no longer trusted by default. - Observed addresses (aka. external address candidates) of the local node, reported by a remote node via `libp2p-identify`, are no longer automatically considered confirmed external addresses, in other words they are no longer trusted by default.

View File

@ -3,7 +3,7 @@ name = "libp2p-identify"
edition = "2021" edition = "2021"
rust-version = { workspace = true } rust-version = { workspace = true }
description = "Nodes identifcation protocol for libp2p" description = "Nodes identifcation protocol for libp2p"
version = "0.43.0" version = "0.43.1"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT" license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p" repository = "https://github.com/libp2p/rust-libp2p"

View File

@ -18,7 +18,8 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
use crate::protocol::{Identify, InboundPush, Info, OutboundPush, Push, UpgradeError}; use crate::protocol::{Identify, InboundPush, OutboundPush, Push, UpgradeError};
use crate::protocol::{Info, PushInfo};
use either::Either; use either::Either;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures::prelude::*; use futures::prelude::*;
@ -48,7 +49,7 @@ use std::{io, task::Context, task::Poll, time::Duration};
/// permitting the underlying connection to be closed. /// permitting the underlying connection to be closed.
pub struct Handler { pub struct Handler {
remote_peer_id: PeerId, remote_peer_id: PeerId,
inbound_identify_push: Option<BoxFuture<'static, Result<Info, UpgradeError>>>, inbound_identify_push: Option<BoxFuture<'static, Result<PushInfo, UpgradeError>>>,
/// Pending events to yield. /// Pending events to yield.
events: SmallVec< events: SmallVec<
[ConnectionHandlerEvent<Either<Identify, Push<OutboundPush>>, (), Event, io::Error>; 4], [ConnectionHandlerEvent<Either<Identify, Push<OutboundPush>>, (), Event, io::Error>; 4],
@ -80,6 +81,9 @@ pub struct Handler {
/// Address observed by or for the remote. /// Address observed by or for the remote.
observed_addr: Multiaddr, observed_addr: Multiaddr,
/// Identify information about the remote peer.
remote_info: Option<Info>,
local_supported_protocols: SupportedProtocols, local_supported_protocols: SupportedProtocols,
remote_supported_protocols: HashSet<StreamProtocol>, remote_supported_protocols: HashSet<StreamProtocol>,
external_addresses: HashSet<Multiaddr>, external_addresses: HashSet<Multiaddr>,
@ -133,6 +137,7 @@ impl Handler {
observed_addr, observed_addr,
local_supported_protocols: SupportedProtocols::default(), local_supported_protocols: SupportedProtocols::default(),
remote_supported_protocols: HashSet::default(), remote_supported_protocols: HashSet::default(),
remote_info: Default::default(),
external_addresses, external_addresses,
} }
} }
@ -176,7 +181,8 @@ impl Handler {
) { ) {
match output { match output {
future::Either::Left(remote_info) => { future::Either::Left(remote_info) => {
self.update_supported_protocols_for_remote(&remote_info); self.handle_incoming_info(&remote_info);
self.events self.events
.push(ConnectionHandlerEvent::NotifyBehaviour(Event::Identified( .push(ConnectionHandlerEvent::NotifyBehaviour(Event::Identified(
remote_info, remote_info,
@ -213,6 +219,12 @@ impl Handler {
} }
} }
fn handle_incoming_info(&mut self, info: &Info) {
self.remote_info.replace(info.clone());
self.update_supported_protocols_for_remote(info);
}
fn update_supported_protocols_for_remote(&mut self, remote_info: &Info) { fn update_supported_protocols_for_remote(&mut self, remote_info: &Info) {
let new_remote_protocols = HashSet::from_iter(remote_info.protocols.clone()); let new_remote_protocols = HashSet::from_iter(remote_info.protocols.clone());
@ -318,11 +330,15 @@ impl ConnectionHandler for Handler {
{ {
self.inbound_identify_push.take(); self.inbound_identify_push.take();
if let Ok(info) = res { if let Ok(remote_push_info) = res {
self.update_supported_protocols_for_remote(&info); if let Some(mut info) = self.remote_info.clone() {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Event::Identified( info.merge(remote_push_info);
info, self.handle_incoming_info(&info);
)));
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::Identified(info),
));
};
} }
} }

View File

@ -63,7 +63,7 @@ impl Push<OutboundPush> {
} }
} }
/// Information of a peer sent in protocol messages. /// Identify information of a peer sent in protocol messages.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Info { pub struct Info {
/// The public key of the local peer. /// The public key of the local peer.
@ -82,6 +82,41 @@ pub struct Info {
pub observed_addr: Multiaddr, pub observed_addr: Multiaddr,
} }
impl Info {
pub fn merge(&mut self, info: PushInfo) {
if let Some(public_key) = info.public_key {
self.public_key = public_key;
}
if let Some(protocol_version) = info.protocol_version {
self.protocol_version = protocol_version;
}
if let Some(agent_version) = info.agent_version {
self.agent_version = agent_version;
}
if !info.listen_addrs.is_empty() {
self.listen_addrs = info.listen_addrs;
}
if !info.protocols.is_empty() {
self.protocols = info.protocols;
}
if let Some(observed_addr) = info.observed_addr {
self.observed_addr = observed_addr;
}
}
}
/// Identify push information of a peer sent in protocol messages.
/// Note that missing fields should be ignored, as peers may choose to send partial updates containing only the fields whose values have changed.
#[derive(Debug, Clone)]
pub struct PushInfo {
pub public_key: Option<PublicKey>,
pub protocol_version: Option<String>,
pub agent_version: Option<String>,
pub listen_addrs: Vec<Multiaddr>,
pub protocols: Vec<StreamProtocol>,
pub observed_addr: Option<Multiaddr>,
}
impl UpgradeInfo for Identify { impl UpgradeInfo for Identify {
type Info = StreamProtocol; type Info = StreamProtocol;
type InfoIter = iter::Once<Self::Info>; type InfoIter = iter::Once<Self::Info>;
@ -110,7 +145,7 @@ where
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_outbound(self, socket: C, _: Self::Info) -> Self::Future { fn upgrade_outbound(self, socket: C, _: Self::Info) -> Self::Future {
recv(socket).boxed() recv_identify(socket).boxed()
} }
} }
@ -127,13 +162,13 @@ impl<C> InboundUpgrade<C> for Push<InboundPush>
where where
C: AsyncRead + AsyncWrite + Unpin + Send + 'static, C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{ {
type Output = BoxFuture<'static, Result<Info, UpgradeError>>; type Output = BoxFuture<'static, Result<PushInfo, UpgradeError>>;
type Error = Void; type Error = Void;
type Future = future::Ready<Result<Self::Output, Self::Error>>; type Future = future::Ready<Result<Self::Output, Self::Error>>;
fn upgrade_inbound(self, socket: C, _: Self::Info) -> Self::Future { fn upgrade_inbound(self, socket: C, _: Self::Info) -> Self::Future {
// Lazily upgrade stream, thus allowing upgrade to happen within identify's handler. // Lazily upgrade stream, thus allowing upgrade to happen within identify's handler.
future::ok(recv(socket).boxed()) future::ok(recv_push(socket).boxed())
} }
} }
@ -184,7 +219,29 @@ where
Ok(()) Ok(())
} }
async fn recv<T>(socket: T) -> Result<Info, UpgradeError> async fn recv_push<T>(socket: T) -> Result<PushInfo, UpgradeError>
where
T: AsyncRead + AsyncWrite + Unpin,
{
let info = recv(socket).await?.try_into()?;
trace!("Received {:?}", info);
Ok(info)
}
async fn recv_identify<T>(socket: T) -> Result<Info, UpgradeError>
where
T: AsyncRead + AsyncWrite + Unpin,
{
let info = recv(socket).await?.try_into()?;
trace!("Received {:?}", info);
Ok(info)
}
async fn recv<T>(socket: T) -> Result<proto::Identify, UpgradeError>
where where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
{ {
@ -199,61 +256,93 @@ where
) )
.next() .next()
.await .await
.ok_or(UpgradeError::StreamClosed)?? .ok_or(UpgradeError::StreamClosed)??;
.try_into()?;
trace!("Received: {:?}", info);
Ok(info) Ok(info)
} }
fn parse_listen_addrs(listen_addrs: Vec<Vec<u8>>) -> Vec<Multiaddr> {
listen_addrs
.into_iter()
.filter_map(|bytes| match Multiaddr::try_from(bytes) {
Ok(a) => Some(a),
Err(e) => {
debug!("Unable to parse multiaddr: {e:?}");
None
}
})
.collect()
}
fn parse_protocols(protocols: Vec<String>) -> Vec<StreamProtocol> {
protocols
.into_iter()
.filter_map(|p| match StreamProtocol::try_from_owned(p) {
Ok(p) => Some(p),
Err(e) => {
debug!("Received invalid protocol from peer: {e}");
None
}
})
.collect()
}
fn parse_public_key(public_key: Option<Vec<u8>>) -> Option<PublicKey> {
public_key.and_then(|key| match PublicKey::try_decode_protobuf(&key) {
Ok(k) => Some(k),
Err(e) => {
debug!("Unable to decode public key: {e:?}");
None
}
})
}
fn parse_observed_addr(observed_addr: Option<Vec<u8>>) -> Option<Multiaddr> {
observed_addr.and_then(|bytes| match Multiaddr::try_from(bytes) {
Ok(a) => Some(a),
Err(e) => {
debug!("Unable to parse observed multiaddr: {e:?}");
None
}
})
}
impl TryFrom<proto::Identify> for Info { impl TryFrom<proto::Identify> for Info {
type Error = UpgradeError; type Error = UpgradeError;
fn try_from(msg: proto::Identify) -> Result<Self, Self::Error> { fn try_from(msg: proto::Identify) -> Result<Self, Self::Error> {
fn parse_multiaddr(bytes: Vec<u8>) -> Result<Multiaddr, multiaddr::Error> { let public_key = {
Multiaddr::try_from(bytes) match parse_public_key(msg.publicKey) {
} Some(key) => key,
// This will always produce a DecodingError if the public key is missing.
let listen_addrs = { None => PublicKey::try_decode_protobuf(Default::default())?,
let mut addrs = Vec::new();
for addr in msg.listenAddrs.into_iter() {
match parse_multiaddr(addr) {
Ok(a) => addrs.push(a),
Err(e) => {
debug!("Unable to parse multiaddr: {e:?}");
}
}
}
addrs
};
let public_key = PublicKey::try_decode_protobuf(&msg.publicKey.unwrap_or_default())?;
let observed_addr = match parse_multiaddr(msg.observedAddr.unwrap_or_default()) {
Ok(a) => a,
Err(e) => {
debug!("Unable to parse multiaddr: {e:?}");
Multiaddr::empty()
} }
}; };
let info = Info { let info = Info {
public_key, public_key,
protocol_version: msg.protocolVersion.unwrap_or_default(), protocol_version: msg.protocolVersion.unwrap_or_default(),
agent_version: msg.agentVersion.unwrap_or_default(), agent_version: msg.agentVersion.unwrap_or_default(),
listen_addrs, listen_addrs: parse_listen_addrs(msg.listenAddrs),
protocols: msg protocols: parse_protocols(msg.protocols),
.protocols observed_addr: parse_observed_addr(msg.observedAddr).unwrap_or(Multiaddr::empty()),
.into_iter() };
.filter_map(|p| match StreamProtocol::try_from_owned(p) {
Ok(p) => Some(p), Ok(info)
Err(e) => { }
debug!("Received invalid protocol from peer: {e}"); }
None
} impl TryFrom<proto::Identify> for PushInfo {
}) type Error = UpgradeError;
.collect(),
observed_addr, fn try_from(msg: proto::Identify) -> Result<Self, Self::Error> {
let info = PushInfo {
public_key: parse_public_key(msg.publicKey),
protocol_version: msg.protocolVersion,
agent_version: msg.agentVersion,
listen_addrs: parse_listen_addrs(msg.listenAddrs),
protocols: parse_protocols(msg.protocols),
observed_addr: parse_observed_addr(msg.observedAddr),
}; };
Ok(info) Ok(info)
@ -303,7 +392,7 @@ mod tests {
), ),
}; };
let info = Info::try_from(payload).expect("not to fail"); let info = PushInfo::try_from(payload).expect("not to fail");
assert_eq!(info.listen_addrs, vec![valid_multiaddr]) assert_eq!(info.listen_addrs, vec![valid_multiaddr])
} }