Skip to content

Commit

Permalink
Be sure to count for system sleep in timers (cloudflare#317)
Browse files Browse the repository at this point in the history
* Be sure to count for system sleep in timers

Currently the timers in boringtun use std::time::Instant. This timer
does not increment while the machine is asleep on macOS and Linux
meaning the device does not know to properly handshake on wake from
sleep.

To solve this we use `CLOCK_BOOTTIME` on Linux/Android and
`CLOCK_MONOTONIC` on macOS/iOS to get the actual duration since the last
handshake.

Fixes cloudflare#316

* Move sleepyinstant to module instead of crate

* Moved Windows to new files

Stopped using `path`
  • Loading branch information
Matt Schulte committed Sep 13, 2022
1 parent 370a9ed commit 6d4fb2e
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 13 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions boringtun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ hmac = "0.12"
jni = { version = "0.19.0", optional = true }
mock_instant = { version = "0.2", optional = true }

[target.'cfg(target_os="macos")'.dependencies]
nix = "0.24.1"
[target.'cfg(unix)'.dependencies]
nix = { version = "0.25", default-features = false, features = ["time", "user"] }

[dev-dependencies]
etherparse = "0.12"
Expand Down
6 changes: 4 additions & 2 deletions boringtun/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ pub mod device;

#[cfg(feature = "ffi-bindings")]
pub mod ffi;
pub mod noise;

#[cfg(feature = "jni-bindings")]
pub mod jni;
pub mod noise;

#[cfg(not(feature = "mock-instant"))]
pub(crate) mod sleepyinstant;

pub(crate) mod serialization;
4 changes: 2 additions & 2 deletions boringtun/src/noise/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
use super::{HandshakeInit, HandshakeResponse, PacketCookieReply};
use crate::noise::errors::WireGuardError;
use crate::noise::session::Session;
#[cfg(not(feature = "mock-instant"))]
use crate::sleepyinstant::Instant;
use aead::{Aead, Payload};
use blake2::digest::{FixedOutput, KeyInit};
use blake2::{Blake2s256, Blake2sMac, Digest};
use chacha20poly1305::XChaCha20Poly1305;
use rand_core::OsRng;
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305};
use std::convert::TryInto;
#[cfg(not(feature = "mock-instant"))]
use std::time::Instant;
use std::time::{Duration, SystemTime};

#[cfg(feature = "mock-instant")]
Expand Down
2 changes: 1 addition & 1 deletion boringtun/src/noise/rate_limiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::net::IpAddr;
use std::sync::atomic::{AtomicU64, Ordering};

#[cfg(not(feature = "mock-instant"))]
use std::time::Instant;
use crate::sleepyinstant::Instant;

use aead::generic_array::GenericArray;
use aead::{AeadInPlace, KeyInit};
Expand Down
2 changes: 1 addition & 1 deletion boringtun/src/noise/timers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::time::Duration;
use mock_instant::Instant;

#[cfg(not(feature = "mock-instant"))]
use std::time::Instant;
use crate::sleepyinstant::Instant;

// Some constants, represent time in seconds
// https://www.wireguard.com/papers/wireguard.pdf#page=14
Expand Down
77 changes: 77 additions & 0 deletions boringtun/src/sleepyinstant/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#![forbid(unsafe_code)]
//! Attempts to provide the same functionality as std::time::Instant, except it
//! uses a timer which accounts for time when the system is asleep
use std::time::Duration;

#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
use windows as inner;

#[cfg(unix)]
mod unix;
#[cfg(unix)]
use unix as inner;

/// A measurement of a monotonically nondecreasing clock.
/// Opaque and useful only with [`Duration`].
///
/// Instants are always guaranteed, barring [platform bugs], to be no less than any previously
/// measured instant when created, and are often useful for tasks such as measuring
/// benchmarks or timing how long an operation takes.
///
/// Note, however, that instants are **not** guaranteed to be **steady**. In other
/// words, each tick of the underlying clock might not be the same length (e.g.
/// some seconds may be longer than others). An instant may jump forwards or
/// experience time dilation (slow down or speed up), but it will never go
/// backwards.
///
/// Instants are opaque types that can only be compared to one another. There is
/// no method to get "the number of seconds" from an instant. Instead, it only
/// allows measuring the duration between two instants (or comparing two
/// instants).
///
/// The size of an `Instant` struct may vary depending on the target operating
/// system.
///
#[derive(Clone, Copy, Debug)]
pub struct Instant {
t: inner::Instant,
}

impl Instant {
/// Returns an instant corresponding to "now".
pub fn now() -> Self {
Self {
t: inner::Instant::now(),
}
}

/// Returns the amount of time elapsed from another instant to this one,
/// or zero duration if that instant is later than this one.
///
/// # Panics
///
/// panics when `earlier` was later than `self`.
pub fn duration_since(&self, earlier: Instant) -> Duration {
self.t.duration_since(earlier.t)
}

/// Returns the amount of time elapsed since this instant was created.
pub fn elapsed(&self) -> Duration {
Self::now().duration_since(*self)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn time_increments_after_sleep() {
let sleep_time = Duration::from_millis(10);
let start = Instant::now();
std::thread::sleep(sleep_time);
assert!(start.elapsed() >= sleep_time);
}
}
48 changes: 48 additions & 0 deletions boringtun/src/sleepyinstant/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::time::Duration;

use nix::sys::time::TimeSpec;
use nix::time::{clock_gettime, ClockId};

#[cfg(any(target_os = "macos", target_os = "ios"))]
const CLOCK_ID: ClockId = ClockId::CLOCK_MONOTONIC;
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
const CLOCK_ID: ClockId = ClockId::CLOCK_BOOTTIME;

#[derive(Clone, Copy, Debug)]
pub(crate) struct Instant {
t: TimeSpec,
}

impl Instant {
pub(crate) fn now() -> Self {
// std::time::Instant unwraps as well, so feel safe doing so here
let t = clock_gettime(CLOCK_ID).unwrap();
Self { t }
}

fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
const NANOSECOND: nix::libc::c_long = 1_000_000_000;
let (tv_sec, tv_nsec) = if self.t.tv_nsec() < earlier.t.tv_nsec() {
(
self.t.tv_sec() - earlier.t.tv_sec() - 1,
self.t.tv_nsec() - earlier.t.tv_nsec() + NANOSECOND,
)
} else {
(
self.t.tv_sec() - earlier.t.tv_sec(),
self.t.tv_nsec() - earlier.t.tv_nsec(),
)
};

if tv_sec < 0 {
None
} else {
Some(Duration::new(tv_sec as _, tv_nsec as _))
}
}

pub(crate) fn duration_since(&self, earlier: Instant) -> Duration {
self.checked_duration_since(earlier)
.unwrap_or(Duration::ZERO)
}
}
1 change: 1 addition & 0 deletions boringtun/src/sleepyinstant/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) use std::time::Instant;

0 comments on commit 6d4fb2e

Please sign in to comment.