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 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 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; }
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}