burrow/burrow/src/wireguard/noise/timers.rs
2023-12-17 01:26:37 +08:00

333 lines
13 KiB
Rust
Executable file

// Copyright (c) 2019 Cloudflare, Inc. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
use std::{
mem,
ops::{Index, IndexMut},
time::{Duration, Instant},
};
use super::{errors::WireGuardError, TunnResult, Tunnel};
// Some constants, represent time in seconds
// https://www.wireguard.com/papers/wireguard.pdf#page=14
pub(crate) const REKEY_AFTER_TIME: Duration = Duration::from_secs(120);
const REJECT_AFTER_TIME: Duration = Duration::from_secs(180);
const REKEY_ATTEMPT_TIME: Duration = Duration::from_secs(90);
pub(crate) const REKEY_TIMEOUT: Duration = Duration::from_secs(5);
const KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(10);
const COOKIE_EXPIRATION_TIME: Duration = Duration::from_secs(120);
#[derive(Debug)]
pub enum TimerName {
/// Current time, updated each call to `update_timers`
TimeCurrent,
/// Time when last handshake was completed
TimeSessionEstablished,
/// Time the last attempt for a new handshake began
TimeLastHandshakeStarted,
/// Time we last received and authenticated a packet
TimeLastPacketReceived,
/// Time we last send a packet
TimeLastPacketSent,
/// Time we last received and authenticated a DATA packet
TimeLastDataPacketReceived,
/// Time we last send a DATA packet
TimeLastDataPacketSent,
/// Time we last received a cookie
TimeCookieReceived,
/// Time we last sent persistent keepalive
TimePersistentKeepalive,
Top,
}
use self::TimerName::*;
#[derive(Debug)]
pub struct Timers {
/// Is the owner of the timer the initiator or the responder for the last
/// handshake?
is_initiator: bool,
/// Start time of the tunnel
time_started: Instant,
timers: [Duration; TimerName::Top as usize],
pub(super) session_timers: [Duration; super::N_SESSIONS],
/// Did we receive data without sending anything back?
want_keepalive: bool,
/// Did we send data without hearing back?
want_handshake: bool,
persistent_keepalive: usize,
/// Should this timer call reset rr function (if not a shared rr instance)
pub(super) should_reset_rr: bool,
}
impl Timers {
pub(super) fn new(persistent_keepalive: Option<u16>, reset_rr: bool) -> Timers {
Timers {
is_initiator: false,
time_started: Instant::now(),
timers: Default::default(),
session_timers: Default::default(),
want_keepalive: Default::default(),
want_handshake: Default::default(),
persistent_keepalive: usize::from(persistent_keepalive.unwrap_or(0)),
should_reset_rr: reset_rr,
}
}
fn is_initiator(&self) -> bool {
self.is_initiator
}
// We don't really clear the timers, but we set them to the current time to
// so the reference time frame is the same
pub(super) fn clear(&mut self) {
let now = Instant::now().duration_since(self.time_started);
for t in &mut self.timers[..] {
*t = now;
}
self.want_handshake = false;
self.want_keepalive = false;
}
}
impl Index<TimerName> for Timers {
type Output = Duration;
fn index(&self, index: TimerName) -> &Duration {
&self.timers[index as usize]
}
}
impl IndexMut<TimerName> for Timers {
fn index_mut(&mut self, index: TimerName) -> &mut Duration {
&mut self.timers[index as usize]
}
}
impl Tunnel {
pub(super) fn timer_tick(&mut self, timer_name: TimerName) {
match timer_name {
TimeLastPacketReceived => {
self.timers.want_keepalive = true;
self.timers.want_handshake = false;
}
TimeLastPacketSent => {
self.timers.want_handshake = true;
self.timers.want_keepalive = false;
}
_ => {}
}
let time = self.timers[TimeCurrent];
self.timers[timer_name] = time;
}
pub(super) fn timer_tick_session_established(
&mut self,
is_initiator: bool,
session_idx: usize,
) {
self.timer_tick(TimeSessionEstablished);
self.timers.session_timers[session_idx % super::N_SESSIONS] = self.timers[TimeCurrent];
self.timers.is_initiator = is_initiator;
}
// We don't really clear the timers, but we set them to the current time to
// so the reference time frame is the same
fn clear_all(&mut self) {
for session in &mut self.sessions {
*session = None;
}
self.packet_queue.clear();
self.timers.clear();
}
fn update_session_timers(&mut self, time_now: Duration) {
let timers = &mut self.timers;
for (i, t) in timers.session_timers.iter_mut().enumerate() {
if time_now - *t > REJECT_AFTER_TIME {
if let Some(session) = self.sessions[i].take() {
tracing::debug!(
message = "SESSION_EXPIRED(REJECT_AFTER_TIME)",
session = session.receiving_index
);
}
*t = time_now;
}
}
}
pub fn update_timers<'a>(&mut self, dst: &'a mut [u8]) -> TunnResult<'a> {
let mut handshake_initiation_required = false;
let mut keepalive_required = false;
let time = Instant::now();
if self.timers.should_reset_rr {
self.rate_limiter.reset_count();
}
// All the times are counted from tunnel initiation, for efficiency our timers
// are rounded to a second, as there is no real benefit to having highly
// accurate timers.
let now = time.duration_since(self.timers.time_started);
self.timers[TimeCurrent] = now;
self.update_session_timers(now);
// Load timers only once:
let session_established = self.timers[TimeSessionEstablished];
let handshake_started = self.timers[TimeLastHandshakeStarted];
let aut_packet_received = self.timers[TimeLastPacketReceived];
let aut_packet_sent = self.timers[TimeLastPacketSent];
let data_packet_received = self.timers[TimeLastDataPacketReceived];
let data_packet_sent = self.timers[TimeLastDataPacketSent];
let persistent_keepalive = self.timers.persistent_keepalive;
{
if self.handshake.is_expired() {
return TunnResult::Err(WireGuardError::ConnectionExpired)
}
// Clear cookie after COOKIE_EXPIRATION_TIME
if self.handshake.has_cookie()
&& now - self.timers[TimeCookieReceived] >= COOKIE_EXPIRATION_TIME
{
self.handshake.clear_cookie();
}
// All ephemeral private keys and symmetric session keys are zeroed out after
// (REJECT_AFTER_TIME * 3) ms if no new keys have been exchanged.
if now - session_established >= REJECT_AFTER_TIME * 3 {
tracing::error!("CONNECTION_EXPIRED(REJECT_AFTER_TIME * 3)");
self.handshake.set_expired();
self.clear_all();
return TunnResult::Err(WireGuardError::ConnectionExpired)
}
if let Some(time_init_sent) = self.handshake.timer() {
// Handshake Initiation Retransmission
if now - handshake_started >= REKEY_ATTEMPT_TIME {
// After REKEY_ATTEMPT_TIME ms of trying to initiate a new handshake,
// the retries give up and cease, and clear all existing packets queued
// up to be sent. If a packet is explicitly queued up to be sent, then
// this timer is reset.
tracing::error!("CONNECTION_EXPIRED(REKEY_ATTEMPT_TIME)");
self.handshake.set_expired();
self.clear_all();
return TunnResult::Err(WireGuardError::ConnectionExpired)
}
if time_init_sent.elapsed() >= REKEY_TIMEOUT {
// We avoid using `time` here, because it can be earlier than `time_init_sent`.
// Once `checked_duration_since` is stable we can use that.
// A handshake initiation is retried after REKEY_TIMEOUT + jitter ms,
// if a response has not been received, where jitter is some random
// value between 0 and 333 ms.
tracing::warn!("HANDSHAKE(REKEY_TIMEOUT)");
handshake_initiation_required = true;
}
} else {
if self.timers.is_initiator() {
// After sending a packet, if the sender was the original initiator
// of the handshake and if the current session key is REKEY_AFTER_TIME
// ms old, we initiate a new handshake. If the sender was the original
// responder of the handshake, it does not re-initiate a new handshake
// after REKEY_AFTER_TIME ms like the original initiator does.
if session_established < data_packet_sent
&& now - session_established >= REKEY_AFTER_TIME
{
tracing::debug!("HANDSHAKE(REKEY_AFTER_TIME (on send))");
handshake_initiation_required = true;
}
// After receiving a packet, if the receiver was the original initiator
// of the handshake and if the current session key is REJECT_AFTER_TIME
// - KEEPALIVE_TIMEOUT - REKEY_TIMEOUT ms old, we initiate a new
// handshake.
if session_established < data_packet_received
&& now - session_established
>= REJECT_AFTER_TIME - KEEPALIVE_TIMEOUT - REKEY_TIMEOUT
{
tracing::warn!(
"HANDSHAKE(REJECT_AFTER_TIME - KEEPALIVE_TIMEOUT - \
REKEY_TIMEOUT \
(on receive))"
);
handshake_initiation_required = true;
}
}
// If we have sent a packet to a given peer but have not received a
// packet after from that peer for (KEEPALIVE + REKEY_TIMEOUT) ms,
// we initiate a new handshake.
if data_packet_sent > aut_packet_received
&& now - aut_packet_received >= KEEPALIVE_TIMEOUT + REKEY_TIMEOUT
&& mem::replace(&mut self.timers.want_handshake, false)
{
tracing::warn!("HANDSHAKE(KEEPALIVE + REKEY_TIMEOUT)");
handshake_initiation_required = true;
}
if !handshake_initiation_required {
// If a packet has been received from a given peer, but we have not sent one
// back to the given peer in KEEPALIVE ms, we send an empty
// packet.
if data_packet_received > aut_packet_sent
&& now - aut_packet_sent >= KEEPALIVE_TIMEOUT
&& mem::replace(&mut self.timers.want_keepalive, false)
{
tracing::debug!("KEEPALIVE(KEEPALIVE_TIMEOUT)");
keepalive_required = true;
}
// Persistent KEEPALIVE
if persistent_keepalive > 0
&& (now - self.timers[TimePersistentKeepalive]
>= Duration::from_secs(persistent_keepalive as _))
{
tracing::debug!("KEEPALIVE(PERSISTENT_KEEPALIVE)");
self.timer_tick(TimePersistentKeepalive);
keepalive_required = true;
}
}
}
}
if handshake_initiation_required {
return self.format_handshake_initiation(dst, true)
}
if keepalive_required {
return self.encapsulate(&[], dst)
}
TunnResult::Done
}
pub fn time_since_last_handshake(&self) -> Option<Duration> {
let current_session = self.current;
if self.sessions[current_session % super::N_SESSIONS].is_some() {
let duration_since_tun_start = Instant::now().duration_since(self.timers.time_started);
let duration_since_session_established = self.timers[TimeSessionEstablished];
Some(duration_since_tun_start - duration_since_session_established)
} else {
None
}
}
pub fn persistent_keepalive(&self) -> Option<u16> {
let keepalive = self.timers.persistent_keepalive;
if keepalive > 0 {
Some(keepalive as u16)
} else {
None
}
}
}