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]]
name = "libp2p-identify"
version = "0.43.0"
version = "0.43.1"
dependencies = [
"async-std",
"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-floodsub = { version = "0.43.0", path = "protocols/floodsub" }
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-kad = { version = "0.44.5", path = "protocols/kad" }
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
- 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"
rust-version = { workspace = true }
description = "Nodes identifcation protocol for libp2p"
version = "0.43.0"
version = "0.43.1"
authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT"
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
// 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 futures::future::BoxFuture;
use futures::prelude::*;
@ -48,7 +49,7 @@ use std::{io, task::Context, task::Poll, time::Duration};
/// permitting the underlying connection to be closed.
pub struct Handler {
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.
events: SmallVec<
[ConnectionHandlerEvent<Either<Identify, Push<OutboundPush>>, (), Event, io::Error>; 4],
@ -80,6 +81,9 @@ pub struct Handler {
/// Address observed by or for the remote.
observed_addr: Multiaddr,
/// Identify information about the remote peer.
remote_info: Option<Info>,
local_supported_protocols: SupportedProtocols,
remote_supported_protocols: HashSet<StreamProtocol>,
external_addresses: HashSet<Multiaddr>,
@ -133,6 +137,7 @@ impl Handler {
observed_addr,
local_supported_protocols: SupportedProtocols::default(),
remote_supported_protocols: HashSet::default(),
remote_info: Default::default(),
external_addresses,
}
}
@ -176,7 +181,8 @@ impl Handler {
) {
match output {
future::Either::Left(remote_info) => {
self.update_supported_protocols_for_remote(&remote_info);
self.handle_incoming_info(&remote_info);
self.events
.push(ConnectionHandlerEvent::NotifyBehaviour(Event::Identified(
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) {
let new_remote_protocols = HashSet::from_iter(remote_info.protocols.clone());
@ -318,11 +330,15 @@ impl ConnectionHandler for Handler {
{
self.inbound_identify_push.take();
if let Ok(info) = res {
self.update_supported_protocols_for_remote(&info);
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Event::Identified(
info,
)));
if let Ok(remote_push_info) = res {
if let Some(mut info) = self.remote_info.clone() {
info.merge(remote_push_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)]
pub struct Info {
/// The public key of the local peer.
@ -82,6 +82,41 @@ pub struct Info {
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 {
type Info = StreamProtocol;
type InfoIter = iter::Once<Self::Info>;
@ -110,7 +145,7 @@ where
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
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
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Output = BoxFuture<'static, Result<Info, UpgradeError>>;
type Output = BoxFuture<'static, Result<PushInfo, UpgradeError>>;
type Error = Void;
type Future = future::Ready<Result<Self::Output, Self::Error>>;
fn upgrade_inbound(self, socket: C, _: Self::Info) -> Self::Future {
// 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(())
}
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
T: AsyncRead + AsyncWrite + Unpin,
{
@ -199,51 +256,26 @@ where
)
.next()
.await
.ok_or(UpgradeError::StreamClosed)??
.try_into()?;
trace!("Received: {:?}", info);
.ok_or(UpgradeError::StreamClosed)??;
Ok(info)
}
impl TryFrom<proto::Identify> for Info {
type Error = UpgradeError;
fn try_from(msg: proto::Identify) -> Result<Self, Self::Error> {
fn parse_multiaddr(bytes: Vec<u8>) -> Result<Multiaddr, multiaddr::Error> {
Multiaddr::try_from(bytes)
}
let listen_addrs = {
let mut addrs = Vec::new();
for addr in msg.listenAddrs.into_iter() {
match parse_multiaddr(addr) {
Ok(a) => addrs.push(a),
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
}
}
}
addrs
};
})
.collect()
}
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 {
public_key,
protocol_version: msg.protocolVersion.unwrap_or_default(),
agent_version: msg.agentVersion.unwrap_or_default(),
listen_addrs,
protocols: msg
.protocols
fn parse_protocols(protocols: Vec<String>) -> Vec<StreamProtocol> {
protocols
.into_iter()
.filter_map(|p| match StreamProtocol::try_from_owned(p) {
Ok(p) => Some(p),
@ -252,8 +284,65 @@ impl TryFrom<proto::Identify> for Info {
None
}
})
.collect(),
observed_addr,
.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 {
type Error = UpgradeError;
fn try_from(msg: proto::Identify) -> Result<Self, Self::Error> {
let public_key = {
match parse_public_key(msg.publicKey) {
Some(key) => key,
// This will always produce a DecodingError if the public key is missing.
None => PublicKey::try_decode_protobuf(Default::default())?,
}
};
let info = Info {
public_key,
protocol_version: msg.protocolVersion.unwrap_or_default(),
agent_version: msg.agentVersion.unwrap_or_default(),
listen_addrs: parse_listen_addrs(msg.listenAddrs),
protocols: parse_protocols(msg.protocols),
observed_addr: parse_observed_addr(msg.observedAddr).unwrap_or(Multiaddr::empty()),
};
Ok(info)
}
}
impl TryFrom<proto::Identify> for PushInfo {
type Error = UpgradeError;
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)
@ -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])
}