1use std::net::{IpAddr, SocketAddr};
18use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
19use std::sync::Arc;
20use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
21
22use tokio::io::{AsyncReadExt, AsyncWriteExt};
23use tokio::net::{TcpListener, TcpStream};
24use tokio::time::timeout;
25
26use crate::dns;
27use crate::error::Error;
28
29pub const DEFAULT_PORT: u16 = 33435;
31
32const MAGIC: [u8; 4] = [0x4D, 0x50, 0x42, 0x44]; const VERSION: u8 = 1;
37
38#[repr(u8)]
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum MessageType {
42 Probe = 1,
43 Echo = 2,
44 Ping = 3,
45 Pong = 4,
46}
47
48impl TryFrom<u8> for MessageType {
49 type Error = ();
50
51 fn try_from(value: u8) -> Result<Self, Self::Error> {
52 match value {
53 1 => Ok(MessageType::Probe),
54 2 => Ok(MessageType::Echo),
55 3 => Ok(MessageType::Ping),
56 4 => Ok(MessageType::Pong),
57 _ => Err(()),
58 }
59 }
60}
61
62#[derive(Debug, Clone)]
64struct ProbeMessage {
65 sequence: u32,
66 client_send_time: u64,
67}
68
69impl ProbeMessage {
70 fn new(sequence: u32) -> Self {
71 let client_send_time = SystemTime::now()
72 .duration_since(UNIX_EPOCH)
73 .unwrap_or_default()
74 .as_micros() as u64;
75
76 Self {
77 sequence,
78 client_send_time,
79 }
80 }
81
82 fn to_bytes(&self) -> Vec<u8> {
83 let mut buf = Vec::with_capacity(17);
84 buf.extend_from_slice(&MAGIC);
85 buf.push(VERSION);
86 buf.push(MessageType::Probe as u8);
87 buf.extend_from_slice(&self.sequence.to_be_bytes());
88 buf.extend_from_slice(&self.client_send_time.to_be_bytes());
89 buf
90 }
91
92 fn from_bytes(data: &[u8]) -> Option<Self> {
93 if data.len() < 18 || data[0..4] != MAGIC || data[4] != VERSION {
94 return None;
95 }
96
97 let msg_type = MessageType::try_from(data[5]).ok()?;
98 if msg_type != MessageType::Probe {
99 return None;
100 }
101
102 let sequence = u32::from_be_bytes([data[6], data[7], data[8], data[9]]);
103 let client_send_time = u64::from_be_bytes([
104 data[10], data[11], data[12], data[13],
105 data[14], data[15], data[16], data[17],
106 ]);
107
108 Some(Self { sequence, client_send_time })
109 }
110}
111
112#[derive(Debug, Clone)]
114struct EchoMessage {
115 sequence: u32,
116 client_send_time: u64,
117 server_recv_time: u64,
118 server_send_time: u64,
119}
120
121impl EchoMessage {
122 fn from_probe(probe: &ProbeMessage) -> Self {
123 let now = SystemTime::now()
124 .duration_since(UNIX_EPOCH)
125 .unwrap_or_default()
126 .as_micros() as u64;
127
128 Self {
129 sequence: probe.sequence,
130 client_send_time: probe.client_send_time,
131 server_recv_time: now,
132 server_send_time: now,
133 }
134 }
135
136 fn to_bytes(&self) -> Vec<u8> {
137 let mut buf = Vec::with_capacity(34);
138 buf.extend_from_slice(&MAGIC);
139 buf.push(VERSION);
140 buf.push(MessageType::Echo as u8);
141 buf.extend_from_slice(&self.sequence.to_be_bytes());
142 buf.extend_from_slice(&self.client_send_time.to_be_bytes());
143 buf.extend_from_slice(&self.server_recv_time.to_be_bytes());
144 buf.extend_from_slice(&self.server_send_time.to_be_bytes());
145 buf
146 }
147
148 fn from_bytes(data: &[u8]) -> Option<Self> {
149 if data.len() < 34 || data[0..4] != MAGIC || data[4] != VERSION {
150 return None;
151 }
152
153 let msg_type = MessageType::try_from(data[5]).ok()?;
154 if msg_type != MessageType::Echo {
155 return None;
156 }
157
158 let sequence = u32::from_be_bytes([data[6], data[7], data[8], data[9]]);
159 let client_send_time = u64::from_be_bytes([
160 data[10], data[11], data[12], data[13],
161 data[14], data[15], data[16], data[17],
162 ]);
163 let server_recv_time = u64::from_be_bytes([
164 data[18], data[19], data[20], data[21],
165 data[22], data[23], data[24], data[25],
166 ]);
167 let server_send_time = u64::from_be_bytes([
168 data[26], data[27], data[28], data[29],
169 data[30], data[31], data[32], data[33],
170 ]);
171
172 Some(Self {
173 sequence,
174 client_send_time,
175 server_recv_time,
176 server_send_time,
177 })
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct DirectionStats {
184 pub sent: u32,
186 pub received: u32,
188 pub min_ms: f64,
190 pub max_ms: f64,
192 pub mean_ms: f64,
194 pub jitter_ms: f64,
196 samples: Vec<u64>,
198}
199
200impl DirectionStats {
201 fn new() -> Self {
202 Self {
203 sent: 0,
204 received: 0,
205 min_ms: f64::MAX,
206 max_ms: 0.0,
207 mean_ms: 0.0,
208 jitter_ms: 0.0,
209 samples: Vec::new(),
210 }
211 }
212
213 fn add_sample(&mut self, latency_us: u64) {
214 self.received += 1;
215 self.samples.push(latency_us);
216
217 let latency_ms = latency_us as f64 / 1000.0;
218
219 if latency_ms < self.min_ms {
220 self.min_ms = latency_ms;
221 }
222 if latency_ms > self.max_ms {
223 self.max_ms = latency_ms;
224 }
225 }
226
227 fn finalize(&mut self) {
228 if self.samples.is_empty() {
229 self.min_ms = 0.0;
230 return;
231 }
232
233 let sum: u64 = self.samples.iter().sum();
234 self.mean_ms = (sum as f64 / self.samples.len() as f64) / 1000.0;
235
236 if self.samples.len() > 1 {
237 let mut jitter_sum = 0.0;
238 for i in 1..self.samples.len() {
239 jitter_sum += (self.samples[i] as f64 - self.samples[i-1] as f64).abs();
240 }
241 self.jitter_ms = (jitter_sum / (self.samples.len() - 1) as f64) / 1000.0;
242 }
243 }
244
245 pub fn loss_percent(&self) -> f64 {
247 if self.sent == 0 {
248 0.0
249 } else {
250 ((self.sent - self.received) as f64 / self.sent as f64) * 100.0
251 }
252 }
253}
254
255#[derive(Debug, Clone)]
257pub struct BidirectionalResult {
258 pub target: String,
260 pub target_ip: IpAddr,
262 pub port: u16,
264 pub forward: DirectionStats,
266 pub reverse: DirectionStats,
268 pub round_trip: DirectionStats,
270 pub asymmetry_score: f64,
272 pub asymmetric: bool,
274 pub probe_count: u32,
276 pub duration: Duration,
278}
279
280impl BidirectionalResult {
281 pub fn interpretation(&self) -> &'static str {
283 if self.asymmetry_score < 0.1 {
284 "Symmetric path"
285 } else if self.asymmetry_score < 0.3 {
286 "Slight asymmetry"
287 } else if self.asymmetry_score < 0.5 {
288 "Moderate asymmetry"
289 } else {
290 "Significant asymmetry - likely different ECMP paths"
291 }
292 }
293}
294
295#[derive(Debug, Clone)]
297pub struct BidirectionalOptions {
298 pub port: u16,
300 pub probe_count: u32,
302 pub interval: Duration,
304 pub timeout: Duration,
306}
307
308impl Default for BidirectionalOptions {
309 fn default() -> Self {
310 Self {
311 port: DEFAULT_PORT,
312 probe_count: 10,
313 interval: Duration::from_millis(100),
314 timeout: Duration::from_secs(5),
315 }
316 }
317}
318
319pub struct BidirectionalServer {
321 listener: TcpListener,
322 running: Arc<AtomicBool>,
323 probes_handled: Arc<AtomicU64>,
324}
325
326impl BidirectionalServer {
327 pub async fn bind(addr: &str) -> crate::Result<Self> {
329 let listener = TcpListener::bind(addr).await
330 .map_err(Error::SocketCreation)?;
331
332 Ok(Self {
333 listener,
334 running: Arc::new(AtomicBool::new(false)),
335 probes_handled: Arc::new(AtomicU64::new(0)),
336 })
337 }
338
339 pub fn local_addr(&self) -> crate::Result<SocketAddr> {
341 self.listener.local_addr()
342 .map_err(Error::SocketCreation)
343 }
344
345 pub fn probes_handled(&self) -> u64 {
347 self.probes_handled.load(Ordering::Relaxed)
348 }
349
350 pub fn is_running(&self) -> bool {
352 self.running.load(Ordering::Relaxed)
353 }
354
355 pub fn stop(&self) {
357 self.running.store(false, Ordering::Relaxed);
358 }
359
360 pub async fn run(&self) -> crate::Result<()> {
362 self.running.store(true, Ordering::Relaxed);
363
364 while self.running.load(Ordering::Relaxed) {
365 let accept_result = timeout(
366 Duration::from_millis(100),
367 self.listener.accept()
368 ).await;
369
370 match accept_result {
371 Ok(Ok((stream, _addr))) => {
372 let probes_handled = self.probes_handled.clone();
373 tokio::spawn(async move {
374 let _ = Self::handle_connection(stream, probes_handled).await;
375 });
376 }
377 Ok(Err(e)) => {
378 if self.running.load(Ordering::Relaxed) {
379 return Err(Error::ConnectionFailed {
380 target: "server".to_string(),
381 message: e.to_string(),
382 });
383 }
384 }
385 Err(_) => {
386 continue;
388 }
389 }
390 }
391
392 Ok(())
393 }
394
395 async fn handle_connection(
396 mut stream: TcpStream,
397 probes_handled: Arc<AtomicU64>,
398 ) -> crate::Result<()> {
399 let mut buf = [0u8; 64];
400
401 loop {
402 match stream.read(&mut buf).await {
403 Ok(0) => break, Ok(n) => {
405 if let Some(probe) = ProbeMessage::from_bytes(&buf[..n]) {
406 let echo = EchoMessage::from_probe(&probe);
407 let echo_bytes = echo.to_bytes();
408
409 if stream.write_all(&echo_bytes).await.is_ok() {
410 probes_handled.fetch_add(1, Ordering::Relaxed);
411 }
412 }
413 }
414 Err(_) => break,
415 }
416 }
417
418 Ok(())
419 }
420}
421
422pub async fn probe_bidirectional(
424 target: &str,
425 options: &BidirectionalOptions,
426) -> crate::Result<BidirectionalResult> {
427 let start = Instant::now();
428
429 let dns_result = dns::resolve_ipv4(target).await?;
431 let target_ip = dns_result.ip;
432 let addr = SocketAddr::new(target_ip, options.port);
433
434 let mut stream = timeout(options.timeout, TcpStream::connect(addr))
436 .await
437 .map_err(|_| Error::Timeout { timeout_ms: options.timeout.as_millis() as u64 })?
438 .map_err(|e| Error::ConnectionFailed {
439 target: target.to_string(),
440 message: e.to_string(),
441 })?;
442
443 let mut forward = DirectionStats::new();
444 let mut reverse = DirectionStats::new();
445 let mut round_trip = DirectionStats::new();
446
447 let mut buf = [0u8; 64];
448
449 for seq in 0..options.probe_count {
450 forward.sent += 1;
451 reverse.sent += 1;
452 round_trip.sent += 1;
453
454 let probe = ProbeMessage::new(seq);
455 let probe_bytes = probe.to_bytes();
456 let send_time = Instant::now();
457
458 if stream.write_all(&probe_bytes).await.is_err() {
460 continue;
461 }
462
463 let read_result = timeout(options.timeout, stream.read(&mut buf)).await;
465 let recv_time = Instant::now();
466
467 match read_result {
468 Ok(Ok(n)) if n > 0 => {
469 if let Some(echo) = EchoMessage::from_bytes(&buf[..n]) {
470 let client_recv_time = SystemTime::now()
471 .duration_since(UNIX_EPOCH)
472 .unwrap_or_default()
473 .as_micros() as u64;
474
475 let forward_latency = echo.server_recv_time.saturating_sub(echo.client_send_time);
477 forward.add_sample(forward_latency);
478
479 let reverse_latency = client_recv_time.saturating_sub(echo.server_send_time);
481 reverse.add_sample(reverse_latency);
482
483 let rtt = recv_time.duration_since(send_time).as_micros() as u64;
485 round_trip.add_sample(rtt);
486 }
487 }
488 _ => {
489 }
491 }
492
493 if seq < options.probe_count - 1 {
495 tokio::time::sleep(options.interval).await;
496 }
497 }
498
499 forward.finalize();
501 reverse.finalize();
502 round_trip.finalize();
503
504 let asymmetry_score = if forward.received > 0 && reverse.received > 0 {
506 let diff = (forward.mean_ms - reverse.mean_ms).abs();
507 let avg = (forward.mean_ms + reverse.mean_ms) / 2.0;
508 if avg > 0.0 {
509 (diff / avg).min(1.0)
510 } else {
511 0.0
512 }
513 } else {
514 0.0
515 };
516
517 let duration = start.elapsed();
518
519 Ok(BidirectionalResult {
520 target: target.to_string(),
521 target_ip,
522 port: options.port,
523 forward,
524 reverse,
525 round_trip,
526 asymmetry_score,
527 asymmetric: asymmetry_score > 0.3,
528 probe_count: options.probe_count,
529 duration,
530 })
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536 use std::net::Ipv4Addr;
537
538 #[test]
539 fn test_probe_message_serialization() {
540 let probe = ProbeMessage::new(42);
541 let bytes = probe.to_bytes();
542
543 assert_eq!(&bytes[0..4], &MAGIC);
544 assert_eq!(bytes[4], VERSION);
545 assert_eq!(bytes[5], MessageType::Probe as u8);
546
547 let parsed = ProbeMessage::from_bytes(&bytes).unwrap();
548 assert_eq!(parsed.sequence, 42);
549 assert_eq!(parsed.client_send_time, probe.client_send_time);
550 }
551
552 #[test]
553 fn test_echo_message_serialization() {
554 let probe = ProbeMessage::new(123);
555 let echo = EchoMessage::from_probe(&probe);
556 let bytes = echo.to_bytes();
557
558 assert_eq!(&bytes[0..4], &MAGIC);
559 assert_eq!(bytes[4], VERSION);
560 assert_eq!(bytes[5], MessageType::Echo as u8);
561
562 let parsed = EchoMessage::from_bytes(&bytes).unwrap();
563 assert_eq!(parsed.sequence, 123);
564 assert_eq!(parsed.client_send_time, probe.client_send_time);
565 }
566
567 #[test]
568 fn test_invalid_message() {
569 let invalid = [0u8; 10];
570 assert!(ProbeMessage::from_bytes(&invalid).is_none());
571 assert!(EchoMessage::from_bytes(&invalid).is_none());
572 }
573
574 #[test]
575 fn test_direction_stats() {
576 let mut stats = DirectionStats::new();
577
578 stats.sent = 5;
579 stats.add_sample(10_000); stats.add_sample(15_000); stats.add_sample(12_000); stats.finalize();
584
585 assert_eq!(stats.received, 3);
586 assert!((stats.min_ms - 10.0).abs() < 0.01);
587 assert!((stats.max_ms - 15.0).abs() < 0.01);
588 assert!((stats.mean_ms - 12.33).abs() < 0.1);
589 assert!(stats.jitter_ms > 0.0);
590 assert!((stats.loss_percent() - 40.0).abs() < 0.01);
591 }
592
593 #[test]
594 fn test_direction_stats_empty() {
595 let mut stats = DirectionStats::new();
596 stats.sent = 3;
597 stats.finalize();
598
599 assert_eq!(stats.received, 0);
600 assert_eq!(stats.min_ms, 0.0);
601 assert_eq!(stats.mean_ms, 0.0);
602 assert!((stats.loss_percent() - 100.0).abs() < 0.01);
603 }
604
605 #[test]
606 fn test_direction_stats_single_sample() {
607 let mut stats = DirectionStats::new();
608 stats.sent = 1;
609 stats.add_sample(5_000);
610 stats.finalize();
611
612 assert_eq!(stats.received, 1);
613 assert!((stats.min_ms - 5.0).abs() < 0.01);
614 assert!((stats.max_ms - 5.0).abs() < 0.01);
615 assert!((stats.mean_ms - 5.0).abs() < 0.01);
616 assert_eq!(stats.jitter_ms, 0.0);
617 }
618
619 #[test]
620 fn test_asymmetry_interpretation() {
621 let result = BidirectionalResult {
622 target: "test".to_string(),
623 target_ip: IpAddr::V4(Ipv4Addr::LOCALHOST),
624 port: DEFAULT_PORT,
625 forward: DirectionStats::new(),
626 reverse: DirectionStats::new(),
627 round_trip: DirectionStats::new(),
628 asymmetry_score: 0.05,
629 asymmetric: false,
630 probe_count: 10,
631 duration: Duration::from_secs(1),
632 };
633
634 assert_eq!(result.interpretation(), "Symmetric path");
635
636 let result2 = BidirectionalResult {
637 asymmetry_score: 0.6,
638 asymmetric: true,
639 ..result.clone()
640 };
641
642 assert!(result2.interpretation().contains("Significant"));
643 }
644
645 #[test]
646 fn test_options_default() {
647 let opts = BidirectionalOptions::default();
648
649 assert_eq!(opts.port, DEFAULT_PORT);
650 assert_eq!(opts.probe_count, 10);
651 assert_eq!(opts.interval, Duration::from_millis(100));
652 assert_eq!(opts.timeout, Duration::from_secs(5));
653 }
654
655 #[tokio::test]
656 async fn test_server_client_integration() {
657 let server = BidirectionalServer::bind("127.0.0.1:0").await.unwrap();
659 let addr = server.local_addr().unwrap();
660 let port = addr.port();
661
662 let server_running = server.running.clone();
664 let server_handle = tokio::spawn(async move {
665 let _ = server.run().await;
666 });
667
668 tokio::time::sleep(Duration::from_millis(50)).await;
670
671 let options = BidirectionalOptions {
673 port,
674 probe_count: 5,
675 interval: Duration::from_millis(10),
676 timeout: Duration::from_secs(2),
677 };
678
679 let result = probe_bidirectional("127.0.0.1", &options).await.unwrap();
680
681 assert_eq!(result.target, "127.0.0.1");
682 assert_eq!(result.port, port);
683 assert_eq!(result.probe_count, 5);
684 assert!(result.forward.received > 0);
685 assert!(result.reverse.received > 0);
686 assert!(result.round_trip.received > 0);
687
688 assert!(result.round_trip.mean_ms < 50.0); server_running.store(false, Ordering::Relaxed);
696 let _ = server_handle.await;
697 }
698
699 #[tokio::test]
700 async fn test_server_handles_multiple_probes() {
701 let server = BidirectionalServer::bind("127.0.0.1:0").await.unwrap();
702 let addr = server.local_addr().unwrap();
703 let port = addr.port();
704 let probes_counter = server.probes_handled.clone();
705 let server_running = server.running.clone();
706
707 let server_handle = tokio::spawn(async move {
708 let _ = server.run().await;
709 });
710
711 tokio::time::sleep(Duration::from_millis(50)).await;
712
713 let options = BidirectionalOptions {
714 port,
715 probe_count: 20,
716 interval: Duration::from_millis(5),
717 timeout: Duration::from_secs(2),
718 };
719
720 let result = probe_bidirectional("127.0.0.1", &options).await.unwrap();
721
722 assert!(result.round_trip.received >= 15); assert!(probes_counter.load(Ordering::Relaxed) >= 15);
724
725 server_running.store(false, Ordering::Relaxed);
726 let _ = server_handle.await;
727 }
728
729 #[tokio::test]
730 async fn test_connection_to_nonexistent_server() {
731 let options = BidirectionalOptions {
732 port: 59999, probe_count: 1,
734 timeout: Duration::from_millis(500),
735 ..Default::default()
736 };
737
738 let result = probe_bidirectional("127.0.0.1", &options).await;
739 assert!(result.is_err());
740 }
741
742 #[tokio::test]
743 async fn test_server_bind_address() {
744 let server = BidirectionalServer::bind("127.0.0.1:0").await.unwrap();
745 let addr = server.local_addr().unwrap();
746
747 assert_eq!(addr.ip(), std::net::IpAddr::V4(Ipv4Addr::LOCALHOST));
748 assert!(addr.port() > 0);
749 }
750
751 #[test]
752 fn test_message_type_conversion() {
753 assert_eq!(MessageType::try_from(1), Ok(MessageType::Probe));
754 assert_eq!(MessageType::try_from(2), Ok(MessageType::Echo));
755 assert_eq!(MessageType::try_from(3), Ok(MessageType::Ping));
756 assert_eq!(MessageType::try_from(4), Ok(MessageType::Pong));
757 assert!(MessageType::try_from(99).is_err());
758 }
759
760 #[test]
761 fn test_probe_sequence_numbers() {
762 for seq in [0, 1, 100, u32::MAX] {
763 let probe = ProbeMessage {
764 sequence: seq,
765 client_send_time: 12345,
766 };
767 let bytes = probe.to_bytes();
768 let parsed = ProbeMessage::from_bytes(&bytes).unwrap();
769 assert_eq!(parsed.sequence, seq);
770 }
771 }
772
773 #[test]
774 fn test_timestamp_preservation() {
775 let probe = ProbeMessage {
776 sequence: 1,
777 client_send_time: 1234567890123456,
778 };
779
780 let echo = EchoMessage::from_probe(&probe);
781 assert_eq!(echo.client_send_time, probe.client_send_time);
782 assert!(echo.server_recv_time > 0);
783 assert!(echo.server_send_time >= echo.server_recv_time);
784
785 let bytes = echo.to_bytes();
786 let parsed = EchoMessage::from_bytes(&bytes).unwrap();
787 assert_eq!(parsed.client_send_time, echo.client_send_time);
788 assert_eq!(parsed.server_recv_time, echo.server_recv_time);
789 assert_eq!(parsed.server_send_time, echo.server_send_time);
790 }
791}