Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

authority-discovery: Ignore multi-addresses without IP from DHT records #6545

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a0fca05
authority-discovery: Filter out empty addresses after removing `/p2p/..`
lexnv Nov 19, 2024
3e3e461
authority-discovery: Ignore empty addresses from DHT
lexnv Nov 19, 2024
0724372
authority-discovery/tests: Add test for is empty address
lexnv Nov 19, 2024
7e5aa7b
litep2p: Ignore empty addresses
lexnv Nov 19, 2024
5e71861
litep2p/discovery: Validate empty / no-IP multiaddresses
lexnv Nov 19, 2024
f36d749
libp2p/peer_info: Confirm address only if it is valid
lexnv Nov 19, 2024
9e7c867
Add PRdoc
lexnv Nov 19, 2024
109bb1c
peer_info: Downgrade warn to debug logs
lexnv Nov 20, 2024
281e1eb
network/types: Move verification of external addr
lexnv Nov 21, 2024
f09e25c
network/types: Ensure peerID functionality
lexnv Nov 21, 2024
3906c57
Use crate::type::Multiaddr shared functionality
lexnv Nov 21, 2024
9061359
authority-discovery: Use is_valid_external_address method
lexnv Nov 22, 2024
52acb47
network/types: Enhance address validation by TCP / UDP protocols
lexnv Nov 22, 2024
6389be9
network: Rename is_valid_external_address
lexnv Nov 22, 2024
d109dd7
types/tests: Check empty addresses
lexnv Nov 22, 2024
d6da5d4
types/tests: Extensive tests to check the address is valid
lexnv Nov 22, 2024
6372b5f
litep2p: Use new multiaddr functionality
lexnv Nov 22, 2024
0144119
Merge remote-tracking branch 'origin/master' into lexnv/authorit-empt…
lexnv Nov 22, 2024
335e0e8
authority-discovery: Extra checks before publishing addresses
lexnv Nov 22, 2024
19a3c4e
authority-discovery/tests: Test are moved to network/types
lexnv Nov 22, 2024
a0243b7
network/types: Move is_global directly on the multiaddr
lexnv Nov 22, 2024
9fe8cff
Merge branch 'master' into lexnv/authorit-empty-addr
lexnv Nov 25, 2024
bb5653e
authorit-discovery: Add more logs
lexnv Nov 25, 2024
36ff0c7
types: Do not check peerID implicitely
lexnv Nov 25, 2024
457b4f1
types/tests: Adjust testing
lexnv Nov 25, 2024
45b02e1
Update prdoc
lexnv Nov 25, 2024
7fca2c4
Merge remote-tracking branch 'origin/master' into lexnv/authorit-empt…
lexnv Nov 25, 2024
65ca987
Update PR doc
lexnv Nov 25, 2024
dd8e5ec
litep2p: Downgrade warns to debug
lexnv Nov 25, 2024
7ea83f4
Merge branch 'master' into lexnv/authorit-empty-addr
lexnv Nov 26, 2024
a5e756d
Merge branch 'master' into lexnv/authorit-empty-addr
lexnv Nov 26, 2024
86cab78
Merge branch 'master' into lexnv/authorit-empty-addr
lexnv Nov 27, 2024
66d47b9
Merge branch 'master' into lexnv/authorit-empty-addr
lexnv Nov 27, 2024
40ebbda
Update from lexnv running command 'prdoc --audience node_dev --bump p…
actions-user Nov 27, 2024
d075120
Force CI
lexnv Nov 27, 2024
fd1a4b4
Merge branch 'master' into lexnv/authorit-empty-addr
lexnv Nov 27, 2024
d22d23f
Merge branch 'master' into lexnv/authorit-empty-addr
lexnv Nov 28, 2024
711f1c3
Merge branch 'master' into lexnv/authorit-empty-addr
bkchr Dec 23, 2024
e87378d
Update substrate/client/authority-discovery/Cargo.toml
bkchr Dec 23, 2024
b8285bc
Update substrate/client/network/src/litep2p/mod.rs
bkchr Dec 23, 2024
fa01496
Update substrate/client/network/src/litep2p/mod.rs
bkchr Dec 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions prdoc/pr_6545.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
title: Ignore multi-addresses without IP from DHT records

doc:
- audience: [ Node Dev, Node Operator ]
description: |
This PR ensures that multiaddresses without an IP protocol are not published or utilized from DHT records.

crates:
- name: sc-network
bump: patch
- name: sc-authority-discovery
bump: patch
- name: sc-network-types
bump: patch
1 change: 0 additions & 1 deletion substrate/client/authority-discovery/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ prost-build = { workspace = true }
codec = { workspace = true }
futures = { workspace = true }
futures-timer = { workspace = true }
ip_network = { workspace = true }
multihash = { workspace = true }
linked_hash_set = { workspace = true }
log = { workspace = true, default-features = true }
Expand Down
49 changes: 30 additions & 19 deletions substrate/client/authority-discovery/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ use futures::{channel::mpsc, future, stream::Fuse, FutureExt, Stream, StreamExt}

use addr_cache::AddrCache;
use codec::{Decode, Encode};
use ip_network::IpNetwork;
use linked_hash_set::LinkedHashSet;
use sc_network_types::kad::{Key, PeerRecord, Record};

Expand Down Expand Up @@ -280,7 +279,9 @@ where
config
.public_addresses
.into_iter()
.map(|address| AddressType::PublicAddress(address).without_p2p(local_peer_id))
.filter_map(|address| {
AddressType::PublicAddress(address).without_p2p(local_peer_id)
})
.collect()
};

Expand Down Expand Up @@ -374,17 +375,6 @@ where
let local_peer_id = self.network.local_peer_id();
let publish_non_global_ips = self.publish_non_global_ips;

// Checks that the address is global.
let address_is_global = |address: &Multiaddr| {
address.iter().all(|protocol| match protocol {
// The `ip_network` library is used because its `is_global()` method is stable,
// while `is_global()` in the standard library currently isn't.
multiaddr::Protocol::Ip4(ip) => IpNetwork::from(ip).is_global(),
multiaddr::Protocol::Ip6(ip) => IpNetwork::from(ip).is_global(),
_ => true,
})
};

// These are the addresses the node is listening for incoming connections,
// as reported by installed protocols (tcp / websocket etc).
//
Expand All @@ -397,8 +387,9 @@ where
.listen_addresses()
.into_iter()
.filter_map(|address| {
address_is_global(&address)
(address.is_external_address_valid() && address.is_global())
.then(|| AddressType::GlobalListenAddress(address).without_p2p(local_peer_id))
.flatten()
})
.take(MAX_GLOBAL_LISTEN_ADDRESSES)
.peekable();
Expand All @@ -409,8 +400,10 @@ where
.external_addresses()
.into_iter()
.filter_map(|address| {
(publish_non_global_ips || address_is_global(&address))
.then(|| AddressType::ExternalAddress(address).without_p2p(local_peer_id))
(publish_non_global_ips ||
(address.is_external_address_valid() && address.is_global()))
.then(|| AddressType::ExternalAddress(address).without_p2p(local_peer_id))
.flatten()
})
.peekable();

Expand Down Expand Up @@ -843,10 +836,20 @@ where
_ => None,
};

log::debug!(
target: LOG_TARGET,
"Received DHT record for authority {:?} with addresses {:?}",
authority_id,
addresses
);

// Ignore [`Multiaddr`]s without [`PeerId`] or with own addresses.
let addresses: Vec<Multiaddr> = addresses
.into_iter()
.filter(|a| get_peer_id(&a).filter(|p| *p != local_peer_id).is_some())
.filter(|addr| {
addr.is_external_address_valid() &&
get_peer_id(&addr).filter(|p| *p != local_peer_id).is_some()
})
.collect();

let remote_peer_id = single(addresses.iter().map(|a| get_peer_id(&a)))
Expand Down Expand Up @@ -998,7 +1001,9 @@ impl AddressType {
///
/// In case the peer id in the address does not match the local peer id, an error is logged for
/// `ExternalAddress` and `GlobalListenAddress`.
fn without_p2p(self, local_peer_id: PeerId) -> Multiaddr {
///
/// Returns `None` if the address is empty after removing the `/p2p/..` part.
fn without_p2p(self, local_peer_id: PeerId) -> Option<Multiaddr> {
// Get the address and the source str for logging.
let (mut address, source) = match self {
AddressType::PublicAddress(address) => (address, "public address"),
Expand All @@ -1016,7 +1021,13 @@ impl AddressType {
}
address.pop();
}
address

// Empty addresses cannot be published.
if address.is_empty() {
None
} else {
Some(address)
}
}
}

Expand Down
81 changes: 43 additions & 38 deletions substrate/client/network/src/litep2p/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,48 @@ impl Discovery {

(false, None)
}

/// Handle the observed address from the network.
fn handle_observed_addresses(&mut self, observed_address: Multiaddr, peer: PeerId) {
// Converting to and from `sc_network_types::multiaddr::Multiaddr` is cheap considering
// it is a small wrapper over litep2p `Multiaddr`.
let observed_address: sc_network_types::multiaddr::Multiaddr = observed_address.into();
if !observed_address.is_external_address_valid() {
log::debug!(
target: LOG_TARGET,
"Ignoring invalid external address {observed_address} from {peer:?}",
);
return;
}

let Some(observed_address) = observed_address.ensure_peer_id(self.local_peer_id.into())
else {
log::debug!(
target: LOG_TARGET,
"Ignoring external address with different peer ID from {peer:?}",
);
return
};
let observed_address = observed_address.into();

let (is_new, expired_address) = self.is_new_external_address(&observed_address, peer);

if let Some(expired_address) = expired_address {
log::trace!(
target: LOG_TARGET,
"Removing expired external address expired={expired_address} is_new={is_new} observed={observed_address}",
);

self.pending_events
.push_back(DiscoveryEvent::ExternalAddressExpired { address: expired_address });
}

if is_new {
self.pending_events.push_back(DiscoveryEvent::ExternalAddressDiscovered {
address: observed_address.clone(),
});
}
}
}

impl Stream for Discovery {
Expand Down Expand Up @@ -596,44 +638,7 @@ impl Stream for Discovery {
observed_address,
..
})) => {
let observed_address =
if let Some(Protocol::P2p(peer_id)) = observed_address.iter().last() {
if peer_id != *this.local_peer_id.as_ref() {
log::warn!(
target: LOG_TARGET,
"Discovered external address for a peer that is not us: {observed_address}",
);
None
} else {
Some(observed_address)
}
} else {
Some(observed_address.with(Protocol::P2p(this.local_peer_id.into())))
};

// Ensure that an external address with a different peer ID does not have
// side effects of evicting other external addresses via `ExternalAddressExpired`.
if let Some(observed_address) = observed_address {
let (is_new, expired_address) =
this.is_new_external_address(&observed_address, peer);

if let Some(expired_address) = expired_address {
log::trace!(
target: LOG_TARGET,
"Removing expired external address expired={expired_address} is_new={is_new} observed={observed_address}",
);

this.pending_events.push_back(DiscoveryEvent::ExternalAddressExpired {
address: expired_address,
});
}

if is_new {
this.pending_events.push_back(DiscoveryEvent::ExternalAddressDiscovered {
address: observed_address.clone(),
});
}
}
this.handle_observed_addresses(observed_address, peer);

return Poll::Ready(Some(DiscoveryEvent::Identified {
peer,
Expand Down
22 changes: 15 additions & 7 deletions substrate/client/network/src/litep2p/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,14 +746,22 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac
};
}
NetworkServiceCommand::AddKnownAddress { peer, address } => {
let mut address: Multiaddr = address.into();

if !address.iter().any(|protocol| std::matches!(protocol, Protocol::P2p(_))) {
address.push(Protocol::P2p(litep2p::PeerId::from(peer).into()));
if !address.is_external_address_valid() {
log::debug!(
target: LOG_TARGET,
"ignoring invalid external address {address} for {peer:?}",
);
continue
}

if self.litep2p.add_known_address(peer.into(), iter::once(address.clone())) == 0usize {
log::warn!(
let Some(address) = address.clone().ensure_peer_id(peer) else {
log::debug!(
target: LOG_TARGET,
"ignoring address ({address}) with different peer ID {peer:?}",
);
continue
};
if self.litep2p.add_known_address(peer.into(), iter::once(address.clone().into())) == 0usize {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this PR deployed, we receive the following warnings for adding addresses (only on Kusama validators):

sub-libp2p: couldn't add known address (/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWRjoe4poRBkBtuJ5JJGYanS7PWTfPc8sN4zvQ3gFEfSP6) for PeerId("12D3KooWRjoe4poRBkBtuJ5JJGYanS7PWTfPc8sN4zvQ3gFEfSP6"), unsupported transport

All warnings coming from this are related to addresses pointing to the localhost.

Will let this deployment run for a bit and then I believe its safe to turn these into debug

log::debug!(
target: LOG_TARGET,
"couldn't add known address ({address}) for {peer:?}, unsupported transport"
);
Expand Down
33 changes: 22 additions & 11 deletions substrate/client/network/src/peer_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ pub struct PeerInfoBehaviour {
ping: Ping,
/// Periodically identifies the remote and responds to incoming requests.
identify: Identify,
/// Identity of our local node.
local_peer_id: PeerId,

/// Information that we know about all nodes.
nodes_info: FnvHashMap<PeerId, NodeInfo>,
/// Interval at which we perform garbage collection in `nodes_info`.
Expand Down Expand Up @@ -123,6 +126,7 @@ impl PeerInfoBehaviour {
local_public_key: PublicKey,
external_addresses: Arc<Mutex<HashSet<Multiaddr>>>,
) -> Self {
let local_peer_id = local_public_key.to_peer_id();
let identify = {
let cfg = IdentifyConfig::new("/substrate/1.0".to_string(), local_public_key)
.with_agent_version(user_agent)
Expand All @@ -134,6 +138,7 @@ impl PeerInfoBehaviour {
Self {
ping: Ping::new(PingConfig::new()),
identify,
local_peer_id,
nodes_info: FnvHashMap::default(),
garbage_collect: Box::pin(interval(GARBAGE_COLLECT_INTERVAL)),
external_addresses: ExternalAddresses { addresses: external_addresses },
Expand Down Expand Up @@ -401,17 +406,23 @@ impl NetworkBehaviour for PeerInfoBehaviour {
self.external_addresses.remove(e.addr);
},
FromSwarm::NewExternalAddrCandidate(e @ NewExternalAddrCandidate { addr }) => {
self.ping.on_swarm_event(FromSwarm::NewExternalAddrCandidate(e));
self.identify.on_swarm_event(FromSwarm::NewExternalAddrCandidate(e));

// Manually confirm all external address candidates.
// TODO: consider adding [AutoNAT protocol](https://docs.rs/libp2p/0.52.3/libp2p/autonat/index.html)
// (must go through the polkadot protocol spec) or implemeting heuristics for
// approving external address candidates. This can be done, for example, by
// approving only addresses reported by multiple peers.
// See also https://github.com/libp2p/rust-libp2p/pull/4721 introduced
// in libp2p v0.53 for heuristics approach.
self.pending_actions.push_back(ToSwarm::ExternalAddrConfirmed(addr.clone()));
// Verify the external address is valid.
let multiaddr: sc_network_types::multiaddr::Multiaddr = addr.clone().into();
if multiaddr.is_external_address_valid() &&
multiaddr.ensure_peer_id(self.local_peer_id.into()).is_some()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just cross-checking if the following is intended:
ensure_peer_id modifies multiaddr, but the new value (which may contain p2p part) may get skipped.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, unfortunately libp2p API forces us to pass the event with a reference to the multiaddr (&'a multiaddr). I think this should be fine as long as we validate the address here. We also re-add /p2p/[peer] in the authority-discovery where this matters in publishing records 🤔

{
self.ping.on_swarm_event(FromSwarm::NewExternalAddrCandidate(e));
self.identify.on_swarm_event(FromSwarm::NewExternalAddrCandidate(e));

// Manually confirm all external address candidates.
// TODO: consider adding [AutoNAT protocol](https://docs.rs/libp2p/0.52.3/libp2p/autonat/index.html)
// (must go through the polkadot protocol spec) or implemeting heuristics for
// approving external address candidates. This can be done, for example, by
// approving only addresses reported by multiple peers.
// See also https://github.com/libp2p/rust-libp2p/pull/4721 introduced
// in libp2p v0.53 for heuristics approach.
self.pending_actions.push_back(ToSwarm::ExternalAddrConfirmed(addr.clone()));
}
},
FromSwarm::ExternalAddrConfirmed(e @ ExternalAddrConfirmed { addr }) => {
self.ping.on_swarm_event(FromSwarm::ExternalAddrConfirmed(e));
Expand Down
1 change: 1 addition & 0 deletions substrate/client/network/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ documentation = "https://docs.rs/sc-network-types"
bs58 = { workspace = true, default-features = true }
bytes = { version = "1.4.0", default-features = false }
ed25519-dalek = { workspace = true, default-features = true }
ip_network = { workspace = true }
libp2p-identity = { features = ["ed25519", "peerid", "rand"], workspace = true }
libp2p-kad = { version = "0.44.6", default-features = false }
litep2p = { workspace = true }
Expand Down
Loading
Loading