Skip to content

Commit 1e001a2

Browse files
protocols/ping: Don't force close conn if not supported by remote (#2149)
Don't close connection if ping protocol is unsupported by remote. Previously, a failed protocol negotation for ping caused a force close of the connection. As a result, all nodes in a network had to support ping. To allow networks where some nodes don't support ping, we now emit `PingFailure::Unsupported` once for every connection on which ping is not supported. Co-authored-by: Max Inden <[email protected]>
1 parent ad90167 commit 1e001a2

File tree

6 files changed

+158
-166
lines changed

6 files changed

+158
-166
lines changed

examples/ipfs-private.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,12 @@ fn main() -> Result<(), Box<dyn Error>> {
223223
} => {
224224
println!("ping: timeout to {}", peer.to_base58());
225225
}
226+
PingEvent {
227+
peer,
228+
result: Result::Err(PingFailure::Unsupported),
229+
} => {
230+
println!("ping: {} does not support ping protocol", peer.to_base58());
231+
}
226232
PingEvent {
227233
peer,
228234
result: Result::Err(PingFailure::Other { error }),

protocols/ping/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
- Update dependencies.
44

5+
- Don't close connection if ping protocol is unsupported by remote.
6+
Previously, a failed protocol negotation for ping caused a force close of the connection.
7+
As a result, all nodes in a network had to support ping.
8+
To allow networks where some nodes don't support ping, we now emit
9+
`PingFailure::Unsupported` once for every connection on which ping is not supported.
10+
11+
In case you want to stick with the old behavior, you need to close the connection
12+
manually on `PingFailure::Unsupported`.
13+
14+
Fixes [#2109](https://github.com/libp2p/rust-libp2p/issues/2109). Also see [PR 2149].
15+
16+
[PR 2149]: https://github.com/libp2p/rust-libp2p/pull/2149/
17+
518
# 0.30.0 [2021-07-12]
619

720
- Update dependencies.

protocols/ping/src/handler.rs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use crate::protocol;
2222
use futures::prelude::*;
2323
use futures::future::BoxFuture;
24+
use libp2p_core::{UpgradeError, upgrade::NegotiationError};
2425
use libp2p_swarm::{
2526
KeepAlive,
2627
NegotiatedSubstream,
@@ -140,6 +141,8 @@ pub enum PingFailure {
140141
/// The ping timed out, i.e. no response was received within the
141142
/// configured ping timeout.
142143
Timeout,
144+
/// The peer does not support the ping protocol.
145+
Unsupported,
143146
/// The ping failed for reasons other than a timeout.
144147
Other { error: Box<dyn std::error::Error + Send + 'static> }
145148
}
@@ -148,7 +151,8 @@ impl fmt::Display for PingFailure {
148151
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149152
match self {
150153
PingFailure::Timeout => f.write_str("Ping timeout"),
151-
PingFailure::Other { error } => write!(f, "Ping error: {}", error)
154+
PingFailure::Other { error } => write!(f, "Ping error: {}", error),
155+
PingFailure::Unsupported => write!(f, "Ping protocol not supported"),
152156
}
153157
}
154158
}
@@ -157,7 +161,8 @@ impl Error for PingFailure {
157161
fn source(&self) -> Option<&(dyn Error + 'static)> {
158162
match self {
159163
PingFailure::Timeout => None,
160-
PingFailure::Other { error } => Some(&**error)
164+
PingFailure::Other { error } => Some(&**error),
165+
PingFailure::Unsupported => None,
161166
}
162167
}
163168
}
@@ -184,6 +189,21 @@ pub struct PingHandler {
184189
/// substream, this is always a future that waits for the
185190
/// next inbound ping to be answered.
186191
inbound: Option<PongFuture>,
192+
/// Tracks the state of our handler.
193+
state: State
194+
}
195+
196+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197+
enum State {
198+
/// We are inactive because the other peer doesn't support ping.
199+
Inactive {
200+
/// Whether or not we've reported the missing support yet.
201+
///
202+
/// This is used to avoid repeated events being emitted for a specific connection.
203+
reported: bool
204+
},
205+
/// We are actively pinging the other peer.
206+
Active,
187207
}
188208

189209
impl PingHandler {
@@ -196,6 +216,7 @@ impl PingHandler {
196216
failures: 0,
197217
outbound: None,
198218
inbound: None,
219+
state: State::Active,
199220
}
200221
}
201222
}
@@ -226,12 +247,22 @@ impl ProtocolsHandler for PingHandler {
226247

227248
fn inject_dial_upgrade_error(&mut self, _info: (), error: ProtocolsHandlerUpgrErr<Void>) {
228249
self.outbound = None; // Request a new substream on the next `poll`.
229-
self.pending_errors.push_front(
230-
match error {
231-
// Note: This timeout only covers protocol negotiation.
232-
ProtocolsHandlerUpgrErr::Timeout => PingFailure::Timeout,
233-
e => PingFailure::Other { error: Box::new(e) },
234-
})
250+
251+
let error = match error {
252+
ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Select(NegotiationError::Failed)) => {
253+
debug_assert_eq!(self.state, State::Active);
254+
255+
self.state = State::Inactive {
256+
reported: false
257+
};
258+
return;
259+
},
260+
// Note: This timeout only covers protocol negotiation.
261+
ProtocolsHandlerUpgrErr::Timeout => PingFailure::Timeout,
262+
e => PingFailure::Other { error: Box::new(e) },
263+
};
264+
265+
self.pending_errors.push_front(error);
235266
}
236267

237268
fn connection_keep_alive(&self) -> KeepAlive {
@@ -243,6 +274,17 @@ impl ProtocolsHandler for PingHandler {
243274
}
244275

245276
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<ProtocolsHandlerEvent<protocol::Ping, (), PingResult, Self::Error>> {
277+
match self.state {
278+
State::Inactive { reported: true } => {
279+
return Poll::Pending // nothing to do on this connection
280+
},
281+
State::Inactive { reported: false } => {
282+
self.state = State::Inactive { reported: true };
283+
return Poll::Ready(ProtocolsHandlerEvent::Custom(Err(PingFailure::Unsupported)));
284+
},
285+
State::Active => {}
286+
}
287+
246288
// Respond to inbound pings.
247289
if let Some(fut) = self.inbound.as_mut() {
248290
match fut.poll_unpin(cx) {
@@ -355,4 +397,3 @@ enum PingState {
355397
/// A ping is being sent and the response awaited.
356398
Ping(PingFuture),
357399
}
358-

protocols/ping/tests/ping.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use libp2p_core::{
3131
use libp2p_mplex as mplex;
3232
use libp2p_noise as noise;
3333
use libp2p_ping::*;
34-
use libp2p_swarm::{Swarm, SwarmEvent};
34+
use libp2p_swarm::{DummyBehaviour, KeepAlive, Swarm, SwarmEvent};
3535
use libp2p_tcp::TcpConfig;
3636
use libp2p_yamux as yamux;
3737
use futures::{prelude::*, channel::mpsc};
@@ -83,18 +83,18 @@ fn ping_pong() {
8383

8484
loop {
8585
match swarm2.select_next_some().await {
86-
SwarmEvent::Behaviour(PingEvent {
87-
peer,
88-
result: Ok(PingSuccess::Ping { rtt })
86+
SwarmEvent::Behaviour(PingEvent {
87+
peer,
88+
result: Ok(PingSuccess::Ping { rtt })
8989
}) => {
9090
count2 -= 1;
9191
if count2 == 0 {
9292
return (pid2.clone(), peer, rtt)
9393
}
9494
},
95-
SwarmEvent::Behaviour(PingEvent {
96-
result: Err(e),
97-
..
95+
SwarmEvent::Behaviour(PingEvent {
96+
result: Err(e),
97+
..
9898
}) => panic!("Ping failure: {:?}", e),
9999
_ => {}
100100
}
@@ -189,6 +189,52 @@ fn max_failures() {
189189
QuickCheck::new().tests(10).quickcheck(prop as fn(_,_))
190190
}
191191

192+
#[test]
193+
fn unsupported_doesnt_fail() {
194+
let (peer1_id, trans) = mk_transport(MuxerChoice::Mplex);
195+
let mut swarm1 = Swarm::new(trans, DummyBehaviour::with_keep_alive(KeepAlive::Yes), peer1_id.clone());
196+
197+
let (peer2_id, trans) = mk_transport(MuxerChoice::Mplex);
198+
let mut swarm2 = Swarm::new(trans, Ping::new(PingConfig::new().with_keep_alive(true)), peer2_id.clone());
199+
200+
let (mut tx, mut rx) = mpsc::channel::<Multiaddr>(1);
201+
202+
let addr = "/ip4/127.0.0.1/tcp/0".parse().unwrap();
203+
swarm1.listen_on(addr).unwrap();
204+
205+
async_std::task::spawn(async move {
206+
loop {
207+
match swarm1.select_next_some().await {
208+
SwarmEvent::NewListenAddr { address, .. } => tx.send(address).await.unwrap(),
209+
_ => {}
210+
}
211+
}
212+
});
213+
214+
let result = async_std::task::block_on(async move {
215+
swarm2.dial_addr(rx.next().await.unwrap()).unwrap();
216+
217+
loop {
218+
match swarm2.select_next_some().await {
219+
SwarmEvent::Behaviour(PingEvent {
220+
result: Err(PingFailure::Unsupported), ..
221+
}) => {
222+
swarm2.disconnect_peer_id(peer1_id).unwrap();
223+
}
224+
SwarmEvent::ConnectionClosed { cause: Some(e), .. } => {
225+
break Err(e);
226+
}
227+
SwarmEvent::ConnectionClosed { cause: None, .. } => {
228+
break Ok(());
229+
}
230+
_ => {}
231+
}
232+
}
233+
});
234+
235+
result.expect("node with ping should not fail connection due to unsupported protocol");
236+
}
237+
192238

193239
fn mk_transport(muxer: MuxerChoice) -> (
194240
PeerId,

0 commit comments

Comments
 (0)