mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 18:18:55 +00:00
feat(connection): introduce keep-alive and expose last_seen (#3388)
We configure wireguard's keep-alive to 5 seconds and patch `boringtun` to expose the time since the last packet, which will always be < KEEP_ALIVE (5 seconds) if the connection is intact. The policy on what to do on missed keep-alives is pushed to the upper layer. Instead of acting on it, we simply expose a `stats` function that exposes the data. An upper layer can then decide on what to do in the case on missed keep-alives. Resolves: #3372.
This commit is contained in:
2
rust/Cargo.lock
generated
2
rust/Cargo.lock
generated
@@ -744,7 +744,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "boringtun"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/cloudflare/boringtun?branch=master#f672bb6c1e1e371240a8d151f15854687eb740bb"
|
||||
source = "git+https://github.com/thomaseizinger/boringtun?branch=feat/expose-last-seen#6fd54c027e6b78192a02de3e77d00552ec36968d"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"base64 0.13.1",
|
||||
|
||||
@@ -47,7 +47,7 @@ firezone-tunnel = { path = "connlib/tunnel"}
|
||||
phoenix-channel = { path = "phoenix-channel"}
|
||||
|
||||
[patch.crates-io]
|
||||
boringtun = { git = "https://github.com/cloudflare/boringtun", branch = "master" } # Contains unreleased patches we need (bump of x25519-dalek)
|
||||
boringtun = { git = "https://github.com/thomaseizinger/boringtun", branch = "feat/expose-last-seen" }
|
||||
webrtc = { git = "https://github.com/firezone/webrtc", branch = "expose-new-endpoint" }
|
||||
str0m = { git = "https://github.com/algesten/str0m", branch = "main" }
|
||||
|
||||
|
||||
62
rust/connlib/connection/src/info.rs
Normal file
62
rust/connlib/connection/src/info.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::pool::WIREGUARD_KEEP_ALIVE;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectionInfo {
|
||||
pub last_seen: Option<Instant>,
|
||||
|
||||
/// When this instance of [`ConnectionInfo`] was created.
|
||||
pub generated_at: Instant,
|
||||
}
|
||||
|
||||
impl ConnectionInfo {
|
||||
pub fn missed_keep_alives(&self) -> u64 {
|
||||
let Some(last_seen) = self.last_seen else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let duration = self.generated_at.duration_since(last_seen);
|
||||
|
||||
duration.as_secs() / WIREGUARD_KEEP_ALIVE as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn no_missed_keep_alives_on_none() {
|
||||
let info = info(None);
|
||||
|
||||
let missed_keep_alives = info.missed_keep_alives();
|
||||
|
||||
assert_eq!(missed_keep_alives, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_than_5_sec_one_missed_keep_alive() {
|
||||
let info = info(Some(Instant::now() - Duration::from_secs(6)));
|
||||
|
||||
let missed_keep_alives = info.missed_keep_alives();
|
||||
|
||||
assert_eq!(missed_keep_alives, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_than_10_sec_two_missed_keep_alives() {
|
||||
let info = info(Some(Instant::now() - Duration::from_secs(11)));
|
||||
|
||||
let missed_keep_alives = info.missed_keep_alives();
|
||||
|
||||
assert_eq!(missed_keep_alives, 2);
|
||||
}
|
||||
|
||||
fn info(last_seen: Option<Instant>) -> ConnectionInfo {
|
||||
ConnectionInfo {
|
||||
last_seen,
|
||||
generated_at: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
mod allocation;
|
||||
mod channel_data;
|
||||
mod index;
|
||||
mod info;
|
||||
mod ip_packet;
|
||||
mod pool;
|
||||
mod stun_binding;
|
||||
|
||||
pub use info::ConnectionInfo;
|
||||
pub use ip_packet::IpPacket;
|
||||
pub use pool::{
|
||||
Answer, ClientConnectionPool, ConnectionPool, Credentials, Error, Event, Offer,
|
||||
|
||||
@@ -21,6 +21,7 @@ use str0m::{Candidate, CandidateKind, IceConnectionState};
|
||||
|
||||
use crate::allocation::Allocation;
|
||||
use crate::index::IndexLfsr;
|
||||
use crate::info::ConnectionInfo;
|
||||
use crate::stun_binding::StunBinding;
|
||||
use crate::IpPacket;
|
||||
use boringtun::noise::errors::WireGuardError;
|
||||
@@ -30,6 +31,9 @@ use stun_codec::rfc5389::attributes::{Realm, Username};
|
||||
// Note: Taken from boringtun
|
||||
const HANDSHAKE_RATE_LIMIT: u64 = 100;
|
||||
|
||||
/// How often wireguard will send a keep-alive packet.
|
||||
pub(crate) const WIREGUARD_KEEP_ALIVE: u16 = 5;
|
||||
|
||||
const MAX_UDP_SIZE: usize = (1 << 16) - 1;
|
||||
|
||||
/// Manages a set of wireguard connections for a server.
|
||||
@@ -101,6 +105,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Lazily retrieve stats of all connections.
|
||||
pub fn stats(&self) -> impl Iterator<Item = (TId, ConnectionInfo)> + '_ {
|
||||
self.negotiated_connections.iter().map(|(id, c)| {
|
||||
(
|
||||
*id,
|
||||
ConnectionInfo {
|
||||
last_seen: c.last_seen,
|
||||
generated_at: self.last_now,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_local_interface(&mut self, local_addr: SocketAddr) {
|
||||
self.local_interfaces.insert(local_addr);
|
||||
|
||||
@@ -516,7 +533,7 @@ where
|
||||
self.private_key.clone(),
|
||||
remote,
|
||||
Some(key),
|
||||
None,
|
||||
Some(WIREGUARD_KEEP_ALIVE),
|
||||
self.index.next(),
|
||||
Some(self.rate_limiter.clone()),
|
||||
),
|
||||
@@ -525,6 +542,7 @@ where
|
||||
next_timer_update: self.last_now,
|
||||
remote_socket: None,
|
||||
possible_sockets: HashSet::default(),
|
||||
last_seen: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -961,6 +979,8 @@ struct Connection {
|
||||
tunnel: Tunn,
|
||||
next_timer_update: Instant,
|
||||
|
||||
last_seen: Option<Instant>,
|
||||
|
||||
// When this is `Some`, we are connected.
|
||||
remote_socket: Option<RemoteSocket>,
|
||||
// Socket addresses from which we might receive data (even before we are connected).
|
||||
@@ -1034,6 +1054,12 @@ impl Connection {
|
||||
) -> Result<Option<Transmit<'static>>, WireGuardError> {
|
||||
self.agent.handle_timeout(now);
|
||||
|
||||
// TODO: `boringtun` is impure because it calls `Instant::now`.
|
||||
self.last_seen = self
|
||||
.tunnel
|
||||
.time_since_last_received()
|
||||
.and_then(|d| now.checked_sub(d));
|
||||
|
||||
if now >= self.next_timer_update {
|
||||
self.next_timer_update = now + Duration::from_secs(1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user