Skip to main content

fping/
pinger.rs

1use std::collections::HashMap;
2use std::net::IpAddr;
3use std::time::{Duration, Instant};
4
5use crate::args::Args;
6use crate::output::{
7  print_alive, print_unreachable,
8  print_global_stats, print_per_host_stats, print_recv, print_timeout,
9  max_host_len, GlobalStatsSummary, RecvLineOpts, TimeoutLineOpts,
10};
11use crate::socket::{build_icmp_packet, open_raw_socket, recv_ping, send_ping_v4, send_ping_v6, SocketKind};
12use crate::types::{HostEntry, PendingPing};
13
14pub fn run(args: Args, hosts_in: Vec<(String, IpAddr)>) {
15  let count = args.effective_count();
16  let loop_mode = args.r#loop;
17  let verbose_count = args.is_verbose_count();
18
19  let mut hosts: Vec<HostEntry> = hosts_in
20    .into_iter()
21    .map(|(name, addr)| {
22      let is_ipv6 = addr.is_ipv6();
23      let display = if args.addr { addr.to_string() } else { name.clone() };
24      let mut h = HostEntry::new(name, addr, is_ipv6, count.unwrap_or(0));
25      h.display = display;
26      h
27    })
28    .collect();
29
30  let has_v4 = hosts.iter().any(|h| !h.is_ipv6);
31  let has_v6 = hosts.iter().any(|h| h.is_ipv6);
32
33  let (fd4, kind4, dgram_id4) = if has_v4 {
34    let (fd, kind, kid) = open_raw_socket(false).unwrap_or_else(|e| {
35      eprintln!("fping: {}", e);
36      std::process::exit(3);
37    });
38    (Some(fd), kind, kid)
39  } else {
40    (None, SocketKind::Raw, None)
41  };
42
43  let (fd6, kind6, dgram_id6) = if has_v6 {
44    let (fd, kind, kid) = open_raw_socket(true).unwrap_or_else(|e| {
45      eprintln!("fping: {}", e);
46      std::process::exit(3);
47    });
48    (Some(fd), kind, kid)
49  } else {
50    (None, SocketKind::Raw, None)
51  };
52
53  let pid_id = (std::process::id() & 0xFFFF) as u16;
54  let my_id = dgram_id4.or(dgram_id6).unwrap_or(pid_id);
55
56  let interval = Duration::from_millis(args.interval);
57  let period   = Duration::from_millis(args.period);
58  let timeout  = Duration::from_millis(args.timeout);
59
60  for (i, h) in hosts.iter_mut().enumerate() {
61    h.next_send    = Instant::now() + interval * i as u32;
62    h.retries_left = args.retry;
63  }
64
65  // seq -> PendingPing
66  let mut seqmap: HashMap<u16, PendingPing> = HashMap::new();
67  let mut seq_counter: u16 = 0;
68  let mut recv_buf = vec![0u8; 4096];
69
70  let start = Instant::now();
71  let max_len = max_host_len(&hosts);
72
73  // Start main_loop
74  loop {
75    let now = Instant::now();
76
77    if hosts.iter().all(|h| h.done) && seqmap.is_empty() {
78      break;
79    }
80
81    for idx in 0..hosts.len() {
82      if hosts[idx].done || now < hosts[idx].next_send {
83        continue;
84      }
85
86      let ping_idx = hosts[idx].current_ping_index;
87      let seq = seq_counter;
88      seq_counter = seq_counter.wrapping_add(1);
89
90      let is_ipv6  = hosts[idx].is_ipv6;
91      let kind = if is_ipv6 { kind6 } else { kind4 };
92      let pkt = build_icmp_packet(my_id, seq, args.size, is_ipv6, kind);
93
94      let sent = match hosts[idx].addr {
95        IpAddr::V4(ref a) => fd4.map(|fd| send_ping_v4(fd, a, &pkt)).unwrap_or(false),
96        IpAddr::V6(ref a) => fd6.map(|fd| send_ping_v6(fd, a, &pkt)).unwrap_or(false),
97      };
98
99      if sent {
100        let sent_at = Instant::now();
101        seqmap.insert(seq, PendingPing { host_index: idx, ping_index: ping_idx, sent_at });
102        hosts[idx].num_sent  += 1;
103        hosts[idx].last_send  = Some(sent_at);
104
105        hosts[idx].next_send = if count.is_some() || loop_mode {
106          sent_at + period
107        } else {
108          let backoff = args.backoff.powi(hosts[idx].num_sent as i32 - 1);
109          sent_at + timeout.mul_f64(backoff)
110        };
111
112        hosts[idx].current_ping_index += 1;
113
114        if count.map(|c| hosts[idx].current_ping_index >= c).unwrap_or(false) {
115          hosts[idx].next_send = now + Duration::from_secs(86400);
116        }
117
118        let is_default_mode = count.is_none() && !loop_mode;
119        if is_default_mode && hosts[idx].current_ping_index > args.retry {
120          hosts[idx].next_send = now + Duration::from_secs(86400);
121        }
122      }
123    }
124
125    for (fd_opt, is_v6, kind) in &[(fd4, false, kind4), (fd6, true, kind6)] {
126      let fd = match fd_opt { Some(f) => *f, None => continue };
127      loop {
128        let received = match recv_ping(fd, &mut recv_buf, *is_v6, *kind) {
129          Some(r) => r,
130          None => break,
131        };
132
133        if received.id != my_id { continue; }
134
135        if let Some(pending) = seqmap.remove(&received.seq) {
136          let rtt = Instant::now().duration_since(pending.sent_at);
137          let hi  = pending.host_index;
138          let first_reply = hosts[hi].num_recv == 0;
139          hosts[hi].record_reply(rtt, pending.ping_index);
140
141          let is_default_mode = count.is_none() && !loop_mode;
142          if is_default_mode && first_reply {
143            hosts[hi].done = true;
144          }
145
146          if !args.quiet && !args.unreach {
147            if is_default_mode {
148              if first_reply {
149                print_alive(&hosts[hi], args.timestamp, args.json);
150              }
151            } else {
152              print_recv(RecvLineOpts {
153                host: &hosts[hi],
154                ping_index: pending.ping_index,
155                rtt,
156                raw_len: received.raw_len,
157                max_len,
158                timestamp: args.timestamp,
159                json: args.json,
160                verbose_count,
161              });
162            }
163          }
164        }
165      }
166    }
167
168    let now2 = Instant::now();
169    let timed_out: Vec<u16> = seqmap
170      .iter()
171      .filter(|(_, p)| now2.duration_since(p.sent_at) > timeout)
172      .map(|(&seq, _)| seq)
173      .collect();
174
175    for seq in timed_out {
176      if let Some(pending) = seqmap.remove(&seq) {
177        let is_default_mode = count.is_none() && !loop_mode;
178        if !is_default_mode && !args.quiet && !args.alive {
179          print_timeout(TimeoutLineOpts {
180            host: &hosts[pending.host_index],
181            ping_index: pending.ping_index,
182            max_len,
183            timestamp: args.timestamp,
184            json: args.json,
185          });
186        }
187      }
188    }
189
190    let now3 = Instant::now();
191    for h in hosts.iter_mut() {
192      if h.done { continue; }
193
194      let last_expired = h.last_send
195        .map(|s| now3.duration_since(s) > timeout)
196        .unwrap_or(false);
197
198      if let Some(c) = count {
199        if h.current_ping_index >= c && last_expired {
200          h.done = true;
201        }
202      } else if !loop_mode {
203        if h.num_recv > 0 {
204          h.done = true;
205        } else if h.num_sent > args.retry && last_expired {
206          h.done = true;
207        }
208      }
209    }
210
211    std::thread::sleep(Duration::from_millis(1));
212  }
213
214  let max_len = max_host_len(&hosts);
215  let is_default_mode = count.is_none() && !loop_mode;
216
217  if is_default_mode {
218    if !args.quiet {
219      for h in &hosts {
220        if h.num_recv == 0 {
221          print_unreachable(h, args.timestamp, args.json);
222        }
223      }
224    }
225  } else {
226    for h in &hosts {
227      if args.alive  && h.num_recv > 0 { println!("{}", h.display); }
228      if args.unreach && h.num_recv == 0 { println!("{}", h.display); }
229    }
230  }
231
232  if count.is_some() && !args.alive && !args.unreach {
233    for h in &hosts {
234      print_per_host_stats(h, max_len, args.json, verbose_count || args.report_all_rtts);
235    }
236  }
237
238  if args.stats {
239    let all_rtts: Vec<Duration> = hosts.iter()
240      .flat_map(|h| h.resp_times.iter().filter_map(|r| *r))
241      .collect();
242
243    let g_sum: Duration = all_rtts.iter().sum();
244    let g_count = all_rtts.len();
245
246    print_global_stats(&GlobalStatsSummary {
247      num_hosts:      hosts.len(),
248      num_alive:      hosts.iter().filter(|h| h.num_recv > 0).count(),
249      num_unreachable: hosts.iter().filter(|h| h.num_recv == 0).count(),
250      total_sent:     hosts.iter().map(|h| h.num_sent).sum(),
251      total_recv:     hosts.iter().map(|h| h.num_recv).sum(),
252      min_rtt:        all_rtts.iter().min().copied(),
253      avg_rtt:        (g_count > 0).then(|| g_sum / g_count as u32),
254      max_rtt:        all_rtts.iter().max().copied(),
255      elapsed:        start.elapsed(),
256    }, args.json);
257  }
258
259  let exit_ok = if let Some(min_reach) = args.reachable {
260    hosts.iter().filter(|h| h.num_recv > 0).count() as u32 >= min_reach
261  } else {
262    hosts.iter().all(|h| h.num_recv > 0)
263  };
264
265  if !exit_ok {
266    std::process::exit(1);
267  }
268}