Skip to content

Commit 92c8cc4

Browse files
feat(kad): implement automatic client mode
Currently, the kademlia behaviour can only learn that the remote node supports kademlia on a particular connection if we successfully negotiate a stream to them. Using the newly introduced abstractions from #3651, we don't have to attempt to establish a stream to the remote to learn whether they support kademlia on a connection but we can directly learn it from the `ConnectionEvent::RemoteProtocolsChange` event. This happens directly once a connection is established which should overall benefit the DHT. Clients do not advertise the kademlia protocol and thus we will immediately learn that a given connection is not suitable for kadmelia requests. We may receive inbound messages from it but this does not affect the routing table. Resolves: #2032. Pull-Request: #3877.
1 parent 7f86544 commit 92c8cc4

File tree

8 files changed

+490
-103
lines changed

8 files changed

+490
-103
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protocols/kad/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
- Remove deprecated public modules `handler`, `protocol` and `kbucket`.
77
See [PR 3896].
88

9+
- Automatically configure client/server mode based on external addresses.
10+
If we have or learn about an external address of our node, we operate in server-mode and thus allow inbound requests.
11+
By default, a node is in client-mode and only allows outbound requests.
12+
See [PR 3877].
13+
914
[PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715
15+
[PR 3877]: https://github.com/libp2p/rust-libp2p/pull/3877
1016
[PR 3896]: https://github.com/libp2p/rust-libp2p/pull/3896
1117

1218
## 0.43.3

protocols/kad/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
3434
thiserror = "1"
3535

3636
[dev-dependencies]
37+
async-std = { version = "1.12.0", features = ["attributes"] }
3738
env_logger = "0.10.0"
3839
futures-timer = "3.0"
40+
libp2p-identify = { path = "../identify" }
3941
libp2p-noise = { workspace = true }
42+
libp2p-swarm = { path = "../../swarm", features = ["macros"] }
43+
libp2p-swarm-test = { path = "../../swarm-test" }
4044
libp2p-yamux = { workspace = true }
4145
quickcheck = { workspace = true }
4246

protocols/kad/src/behaviour.rs

Lines changed: 125 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
mod test;
2424

2525
use crate::addresses::Addresses;
26-
use crate::handler::{
27-
KademliaHandler, KademliaHandlerConfig, KademliaHandlerEvent, KademliaHandlerIn,
28-
KademliaRequestId,
29-
};
26+
use crate::handler::{KademliaHandler, KademliaHandlerEvent, KademliaHandlerIn, KademliaRequestId};
3027
use crate::jobs::*;
3128
use crate::kbucket::{self, Distance, KBucketsTable, NodeStatus};
3229
use crate::protocol::{KadConnectionType, KadPeer, KademliaProtocolConfig};
@@ -52,7 +49,7 @@ use libp2p_swarm::{
5249
};
5350
use log::{debug, info, warn};
5451
use smallvec::SmallVec;
55-
use std::collections::{BTreeMap, HashSet, VecDeque};
52+
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
5653
use std::fmt;
5754
use std::num::NonZeroUsize;
5855
use std::task::{Context, Poll};
@@ -109,11 +106,15 @@ pub struct Kademlia<TStore> {
109106

110107
external_addresses: ExternalAddresses,
111108

109+
connections: HashMap<ConnectionId, PeerId>,
110+
112111
/// See [`KademliaConfig::caching`].
113112
caching: KademliaCaching,
114113

115114
local_peer_id: PeerId,
116115

116+
mode: Mode,
117+
117118
/// The record storage.
118119
store: TStore,
119120
}
@@ -453,6 +454,8 @@ where
453454
connection_idle_timeout: config.connection_idle_timeout,
454455
external_addresses: Default::default(),
455456
local_peer_id: id,
457+
connections: Default::default(),
458+
mode: Mode::Client,
456459
}
457460
}
458461

@@ -1937,9 +1940,12 @@ where
19371940
ConnectionClosed {
19381941
peer_id,
19391942
remaining_established,
1943+
connection_id,
19401944
..
19411945
}: ConnectionClosed<<Self as NetworkBehaviour>::ConnectionHandler>,
19421946
) {
1947+
self.connections.remove(&connection_id);
1948+
19431949
if remaining_established == 0 {
19441950
for query in self.queries.iter_mut() {
19451951
query.on_failure(&peer_id);
@@ -1964,43 +1970,45 @@ where
19641970

19651971
fn handle_established_inbound_connection(
19661972
&mut self,
1967-
_connection_id: ConnectionId,
1973+
connection_id: ConnectionId,
19681974
peer: PeerId,
19691975
local_addr: &Multiaddr,
19701976
remote_addr: &Multiaddr,
19711977
) -> Result<THandler<Self>, ConnectionDenied> {
1978+
let connected_point = ConnectedPoint::Listener {
1979+
local_addr: local_addr.clone(),
1980+
send_back_addr: remote_addr.clone(),
1981+
};
1982+
self.connections.insert(connection_id, peer);
1983+
19721984
Ok(KademliaHandler::new(
1973-
KademliaHandlerConfig {
1974-
protocol_config: self.protocol_config.clone(),
1975-
allow_listening: true,
1976-
idle_timeout: self.connection_idle_timeout,
1977-
},
1978-
ConnectedPoint::Listener {
1979-
local_addr: local_addr.clone(),
1980-
send_back_addr: remote_addr.clone(),
1981-
},
1985+
self.protocol_config.clone(),
1986+
self.connection_idle_timeout,
1987+
connected_point,
19821988
peer,
1989+
self.mode,
19831990
))
19841991
}
19851992

19861993
fn handle_established_outbound_connection(
19871994
&mut self,
1988-
_connection_id: ConnectionId,
1995+
connection_id: ConnectionId,
19891996
peer: PeerId,
19901997
addr: &Multiaddr,
19911998
role_override: Endpoint,
19921999
) -> Result<THandler<Self>, ConnectionDenied> {
2000+
let connected_point = ConnectedPoint::Dialer {
2001+
address: addr.clone(),
2002+
role_override,
2003+
};
2004+
self.connections.insert(connection_id, peer);
2005+
19932006
Ok(KademliaHandler::new(
1994-
KademliaHandlerConfig {
1995-
protocol_config: self.protocol_config.clone(),
1996-
allow_listening: true,
1997-
idle_timeout: self.connection_idle_timeout,
1998-
},
1999-
ConnectedPoint::Dialer {
2000-
address: addr.clone(),
2001-
role_override,
2002-
},
2007+
self.protocol_config.clone(),
2008+
self.connection_idle_timeout,
2009+
connected_point,
20032010
peer,
2011+
self.mode,
20042012
))
20052013
}
20062014

@@ -2055,9 +2063,18 @@ where
20552063
ConnectedPoint::Dialer { address, .. } => Some(address),
20562064
ConnectedPoint::Listener { .. } => None,
20572065
};
2066+
20582067
self.connection_updated(source, address, NodeStatus::Connected);
20592068
}
20602069

2070+
KademliaHandlerEvent::ProtocolNotSupported { endpoint } => {
2071+
let address = match endpoint {
2072+
ConnectedPoint::Dialer { address, .. } => Some(address),
2073+
ConnectedPoint::Listener { .. } => None,
2074+
};
2075+
self.connection_updated(source, address, NodeStatus::Disconnected);
2076+
}
2077+
20612078
KademliaHandlerEvent::FindNodeReq { key, request_id } => {
20622079
let closer_peers = self.find_closest(&kbucket::Key::new(key), &source);
20632080

@@ -2419,7 +2436,63 @@ where
24192436

24202437
fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) {
24212438
self.listen_addresses.on_swarm_event(&event);
2422-
self.external_addresses.on_swarm_event(&event);
2439+
let external_addresses_changed = self.external_addresses.on_swarm_event(&event);
2440+
2441+
self.mode = match (self.external_addresses.as_slice(), self.mode) {
2442+
([], Mode::Server) => {
2443+
log::debug!("Switching to client-mode because we no longer have any confirmed external addresses");
2444+
2445+
Mode::Client
2446+
}
2447+
([], Mode::Client) => {
2448+
// Previously client-mode, now also client-mode because no external addresses.
2449+
2450+
Mode::Client
2451+
}
2452+
(confirmed_external_addresses, Mode::Client) => {
2453+
if log::log_enabled!(log::Level::Debug) {
2454+
let confirmed_external_addresses =
2455+
to_comma_separated_list(confirmed_external_addresses);
2456+
2457+
log::debug!("Switching to server-mode assuming that one of [{confirmed_external_addresses}] is externally reachable");
2458+
}
2459+
2460+
Mode::Server
2461+
}
2462+
(confirmed_external_addresses, Mode::Server) => {
2463+
debug_assert!(
2464+
!confirmed_external_addresses.is_empty(),
2465+
"Previous match arm handled empty list"
2466+
);
2467+
2468+
// Previously, server-mode, now also server-mode because > 1 external address. Don't log anything to avoid spam.
2469+
2470+
Mode::Server
2471+
}
2472+
};
2473+
2474+
if external_addresses_changed && !self.connections.is_empty() {
2475+
let num_connections = self.connections.len();
2476+
2477+
log::debug!(
2478+
"External addresses changed, re-configuring {} established connection{}",
2479+
num_connections,
2480+
if num_connections > 1 { "s" } else { "" }
2481+
);
2482+
2483+
self.queued_events
2484+
.extend(
2485+
self.connections
2486+
.iter()
2487+
.map(|(conn_id, peer_id)| ToSwarm::NotifyHandler {
2488+
peer_id: *peer_id,
2489+
handler: NotifyHandler::One(*conn_id),
2490+
event: KademliaHandlerIn::ReconfigureMode {
2491+
new_mode: self.mode,
2492+
},
2493+
}),
2494+
);
2495+
}
24232496

24242497
match event {
24252498
FromSwarm::ConnectionEstablished(connection_established) => {
@@ -3187,3 +3260,29 @@ pub enum RoutingUpdate {
31873260
/// peer ID).
31883261
Failed,
31893262
}
3263+
3264+
#[derive(PartialEq, Copy, Clone, Debug)]
3265+
pub enum Mode {
3266+
Client,
3267+
Server,
3268+
}
3269+
3270+
impl fmt::Display for Mode {
3271+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3272+
match self {
3273+
Mode::Client => write!(f, "client"),
3274+
Mode::Server => write!(f, "server"),
3275+
}
3276+
}
3277+
}
3278+
3279+
fn to_comma_separated_list<T>(confirmed_external_addresses: &[T]) -> String
3280+
where
3281+
T: ToString,
3282+
{
3283+
confirmed_external_addresses
3284+
.iter()
3285+
.map(|addr| addr.to_string())
3286+
.collect::<Vec<_>>()
3287+
.join(", ")
3288+
}

protocols/kad/src/behaviour/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ fn build_node_with_config(cfg: KademliaConfig) -> (Multiaddr, TestSwarm) {
7171

7272
let address: Multiaddr = Protocol::Memory(random::<u64>()).into();
7373
swarm.listen_on(address.clone()).unwrap();
74+
swarm.add_external_address(address.clone());
7475

7576
(address, swarm)
7677
}

0 commit comments

Comments
 (0)