diff --git a/src-tauri/core/src/connection/active_state.rs b/src-tauri/core/src/connection/active_state.rs index d8e1ce4e..dece95ee 100644 --- a/src-tauri/core/src/connection/active_state.rs +++ b/src-tauri/core/src/connection/active_state.rs @@ -59,7 +59,7 @@ pub struct InterfaceStats { impl From for InterfaceStats { fn from(stats: super::apple::Stats) -> Self { Self { - listen_port: 0, + listen_port: stats.listen_port.unwrap_or_default().into(), tx_bytes: stats.tx_bytes, rx_bytes: stats.rx_bytes, last_handshake: (stats.last_handshake != 0).then_some(stats.last_handshake), diff --git a/src-tauri/core/src/connection/apple.rs b/src-tauri/core/src/connection/apple.rs index 5e848fac..029dc58f 100644 --- a/src-tauri/core/src/connection/apple.rs +++ b/src-tauri/core/src/connection/apple.rs @@ -161,6 +161,8 @@ pub struct Stats { pub tx_bytes: u64, pub rx_bytes: u64, pub last_handshake: u64, + pub listen_port: Option, + pub persistent_keep_alive: Option, } /// Retrieve VPN tunnel statistics from VPNExtension. @@ -441,3 +443,28 @@ pub async fn sync_locations_and_tunnels(mtu: Option) -> Result<(), sqlx::Er Ok(()) } + +#[cfg(test)] +mod tests { + use super::Stats; + + #[test] + fn test_stats_deserialize_listen_port_and_keepalive() { + let stats: Stats = serde_json::from_str( + r#"{"txBytes":1,"rxBytes":2,"lastHandshake":3,"locationId":7, + "listenPort":51820,"persistentKeepAlive":25}"#, + ) + .unwrap(); + assert_eq!(stats.location_id, Some(7)); + assert_eq!(stats.listen_port, Some(51820)); + assert_eq!(stats.persistent_keep_alive, Some(25)); + + // VPNExtension builds without these fields still send stats. + let stats: Stats = + serde_json::from_str(r#"{"txBytes":1,"rxBytes":2,"lastHandshake":3,"tunnelId":4}"#) + .unwrap(); + assert_eq!(stats.tunnel_id, Some(4)); + assert_eq!(stats.listen_port, None); + assert_eq!(stats.persistent_keep_alive, None); + } +} diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index de346f45..23c9024f 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -165,8 +165,8 @@ pub(crate) async fn stats_handler(id: Id, connection_type: ConnectionType) { stats.tx_bytes.cast_signed(), stats.rx_bytes.cast_signed(), stats.last_handshake.cast_signed(), - 0, - None, + stats.listen_port.unwrap_or_default().into(), + stats.persistent_keep_alive, ) .with_diffs(upload_diff, download_diff); match location_stats.save(&mut *transaction).await { @@ -185,8 +185,8 @@ pub(crate) async fn stats_handler(id: Id, connection_type: ConnectionType) { stats.rx_bytes.cast_signed(), stats.last_handshake.cast_signed(), chrono::Utc::now().naive_utc(), - 0, - 0, + stats.listen_port.unwrap_or_default().into(), + stats.persistent_keep_alive.unwrap_or_default(), ) .with_diffs(upload_diff, download_diff); match tunnel_stats.save(&mut *transaction).await { diff --git a/swift/extension/VPNExtension/Adapter.swift b/swift/extension/VPNExtension/Adapter.swift index 5203abe4..7480561c 100644 --- a/swift/extension/VPNExtension/Adapter.swift +++ b/swift/extension/VPNExtension/Adapter.swift @@ -72,6 +72,7 @@ private struct PathSignature: Equatable { /// For statistics returned to Rust code. var locationId: UInt64? var tunnelId: UInt64? + var persistentKeepAlive: UInt16? private let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() @@ -109,12 +110,18 @@ private struct PathSignature: Equatable { func stats() -> Stats? { syncOnQueue { guard let stats = tunnel?.stats() else { return nil } + var listenPort: UInt16? + if case .hostPort(_, let port)? = connection?.currentPath?.localEndpoint { + listenPort = port.rawValue + } return Stats( txBytes: stats.txBytes, rxBytes: stats.rxBytes, lastHandshake: stats.lastHandshake, locationId: locationId, - tunnelId: tunnelId + tunnelId: tunnelId, + listenPort: listenPort, + persistentKeepAlive: persistentKeepAlive ) } } @@ -154,6 +161,7 @@ private struct PathSignature: Equatable { ) locationId = tunnelConfiguration.locationId tunnelId = tunnelConfiguration.tunnelId + persistentKeepAlive = peer.persistentKeepAlive state = .running reconnectAttempts = 0 lastPathSignature = nil diff --git a/swift/plugin/Sources/Defguard/Stats.swift b/swift/plugin/Sources/Defguard/Stats.swift index 2c833446..0bcbecce 100644 --- a/swift/plugin/Sources/Defguard/Stats.swift +++ b/swift/plugin/Sources/Defguard/Stats.swift @@ -7,12 +7,19 @@ public class Stats: NSObject, Codable { // One or the other. var locationId: UInt64? var tunnelId: UInt64? + var listenPort: UInt16? + var persistentKeepAlive: UInt16? - init(txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64, locationId: UInt64?, tunnelId: UInt64?) { + init( + txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64, locationId: UInt64?, + tunnelId: UInt64?, listenPort: UInt16?, persistentKeepAlive: UInt16? + ) { self.txBytes = txBytes self.rxBytes = rxBytes self.lastHandshake = lastHandshake self.locationId = locationId self.tunnelId = tunnelId + self.listenPort = listenPort + self.persistentKeepAlive = persistentKeepAlive } }