Skip to main content

fping/
pinger.rs

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