anti_ping/
pinger.rs

1//! Main pinger coordinator module
2//!
3//! This module provides a unified interface for all ping types (ICMP, UDP, TCP).
4//! It handles the creation and coordination of the appropriate ping implementation
5//! based on the configured mode.
6
7use crate::icmp::IcmpPinger;
8use crate::tcp::TcpPinger;
9use crate::udp::UdpPinger;
10use anti_common::{PingConfig, PingError, PingMode, PingReply, PingResult, PingStatistics};
11use std::time::{Duration, Instant};
12
13/// Main pinger that coordinates different ping implementations
14pub struct Pinger {
15    mode: PingMode,
16    config: PingConfig,
17    icmp_pinger: Option<IcmpPinger>,
18    udp_pinger: Option<UdpPinger>,
19    tcp_pinger: Option<TcpPinger>,
20}
21
22impl Pinger {
23    /// Create a new pinger with the specified configuration and mode
24    pub fn new(config: PingConfig, mode: PingMode) -> PingResult<Self> {
25        let mut pinger = Self {
26            mode,
27            config: config.clone(),
28            icmp_pinger: None,
29            udp_pinger: None,
30            tcp_pinger: None,
31        };
32
33        // Initialize the appropriate pinger based on mode
34        match mode {
35            PingMode::Icmp => {
36                pinger.icmp_pinger = Some(IcmpPinger::new(config)?);
37            }
38            PingMode::Udp => {
39                pinger.udp_pinger = Some(UdpPinger::new(config)?);
40            }
41            PingMode::Tcp => {
42                pinger.tcp_pinger = Some(TcpPinger::new(config));
43            }
44        }
45
46        Ok(pinger)
47    }
48
49    /// Create a new TCP pinger with a specific port
50    pub fn new_tcp_with_port(config: PingConfig, port: u16) -> PingResult<Self> {
51        let tcp_pinger = TcpPinger::new_with_port(config.clone(), port);
52
53        Ok(Self {
54            mode: PingMode::Tcp,
55            config,
56            icmp_pinger: None,
57            udp_pinger: None,
58            tcp_pinger: Some(tcp_pinger),
59        })
60    }
61
62    /// Create a new UDP pinger with a specific port
63    pub fn new_udp_with_port(config: PingConfig, port: u16) -> PingResult<Self> {
64        let udp_pinger = UdpPinger::new_with_port(config.clone(), port)?;
65
66        Ok(Self {
67            mode: PingMode::Udp,
68            config,
69            icmp_pinger: None,
70            udp_pinger: Some(udp_pinger),
71            tcp_pinger: None,
72        })
73    }
74
75    /// Send a single ping and return the result
76    pub fn ping_once(&self) -> PingResult<PingReply> {
77        self.ping_sequence(1)
78    }
79
80    /// Send a ping with a specific sequence number
81    pub fn ping_sequence(&self, sequence: u16) -> PingResult<PingReply> {
82        match self.mode {
83            PingMode::Icmp => {
84                if let Some(ref pinger) = self.icmp_pinger {
85                    pinger.ping(sequence)
86                } else {
87                    Err(PingError::Configuration {
88                        message: "ICMP pinger not initialized".to_string(),
89                    })
90                }
91            }
92            PingMode::Udp => {
93                if let Some(ref pinger) = self.udp_pinger {
94                    pinger.ping(sequence)
95                } else {
96                    Err(PingError::Configuration {
97                        message: "UDP pinger not initialized".to_string(),
98                    })
99                }
100            }
101            PingMode::Tcp => {
102                if let Some(ref pinger) = self.tcp_pinger {
103                    pinger.ping(sequence)
104                } else {
105                    Err(PingError::Configuration {
106                        message: "TCP pinger not initialized".to_string(),
107                    })
108                }
109            }
110        }
111    }
112
113    /// Send multiple pings according to the configuration
114    pub fn ping_all(&self) -> PingResult<PingStatistics> {
115        let mut stats = PingStatistics::new();
116        let mut rtts = Vec::new();
117        let mut sequence = 1u16;
118
119        println!("PING {} using {} mode", self.config.target, self.mode);
120        println!(
121            "Sending {} packets with {}ms interval",
122            self.config.count,
123            self.config.interval.as_millis()
124        );
125
126        for i in 0..self.config.count {
127            stats.add_transmitted();
128
129            match self.ping_sequence(sequence) {
130                Ok(reply) => {
131                    stats.add_reply(&reply);
132                    rtts.push(reply.rtt);
133
134                    println!(
135                        "Reply from {}: seq={} time={:.2}ms bytes={}",
136                        reply.from,
137                        reply.sequence,
138                        reply.rtt.as_secs_f64() * 1000.0,
139                        reply.bytes_received
140                    );
141                }
142                Err(e) => match e {
143                    PingError::Timeout { .. } => {
144                        println!("Request timeout for seq={}", sequence);
145                    }
146                    _ => {
147                        println!("Error for seq={}: {}", sequence, e);
148                    }
149                },
150            }
151
152            sequence = sequence.wrapping_add(1);
153
154            // Sleep between pings (except for the last one)
155            if i < self.config.count - 1 {
156                std::thread::sleep(self.config.interval);
157            }
158        }
159
160        stats.finalize(&rtts);
161        Ok(stats)
162    }
163
164    /// Send pings with a custom callback for each result
165    pub fn ping_with_callback<F>(&self, mut callback: F) -> PingResult<PingStatistics>
166    where
167        F: FnMut(u16, &PingResult<PingReply>),
168    {
169        let mut stats = PingStatistics::new();
170        let mut rtts = Vec::new();
171        let mut sequence = 1u16;
172
173        for _i in 0..self.config.count {
174            stats.add_transmitted();
175
176            let start_time = Instant::now();
177            let result = self.ping_sequence(sequence);
178
179            // Call the callback with the result
180            callback(sequence, &result);
181
182            match result {
183                Ok(reply) => {
184                    stats.add_reply(&reply);
185                    rtts.push(reply.rtt);
186                }
187                Err(_) => {
188                    // Error already handled by callback
189                }
190            }
191
192            sequence = sequence.wrapping_add(1);
193
194            // Ensure we don't send too quickly
195            let elapsed = start_time.elapsed();
196            if elapsed < self.config.interval {
197                std::thread::sleep(self.config.interval - elapsed);
198            }
199        }
200
201        stats.finalize(&rtts);
202        Ok(stats)
203    }
204
205    /// Get the ping mode
206    pub fn mode(&self) -> PingMode {
207        self.mode
208    }
209
210    /// Get the configuration
211    pub fn config(&self) -> &PingConfig {
212        &self.config
213    }
214
215    /// Check if the pinger is properly initialized
216    pub fn is_initialized(&self) -> bool {
217        match self.mode {
218            PingMode::Icmp => self.icmp_pinger.is_some(),
219            PingMode::Udp => self.udp_pinger.is_some(),
220            PingMode::Tcp => self.tcp_pinger.is_some(),
221        }
222    }
223
224    /// Get information about the pinger setup
225    pub fn info(&self) -> String {
226        let mode_info = match self.mode {
227            PingMode::Icmp => {
228                if let Some(ref pinger) = self.icmp_pinger {
229                    format!(
230                        "ICMP (socket type: {})",
231                        if pinger.is_raw() { "raw" } else { "dgram" }
232                    )
233                } else {
234                    "ICMP (not initialized)".to_string()
235                }
236            }
237            PingMode::Udp => {
238                if let Some(ref pinger) = self.udp_pinger {
239                    format!("UDP (base port: {})", pinger.base_port())
240                } else {
241                    "UDP (not initialized)".to_string()
242                }
243            }
244            PingMode::Tcp => {
245                if let Some(ref pinger) = self.tcp_pinger {
246                    format!("TCP (port: {})", pinger.target_port())
247                } else {
248                    "TCP (not initialized)".to_string()
249                }
250            }
251        };
252
253        format!(
254            "Pinger: {} -> {} ({})",
255            self.config.target,
256            mode_info,
257            if self.is_initialized() {
258                "ready"
259            } else {
260                "not ready"
261            }
262        )
263    }
264}
265
266/// Builder pattern for creating pingers with custom configurations
267pub struct PingerBuilder {
268    config: PingConfig,
269}
270
271impl PingerBuilder {
272    /// Create a new pinger builder
273    pub fn new() -> Self {
274        Self {
275            config: PingConfig::default(),
276        }
277    }
278
279    /// Set the target IP address
280    pub fn target(mut self, target: std::net::Ipv4Addr) -> Self {
281        self.config.target = target;
282        self
283    }
284
285    /// Set the number of pings to send
286    pub fn count(mut self, count: u16) -> Self {
287        self.config.count = count;
288        self
289    }
290
291    /// Set the timeout for each ping
292    pub fn timeout(mut self, timeout: Duration) -> Self {
293        self.config.timeout = timeout;
294        self
295    }
296
297    /// Set the interval between pings
298    pub fn interval(mut self, interval: Duration) -> Self {
299        self.config.interval = interval;
300        self
301    }
302
303    /// Set the packet size
304    pub fn packet_size(mut self, size: usize) -> Self {
305        self.config.packet_size = size;
306        self
307    }
308
309    /// Set a custom identifier
310    pub fn identifier(mut self, id: u16) -> Self {
311        self.config.identifier = Some(id);
312        self
313    }
314
315    /// Build an ICMP pinger
316    pub fn build_icmp(self) -> PingResult<Pinger> {
317        Pinger::new(self.config, PingMode::Icmp)
318    }
319
320    /// Build a UDP pinger
321    pub fn build_udp(self) -> PingResult<Pinger> {
322        Pinger::new(self.config, PingMode::Udp)
323    }
324
325    /// Build a UDP pinger with custom port
326    pub fn build_udp_with_port(self, port: u16) -> PingResult<Pinger> {
327        Pinger::new_udp_with_port(self.config, port)
328    }
329
330    /// Build a TCP pinger
331    pub fn build_tcp(self) -> PingResult<Pinger> {
332        Pinger::new(self.config, PingMode::Tcp)
333    }
334
335    /// Build a TCP pinger with custom port
336    pub fn build_tcp_with_port(self, port: u16) -> PingResult<Pinger> {
337        Pinger::new_tcp_with_port(self.config, port)
338    }
339}
340
341impl Default for PingerBuilder {
342    fn default() -> Self {
343        Self::new()
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    use std::net::Ipv4Addr;
351
352    #[test]
353    fn test_pinger_builder() {
354        let builder = PingerBuilder::new()
355            .target(Ipv4Addr::new(8, 8, 8, 8))
356            .count(3)
357            .timeout(Duration::from_secs(2))
358            .interval(Duration::from_millis(500))
359            .packet_size(128)
360            .identifier(12345);
361
362        assert_eq!(builder.config.target, Ipv4Addr::new(8, 8, 8, 8));
363        assert_eq!(builder.config.count, 3);
364        assert_eq!(builder.config.timeout, Duration::from_secs(2));
365        assert_eq!(builder.config.interval, Duration::from_millis(500));
366        assert_eq!(builder.config.packet_size, 128);
367        assert_eq!(builder.config.identifier, Some(12345));
368    }
369
370    #[test]
371    fn test_pinger_info() {
372        let config = PingConfig {
373            target: Ipv4Addr::new(127, 0, 0, 1),
374            ..Default::default()
375        };
376
377        // TCP pinger should always initialize successfully
378        let tcp_pinger = Pinger::new(config, PingMode::Tcp).unwrap();
379        assert!(tcp_pinger.is_initialized());
380        assert_eq!(tcp_pinger.mode(), PingMode::Tcp);
381
382        let info = tcp_pinger.info();
383        assert!(info.contains("TCP"));
384        assert!(info.contains("127.0.0.1"));
385    }
386
387    #[test]
388    fn test_pinger_config_access() {
389        let config = PingConfig {
390            target: Ipv4Addr::new(8, 8, 8, 8),
391            count: 5,
392            timeout: Duration::from_secs(3),
393            ..Default::default()
394        };
395
396        let pinger = Pinger::new(config, PingMode::Tcp).unwrap();
397
398        assert_eq!(pinger.config().target, Ipv4Addr::new(8, 8, 8, 8));
399        assert_eq!(pinger.config().count, 5);
400        assert_eq!(pinger.config().timeout, Duration::from_secs(3));
401    }
402}