Skip to main content

fping/
socket.rs

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}