1use libc::{
2 c_void, recvfrom, sendto, sockaddr, socklen_t,
3 AF_INET, AF_INET6, IPPROTO_ICMP, IPPROTO_ICMPV6, SOCK_DGRAM, SOCK_RAW,
4};
5use std::net::{Ipv4Addr, Ipv6Addr};
6use std::os::unix::io::RawFd;
7
8use crate::constants::{
9 ICMP6_ECHO_REPLY, ICMP6_ECHO_REQUEST, ICMP_ECHO_REPLY, ICMP_ECHO_REQUEST, ICMP_HEADER_LEN,
10};
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
13pub enum SocketKind {
14 Raw,
15 Dgram,
16}
17
18pub fn open_raw_socket(is_ipv6: bool) -> Result<(RawFd, SocketKind, Option<u16>), String> {
19 let (domain, proto) = if is_ipv6 {
20 (AF_INET6, IPPROTO_ICMPV6)
21 } else {
22 (AF_INET, IPPROTO_ICMP)
23 };
24
25 let fd = unsafe { libc::socket(domain, SOCK_RAW, proto) };
26 if fd >= 0 {
27 set_nonblocking(fd);
28 return Ok((fd, SocketKind::Raw, None));
29 }
30
31 let fd = unsafe { libc::socket(domain, SOCK_DGRAM, proto) };
32 if fd >= 0 {
33 set_nonblocking(fd);
34 let assigned_id = dgram_bind_and_get_id(fd, is_ipv6);
35 return Ok((fd, SocketKind::Dgram, assigned_id));
36 }
37
38 Err(format!(
39 "Unable to open ICMP{} socket (no root and SOCK_DGRAM denied).\n\
40 Fix with one of:\n\
41 \x20 sudo setcap cap_net_raw+ep ./fping\n\
42 \x20 sudo sysctl -w net.ipv4.ping_group_range=\"0 2147483647\"",
43 if is_ipv6 { "v6" } else { "" }
44 ))
45}
46
47fn dgram_bind_and_get_id(fd: RawFd, is_ipv6: bool) -> Option<u16> {
48 unsafe {
49 if is_ipv6 {
50 let mut sa: libc::sockaddr_in6 = std::mem::zeroed();
51 sa.sin6_family = AF_INET6 as libc::sa_family_t;
52 let r = libc::bind(
53 fd,
54 &sa as *const _ as *const libc::sockaddr,
55 std::mem::size_of::<libc::sockaddr_in6>() as socklen_t,
56 );
57 if r < 0 { return None; }
58
59 let mut sa2: libc::sockaddr_in6 = std::mem::zeroed();
60 let mut len = std::mem::size_of::<libc::sockaddr_in6>() as socklen_t;
61 let r = libc::getsockname(fd, &mut sa2 as *mut _ as *mut libc::sockaddr, &mut len);
62 if r < 0 || sa2.sin6_port == 0 { return None; }
63 Some(u16::from_be(sa2.sin6_port))
64 } else {
65 let mut sa: libc::sockaddr_in = std::mem::zeroed();
66 sa.sin_family = AF_INET as libc::sa_family_t;
67 let r = libc::bind(
68 fd,
69 &sa as *const _ as *const libc::sockaddr,
70 std::mem::size_of::<libc::sockaddr_in>() as socklen_t,
71 );
72 if r < 0 { return None; }
73
74 let mut sa2: libc::sockaddr_in = std::mem::zeroed();
75 let mut len = std::mem::size_of::<libc::sockaddr_in>() as socklen_t;
76 let r = libc::getsockname(fd, &mut sa2 as *mut _ as *mut libc::sockaddr, &mut len);
77 if r < 0 || sa2.sin_port == 0 { return None; }
78 Some(u16::from_be(sa2.sin_port))
79 }
80 }
81}
82
83fn set_nonblocking(fd: RawFd) {
84 unsafe {
85 let flags = libc::fcntl(fd, libc::F_GETFL, 0);
86 libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
87 }
88}
89
90pub fn build_icmp_packet(id: u16, seq: u16, data_size: usize, is_ipv6: bool, _kind: SocketKind) -> Vec<u8> {
91 let total = ICMP_HEADER_LEN + data_size;
92 let mut pkt = vec![0u8; total];
93
94 pkt[0] = if is_ipv6 { ICMP6_ECHO_REQUEST } else { ICMP_ECHO_REQUEST };
95 pkt[1] = 0;
96 pkt[2] = 0;
97 pkt[3] = 0;
98 pkt[4] = (id >> 8) as u8;
99 pkt[5] = (id & 0xFF) as u8;
100 pkt[6] = (seq >> 8) as u8;
101 pkt[7] = (seq & 0xFF) as u8;
102
103 for (i, b) in pkt[8..].iter_mut().enumerate() {
104 *b = (i & 0xFF) as u8;
105 }
106
107 if !is_ipv6 {
108 let cksum = icmp_checksum(&pkt);
109 pkt[2] = (cksum >> 8) as u8;
110 pkt[3] = (cksum & 0xFF) as u8;
111 }
112
113 pkt
114}
115
116fn icmp_checksum(data: &[u8]) -> u16 {
117 let mut sum: u32 = 0;
118 let mut i = 0;
119 while i + 1 < data.len() {
120 sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
121 i += 2;
122 }
123 if i < data.len() {
124 sum += (data[i] as u32) << 8;
125 }
126 while sum >> 16 != 0 {
127 sum = (sum & 0xFFFF) + (sum >> 16);
128 }
129 !(sum as u16)
130}
131
132pub fn send_ping_v4(fd: RawFd, addr: &Ipv4Addr, pkt: &[u8]) -> bool {
133 unsafe {
134 let mut sa: libc::sockaddr_in = std::mem::zeroed();
135 sa.sin_family = AF_INET as libc::sa_family_t;
136 sa.sin_addr.s_addr = u32::from_ne_bytes(addr.octets());
137
138 let n = sendto(
139 fd,
140 pkt.as_ptr() as *const c_void,
141 pkt.len(),
142 0,
143 &sa as *const _ as *const sockaddr,
144 std::mem::size_of::<libc::sockaddr_in>() as socklen_t,
145 );
146 n == pkt.len() as isize
147 }
148}
149
150pub fn send_ping_v6(fd: RawFd, addr: &Ipv6Addr, pkt: &[u8]) -> bool {
151 unsafe {
152 let mut sa: libc::sockaddr_in6 = std::mem::zeroed();
153 sa.sin6_family = AF_INET6 as libc::sa_family_t;
154 sa.sin6_addr.s6_addr = addr.octets();
155
156 let n = sendto(
157 fd,
158 pkt.as_ptr() as *const c_void,
159 pkt.len(),
160 0,
161 &sa as *const _ as *const sockaddr,
162 std::mem::size_of::<libc::sockaddr_in6>() as socklen_t,
163 );
164 n == pkt.len() as isize
165 }
166}
167
168pub struct ReceivedPing {
169 pub seq: u16,
170 pub raw_len: usize,
171}
172
173pub fn recv_ping(fd: RawFd, buf: &mut [u8], is_ipv6: bool, kind: SocketKind, expected_id: Option<u16>) -> Option<ReceivedPing> {
174 let mut src: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
175 let mut src_len = std::mem::size_of::<libc::sockaddr_storage>() as socklen_t;
176
177 let n = unsafe {
178 recvfrom(
179 fd,
180 buf.as_mut_ptr() as *mut c_void,
181 buf.len(),
182 libc::MSG_DONTWAIT,
183 &mut src as *mut _ as *mut sockaddr,
184 &mut src_len,
185 )
186 };
187
188 if n < 0 {
189 return None;
190 }
191
192 let raw_len = n as usize;
193 let data = &buf[..raw_len];
194
195 let icmp = if !is_ipv6 && kind == SocketKind::Raw {
196 if data.len() < 20 + 8 { return None; }
197 let ihl = ((data[0] & 0x0F) as usize) * 4;
198 if data.len() < ihl + 8 { return None; }
199 &data[ihl..]
200 } else {
201 if data.len() < 8 { return None; }
202 data
203 };
204
205 let icmp_type = icmp[0];
206 let is_reply = icmp_type == ICMP_ECHO_REPLY || icmp_type == ICMP6_ECHO_REPLY;
207 if !is_reply {
208 return None;
209 }
210
211 let id = u16::from_be_bytes([icmp[4], icmp[5]]);
212
213 if let Some(eid) = expected_id {
214 if id != eid { return None; }
215 }
216
217 Some(ReceivedPing {
218 seq: u16::from_be_bytes([icmp[6], icmp[7]]),
219 raw_len,
220 })
221}