1use 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
13pub 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 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 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 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 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 pub fn ping_once(&self) -> PingResult<PingReply> {
77 self.ping_sequence(1)
78 }
79
80 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 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 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 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 callback(sequence, &result);
181
182 match result {
183 Ok(reply) => {
184 stats.add_reply(&reply);
185 rtts.push(reply.rtt);
186 }
187 Err(_) => {
188 }
190 }
191
192 sequence = sequence.wrapping_add(1);
193
194 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 pub fn mode(&self) -> PingMode {
207 self.mode
208 }
209
210 pub fn config(&self) -> &PingConfig {
212 &self.config
213 }
214
215 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 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
266pub struct PingerBuilder {
268 config: PingConfig,
269}
270
271impl PingerBuilder {
272 pub fn new() -> Self {
274 Self {
275 config: PingConfig::default(),
276 }
277 }
278
279 pub fn target(mut self, target: std::net::Ipv4Addr) -> Self {
281 self.config.target = target;
282 self
283 }
284
285 pub fn count(mut self, count: u16) -> Self {
287 self.config.count = count;
288 self
289 }
290
291 pub fn timeout(mut self, timeout: Duration) -> Self {
293 self.config.timeout = timeout;
294 self
295 }
296
297 pub fn interval(mut self, interval: Duration) -> Self {
299 self.config.interval = interval;
300 self
301 }
302
303 pub fn packet_size(mut self, size: usize) -> Self {
305 self.config.packet_size = size;
306 self
307 }
308
309 pub fn identifier(mut self, id: u16) -> Self {
311 self.config.identifier = Some(id);
312 self
313 }
314
315 pub fn build_icmp(self) -> PingResult<Pinger> {
317 Pinger::new(self.config, PingMode::Icmp)
318 }
319
320 pub fn build_udp(self) -> PingResult<Pinger> {
322 Pinger::new(self.config, PingMode::Udp)
323 }
324
325 pub fn build_udp_with_port(self, port: u16) -> PingResult<Pinger> {
327 Pinger::new_udp_with_port(self.config, port)
328 }
329
330 pub fn build_tcp(self) -> PingResult<Pinger> {
332 Pinger::new(self.config, PingMode::Tcp)
333 }
334
335 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 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}