mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-05-10 18:07:19 +00:00
fix(swarm): prevent overflow in keep-alive computation
When adding a very large `Duration` to an `Instant`, an overflow can occur. To fix this, we check this before instantiating `Delay` and half the given duration until it no longer overflows. Fixes: #4555. Pull-Request: #4559.
This commit is contained in:
parent
38ea7ad453
commit
9cd0f3dbed
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3039,7 +3039,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-swarm"
|
||||
version = "0.43.4"
|
||||
version = "0.43.5"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"either",
|
||||
|
@ -100,7 +100,7 @@ libp2p-rendezvous = { version = "0.13.0", path = "protocols/rendezvous" }
|
||||
libp2p-upnp = { version = "0.1.1", path = "protocols/upnp" }
|
||||
libp2p-request-response = { version = "0.25.1", path = "protocols/request-response" }
|
||||
libp2p-server = { version = "0.12.3", path = "misc/server" }
|
||||
libp2p-swarm = { version = "0.43.4", path = "swarm" }
|
||||
libp2p-swarm = { version = "0.43.5", path = "swarm" }
|
||||
libp2p-swarm-derive = { version = "0.33.0", path = "swarm-derive" }
|
||||
libp2p-swarm-test = { version = "0.2.0", path = "swarm-test" }
|
||||
libp2p-tcp = { version = "0.40.0", path = "transports/tcp" }
|
||||
|
@ -1,3 +1,8 @@
|
||||
## 0.43.5
|
||||
|
||||
- Fix overflow in `KeepAlive` computation that could occur if `SwarmBuilder::idle_connection_timeout` is configured with `u64::MAX`.
|
||||
See [PR 4559](https://github.com/libp2p/rust-libp2p/pull/4559).
|
||||
|
||||
## 0.43.4
|
||||
|
||||
- Implement `Debug` for event structs.
|
||||
|
@ -3,7 +3,7 @@ name = "libp2p-swarm"
|
||||
edition = "2021"
|
||||
rust-version = { workspace = true }
|
||||
description = "The libp2p swarm"
|
||||
version = "0.43.4"
|
||||
version = "0.43.5"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
|
@ -361,15 +361,16 @@ where
|
||||
}
|
||||
}
|
||||
(_, KeepAlive::Until(earliest_shutdown)) => {
|
||||
if let Some(requested_keep_alive) =
|
||||
earliest_shutdown.checked_duration_since(Instant::now())
|
||||
{
|
||||
let effective_keep_alive = max(requested_keep_alive, *idle_timeout);
|
||||
let now = Instant::now();
|
||||
|
||||
if let Some(requested) = earliest_shutdown.checked_duration_since(now) {
|
||||
let effective_keep_alive = max(requested, *idle_timeout);
|
||||
|
||||
let safe_keep_alive = checked_add_fraction(now, effective_keep_alive);
|
||||
|
||||
// Important: We store the _original_ `Instant` given by the `ConnectionHandler` in the `Later` instance to ensure we can compare it in the above branch.
|
||||
// This is quite subtle but will hopefully become simpler soon once `KeepAlive::Until` is fully deprecated. See <https://github.com/libp2p/rust-libp2p/issues/3844>/
|
||||
*shutdown =
|
||||
Shutdown::Later(Delay::new(effective_keep_alive), earliest_shutdown)
|
||||
*shutdown = Shutdown::Later(Delay::new(safe_keep_alive), earliest_shutdown)
|
||||
}
|
||||
}
|
||||
(_, KeepAlive::No) if idle_timeout == &Duration::ZERO => {
|
||||
@ -379,8 +380,10 @@ where
|
||||
// Do nothing, i.e. let the shutdown timer continue to tick.
|
||||
}
|
||||
(_, KeepAlive::No) => {
|
||||
let deadline = Instant::now() + *idle_timeout;
|
||||
*shutdown = Shutdown::Later(Delay::new(*idle_timeout), deadline);
|
||||
let now = Instant::now();
|
||||
let safe_keep_alive = checked_add_fraction(now, *idle_timeout);
|
||||
|
||||
*shutdown = Shutdown::Later(Delay::new(safe_keep_alive), now + safe_keep_alive);
|
||||
}
|
||||
(_, KeepAlive::Yes) => *shutdown = Shutdown::None,
|
||||
};
|
||||
@ -479,6 +482,20 @@ fn gather_supported_protocols(handler: &impl ConnectionHandler) -> HashSet<Strea
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Repeatedly halves and adds the [`Duration`] to the [`Instant`] until [`Instant::checked_add`] succeeds.
|
||||
///
|
||||
/// [`Instant`] depends on the underlying platform and has a limit of which points in time it can represent.
|
||||
/// The [`Duration`] computed by the this function may not be the longest possible that we can add to `now` but it will work.
|
||||
fn checked_add_fraction(start: Instant, mut duration: Duration) -> Duration {
|
||||
while start.checked_add(duration).is_none() {
|
||||
log::debug!("{start:?} + {duration:?} cannot be presented, halving duration");
|
||||
|
||||
duration /= 2;
|
||||
}
|
||||
|
||||
duration
|
||||
}
|
||||
|
||||
/// Borrowed information about an incoming connection currently being negotiated.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct IncomingInfo<'a> {
|
||||
@ -957,6 +974,16 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_add_fraction_can_add_u64_max() {
|
||||
let _ = env_logger::try_init();
|
||||
let start = Instant::now();
|
||||
|
||||
let duration = checked_add_fraction(start, Duration::from_secs(u64::MAX));
|
||||
|
||||
assert!(start.checked_add(duration).is_some())
|
||||
}
|
||||
|
||||
struct KeepAliveUntilConnectionHandler {
|
||||
until: Instant,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user