openentropy_core/sources/network/
tcp_connect.rs1use std::net::{SocketAddr, TcpStream};
8use std::sync::OnceLock;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::time::{Duration, Instant};
11
12use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
13use crate::sources::helpers::extract_timing_entropy;
14
15const TCP_TARGETS: &[&str] = &["8.8.8.8:53", "1.1.1.1:53", "9.9.9.9:53"];
20const TCP_TIMEOUT: Duration = Duration::from_secs(2);
21
22pub struct TCPConnectSource {
29 index: AtomicUsize,
31}
32
33static TCP_CONNECT_INFO: SourceInfo = SourceInfo {
34 name: "tcp_connect_timing",
35 description: "Nanosecond timing of TCP three-way handshakes to remote hosts",
36 physics: "Times the TCP three-way handshake (SYN -> SYN-ACK -> ACK). \
37 The timing captures: NIC DMA transfer jitter, kernel socket \
38 buffer allocation, remote server load, network path congestion, \
39 and router queuing delays.",
40 category: SourceCategory::Network,
41 platform: Platform::Any,
42 requirements: &[],
43 entropy_rate_estimate: 2.0,
44 composite: false,
45 is_fast: false,
46};
47
48impl TCPConnectSource {
49 pub fn new() -> Self {
50 Self {
51 index: AtomicUsize::new(0),
52 }
53 }
54}
55
56impl Default for TCPConnectSource {
57 fn default() -> Self {
58 Self::new()
59 }
60}
61
62fn tcp_connect_rtt(target: &str, timeout: Duration) -> Option<u128> {
64 let addr: SocketAddr = target.parse().ok()?;
65 let start = Instant::now();
66 let _stream = TcpStream::connect_timeout(&addr, timeout).ok()?;
67 Some(start.elapsed().as_nanos())
68}
69
70impl EntropySource for TCPConnectSource {
71 fn info(&self) -> &SourceInfo {
72 &TCP_CONNECT_INFO
73 }
74
75 fn is_available(&self) -> bool {
76 static TCP_AVAILABLE: OnceLock<bool> = OnceLock::new();
77 *TCP_AVAILABLE.get_or_init(|| tcp_connect_rtt(TCP_TARGETS[0], TCP_TIMEOUT).is_some())
78 }
79
80 fn collect(&self, n_samples: usize) -> Vec<u8> {
81 let target_count = TCP_TARGETS.len();
82
83 let raw_count = n_samples + 64;
84 let mut timings = Vec::with_capacity(raw_count);
85 let max_iterations = raw_count * 4;
86 let mut iter_count = 0;
87 let deadline = Instant::now() + Duration::from_secs(4);
88
89 while timings.len() < raw_count && iter_count < max_iterations {
90 if Instant::now() >= deadline {
91 break;
92 }
93 iter_count += 1;
94 let idx = self.index.fetch_add(1, Ordering::Relaxed);
95 let target = TCP_TARGETS[idx % target_count];
96
97 if let Some(nanos) = tcp_connect_rtt(target, TCP_TIMEOUT) {
98 timings.push(nanos as u64);
99 }
100 }
101
102 extract_timing_entropy(&timings, n_samples)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn tcp_source_info() {
112 let src = TCPConnectSource::new();
113 assert_eq!(src.info().name, "tcp_connect_timing");
114 assert_eq!(src.info().category, SourceCategory::Network);
115 assert!((src.info().entropy_rate_estimate - 2.0).abs() < f64::EPSILON);
116 }
117
118 #[test]
119 #[ignore] fn tcp_connect_collects_bytes() {
121 let src = TCPConnectSource::new();
122 if src.is_available() {
123 let data = src.collect(32);
124 assert!(!data.is_empty());
125 assert!(data.len() <= 32);
126 }
127 }
128}