Skip to content

Commit dc3a737

Browse files
feat(swarm): replace address scoring with explicit candidates
Previously, a `NetworkBehaviour` could report an `AddressScore` for an external address. This score was a `u32` and addresses would be ranked amongst those. In reality, an address is either confirmed to be publicly reachable (via a protocol such as AutoNAT) or merely represents a candidate that might be an external address. In a way, addresses are guilty (private) until proven innocent (publicly reachable). When a `NetworkBehaviour` reports an address candidate, we perform address translation on it to potentially correct for ephemeral ports of TCP. These candidates are then injected back into the `NetworkBehaviour`. Protocols such as AutoNAT can use these addresses as a source for probing their NAT status. Once confirmed, they can emit a `ToSwarm::ExternalAddrConfirmed` event which again will be passed to all `NetworkBehaviour`s. This simplified approach will allow us implement Kademlia's client-mode (libp2p#2032) without additional configuration options: As soon as an address is reported as publicly reachable, we can activate server-mode for that connection. Related: libp2p#3877. Related: libp2p#3953. Related: libp2p#2032. Related: libp2p/go-libp2p#2229. Co-authored-by: Max Inden <[email protected]> Pull-Request: libp2p#3954.
1 parent 3cbaef0 commit dc3a737

File tree

4 files changed

+38
-85
lines changed

4 files changed

+38
-85
lines changed

src/behaviour.rs

+21-12
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ use libp2p_request_response::{
3636
};
3737
use libp2p_swarm::{
3838
behaviour::{
39-
AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, ExpiredExternalAddr,
40-
ExpiredListenAddr, FromSwarm,
39+
AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, ExpiredListenAddr,
40+
ExternalAddrExpired, FromSwarm,
4141
},
42-
ConnectionDenied, ConnectionId, ExternalAddresses, ListenAddresses, NetworkBehaviour,
42+
ConnectionDenied, ConnectionId, ListenAddresses, NetworkBehaviour, NewExternalAddrCandidate,
4343
PollParameters, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm,
4444
};
4545
use std::{
@@ -214,7 +214,7 @@ pub struct Behaviour {
214214
probe_id: ProbeId,
215215

216216
listen_addresses: ListenAddresses,
217-
external_addresses: ExternalAddresses,
217+
other_candidates: HashSet<Multiaddr>,
218218
}
219219

220220
impl Behaviour {
@@ -240,7 +240,7 @@ impl Behaviour {
240240
pending_actions: VecDeque::new(),
241241
probe_id: ProbeId(0),
242242
listen_addresses: Default::default(),
243-
external_addresses: Default::default(),
243+
other_candidates: Default::default(),
244244
}
245245
}
246246

@@ -279,6 +279,12 @@ impl Behaviour {
279279
self.servers.retain(|p| p != peer);
280280
}
281281

282+
/// Explicitly probe the provided address for external reachability.
283+
pub fn probe_address(&mut self, candidate: Multiaddr) {
284+
self.other_candidates.insert(candidate);
285+
self.as_client().on_new_address();
286+
}
287+
282288
fn as_client(&mut self) -> AsClient {
283289
AsClient {
284290
inner: &mut self.inner,
@@ -294,7 +300,7 @@ impl Behaviour {
294300
last_probe: &mut self.last_probe,
295301
schedule_probe: &mut self.schedule_probe,
296302
listen_addresses: &self.listen_addresses,
297-
external_addresses: &self.external_addresses,
303+
other_candidates: &self.other_candidates,
298304
}
299305
}
300306

@@ -532,7 +538,6 @@ impl NetworkBehaviour for Behaviour {
532538

533539
fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) {
534540
self.listen_addresses.on_swarm_event(&event);
535-
self.external_addresses.on_swarm_event(&event);
536541

537542
match event {
538543
FromSwarm::ConnectionEstablished(connection_established) => {
@@ -561,14 +566,17 @@ impl NetworkBehaviour for Behaviour {
561566
}));
562567
self.as_client().on_expired_address(addr);
563568
}
564-
FromSwarm::ExpiredExternalAddr(ExpiredExternalAddr { addr }) => {
569+
FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr }) => {
565570
self.inner
566-
.on_swarm_event(FromSwarm::ExpiredExternalAddr(ExpiredExternalAddr { addr }));
571+
.on_swarm_event(FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr }));
567572
self.as_client().on_expired_address(addr);
568573
}
569-
external_addr @ FromSwarm::NewExternalAddr(_) => {
570-
self.inner.on_swarm_event(external_addr);
571-
self.as_client().on_new_address();
574+
FromSwarm::NewExternalAddrCandidate(NewExternalAddrCandidate { addr }) => {
575+
self.inner
576+
.on_swarm_event(FromSwarm::NewExternalAddrCandidate(
577+
NewExternalAddrCandidate { addr },
578+
));
579+
self.probe_address(addr.to_owned());
572580
}
573581
listen_failure @ FromSwarm::ListenFailure(_) => {
574582
self.inner.on_swarm_event(listen_failure)
@@ -580,6 +588,7 @@ impl NetworkBehaviour for Behaviour {
580588
listener_closed @ FromSwarm::ListenerClosed(_) => {
581589
self.inner.on_swarm_event(listener_closed)
582590
}
591+
confirmed @ FromSwarm::ExternalAddrConfirmed(_) => self.inner.on_swarm_event(confirmed),
583592
}
584593
}
585594

src/behaviour/as_client.rs

+5-19
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ use instant::Instant;
3030
use libp2p_core::Multiaddr;
3131
use libp2p_identity::PeerId;
3232
use libp2p_request_response::{self as request_response, OutboundFailure, RequestId};
33-
use libp2p_swarm::{
34-
AddressScore, ConnectionId, ExternalAddresses, ListenAddresses, PollParameters, ToSwarm,
35-
};
33+
use libp2p_swarm::{ConnectionId, ListenAddresses, PollParameters, ToSwarm};
3634
use rand::{seq::SliceRandom, thread_rng};
3735
use std::{
3836
collections::{HashMap, HashSet, VecDeque},
@@ -97,13 +95,13 @@ pub(crate) struct AsClient<'a> {
9795
pub(crate) last_probe: &'a mut Option<Instant>,
9896
pub(crate) schedule_probe: &'a mut Delay,
9997
pub(crate) listen_addresses: &'a ListenAddresses,
100-
pub(crate) external_addresses: &'a ExternalAddresses,
98+
pub(crate) other_candidates: &'a HashSet<Multiaddr>,
10199
}
102100

103101
impl<'a> HandleInnerEvent for AsClient<'a> {
104102
fn handle_event(
105103
&mut self,
106-
params: &mut impl PollParameters,
104+
_: &mut impl PollParameters,
107105
event: request_response::Event<DialRequest, DialResponse>,
108106
) -> VecDeque<Action> {
109107
match event {
@@ -147,19 +145,7 @@ impl<'a> HandleInnerEvent for AsClient<'a> {
147145
}
148146

149147
if let Ok(address) = response.result {
150-
// Update observed address score if it is finite.
151-
#[allow(deprecated)]
152-
// TODO: Fix once we report `AddressScore` through `FromSwarm` event.
153-
let score = params
154-
.external_addresses()
155-
.find_map(|r| (r.addr == address).then_some(r.score))
156-
.unwrap_or(AddressScore::Finite(0));
157-
if let AddressScore::Finite(finite_score) = score {
158-
actions.push_back(ToSwarm::ReportObservedAddr {
159-
address,
160-
score: AddressScore::Finite(finite_score + 1),
161-
});
162-
}
148+
actions.push_back(ToSwarm::ExternalAddrConfirmed(address));
163149
}
164150

165151
actions
@@ -201,7 +187,7 @@ impl<'a> AsClient<'a> {
201187
self.schedule_probe.reset(self.config.retry_interval);
202188

203189
let addresses = self
204-
.external_addresses
190+
.other_candidates
205191
.iter()
206192
.chain(self.listen_addresses.iter())
207193
.cloned()

tests/test_client.rs

+5-45
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use libp2p_autonat::{
2424
};
2525
use libp2p_core::Multiaddr;
2626
use libp2p_identity::PeerId;
27-
use libp2p_swarm::{AddressScore, Swarm, SwarmEvent};
27+
use libp2p_swarm::{Swarm, SwarmEvent};
2828
use libp2p_swarm_test::SwarmExt as _;
2929
use std::time::Duration;
3030

@@ -70,48 +70,6 @@ async fn test_auto_probe() {
7070
assert!(client.behaviour().public_address().is_none());
7171
assert_eq!(client.behaviour().confidence(), 0);
7272

73-
// Test Private NAT Status
74-
75-
// Artificially add a faulty address.
76-
let unreachable_addr: Multiaddr = "/ip4/127.0.0.1/tcp/42".parse().unwrap();
77-
client.add_external_address(unreachable_addr.clone(), AddressScore::Infinite);
78-
79-
let id = match client.next_behaviour_event().await {
80-
Event::OutboundProbe(OutboundProbeEvent::Request { probe_id, peer }) => {
81-
assert_eq!(peer, server_id);
82-
probe_id
83-
}
84-
other => panic!("Unexpected behaviour event: {other:?}."),
85-
};
86-
87-
match client.next_behaviour_event().await {
88-
Event::OutboundProbe(OutboundProbeEvent::Error {
89-
probe_id,
90-
peer,
91-
error,
92-
}) => {
93-
assert_eq!(peer.unwrap(), server_id);
94-
assert_eq!(probe_id, id);
95-
assert_eq!(
96-
error,
97-
OutboundProbeError::Response(ResponseError::DialError)
98-
);
99-
}
100-
other => panic!("Unexpected behaviour event: {other:?}."),
101-
}
102-
103-
match client.next_behaviour_event().await {
104-
Event::StatusChanged { old, new } => {
105-
assert_eq!(old, NatStatus::Unknown);
106-
assert_eq!(new, NatStatus::Private);
107-
}
108-
other => panic!("Unexpected behaviour event: {other:?}."),
109-
}
110-
111-
assert_eq!(client.behaviour().confidence(), 0);
112-
assert_eq!(client.behaviour().nat_status(), NatStatus::Private);
113-
assert!(client.behaviour().public_address().is_none());
114-
11573
// Test new public listening address
11674
client.listen().await;
11775

@@ -142,12 +100,14 @@ async fn test_auto_probe() {
142100
}
143101
SwarmEvent::Behaviour(Event::StatusChanged { old, new }) => {
144102
// Expect to flip status to public
145-
assert_eq!(old, NatStatus::Private);
103+
assert_eq!(old, NatStatus::Unknown);
146104
assert!(matches!(new, NatStatus::Public(_)));
147105
assert!(new.is_public());
148106
break;
149107
}
150108
SwarmEvent::IncomingConnection { .. }
109+
| SwarmEvent::ConnectionEstablished { .. }
110+
| SwarmEvent::Dialing { .. }
151111
| SwarmEvent::NewListenAddr { .. }
152112
| SwarmEvent::ExpiredListenAddr { .. } => {}
153113
other => panic!("Unexpected swarm event: {other:?}."),
@@ -198,7 +158,7 @@ async fn test_confidence() {
198158
client.listen().await;
199159
} else {
200160
let unreachable_addr = "/ip4/127.0.0.1/tcp/42".parse().unwrap();
201-
client.add_external_address(unreachable_addr, AddressScore::Infinite);
161+
client.behaviour_mut().probe_address(unreachable_addr);
202162
}
203163

204164
for i in 0..MAX_CONFIDENCE + 1 {

tests/test_server.rs

+7-9
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use libp2p_autonat::{
2424
use libp2p_core::{multiaddr::Protocol, ConnectedPoint, Endpoint, Multiaddr};
2525
use libp2p_identity::PeerId;
2626
use libp2p_swarm::DialError;
27-
use libp2p_swarm::{AddressScore, Swarm, SwarmEvent};
27+
use libp2p_swarm::{Swarm, SwarmEvent};
2828
use libp2p_swarm_test::SwarmExt as _;
2929
use std::{num::NonZeroU32, time::Duration};
3030

@@ -131,10 +131,9 @@ async fn test_dial_back() {
131131
async fn test_dial_error() {
132132
let (mut server, server_id, server_addr) = new_server_swarm(None).await;
133133
let (mut client, client_id) = new_client_swarm(server_id, server_addr).await;
134-
client.add_external_address(
135-
"/ip4/127.0.0.1/tcp/12345".parse().unwrap(),
136-
AddressScore::Infinite,
137-
);
134+
client
135+
.behaviour_mut()
136+
.probe_address("/ip4/127.0.0.1/tcp/12345".parse().unwrap());
138137
async_std::task::spawn(client.loop_on_next());
139138

140139
let request_probe_id = match server.next_behaviour_event().await {
@@ -274,10 +273,9 @@ async fn test_dial_multiple_addr() {
274273

275274
let (mut client, client_id) = new_client_swarm(server_id, server_addr.clone()).await;
276275
client.listen().await;
277-
client.add_external_address(
278-
"/ip4/127.0.0.1/tcp/12345".parse().unwrap(),
279-
AddressScore::Infinite,
280-
);
276+
client
277+
.behaviour_mut()
278+
.probe_address("/ip4/127.0.0.1/tcp/12345".parse().unwrap());
281279
async_std::task::spawn(client.loop_on_next());
282280

283281
let dial_addresses = match server.next_behaviour_event().await {

0 commit comments

Comments
 (0)