1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! BSD async ICMP socket implementation
//!
//! This implementation works for FreeBSD, OpenBSD, NetBSD, and DragonFly BSD.
//! All BSDs use raw ICMP sockets which require root privileges.
//! Unlike macOS, other BSDs do not support DGRAM ICMP sockets.
//!
//! The BSD raw ICMP implementation is essentially the same across all BSD variants,
//! using the standard POSIX socket API with raw sockets.
use crate::probe::{ProbeInfo, ProbeResponse};
use crate::socket::icmp;
use crate::socket::traits::{ProbeMode, ProbeSocket};
use crate::traceroute::TracerouteError;
use crate::TimingConfig;
use socket2::{Domain, Protocol, Socket as Socket2, Type};
use std::future::Future;
use std::net::{IpAddr, SocketAddr};
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::oneshot;
/// BSD async ICMP socket implementation
pub struct BsdAsyncIcmpSocket {
mode: ProbeMode,
icmp_identifier: u16,
destination_reached: Arc<AtomicBool>,
pending_count: Arc<AtomicUsize>,
timing_config: TimingConfig,
}
impl BsdAsyncIcmpSocket {
/// Create a new BSD async ICMP socket
pub fn new() -> Result<Self, TracerouteError> {
Self::new_with_config(TimingConfig::default())
}
/// Create a new BSD async ICMP socket with custom timing configuration
pub fn new_with_config(timing_config: TimingConfig) -> Result<Self, TracerouteError> {
let icmp_identifier = std::process::id() as u16;
// Platform-specific adjustments can go here
#[cfg(target_os = "openbsd")]
{
// OpenBSD might have specific requirements or optimizations
// For example, checking for pledge/unveil compatibility
}
Ok(BsdAsyncIcmpSocket {
mode: ProbeMode::RawIcmp,
icmp_identifier,
destination_reached: Arc::new(AtomicBool::new(false)),
pending_count: Arc::new(AtomicUsize::new(0)),
timing_config,
})
}
/// Create ICMP echo request packet
fn create_echo_request(&self, sequence: u16) -> Vec<u8> {
let mut payload = vec![0u8; 16];
let tag = b"ftr-traceroute";
payload[..tag.len()].copy_from_slice(tag);
icmp::build_echo_request(self.icmp_identifier, sequence, &payload)
}
/// Parse ICMP response
fn parse_icmp_response(
&self,
data: &[u8],
from_addr: IpAddr,
sequence: u16,
) -> Option<(IpAddr, bool)> {
let icmp_data = icmp::ipv4_payload(data)?;
let hdr = icmp::parse_icmp_header(icmp_data)?;
match hdr.icmp_type {
icmp::ICMP_ECHO_REPLY => {
if let Some((id, seq)) = icmp::parse_echo_reply(icmp_data) {
if id == self.icmp_identifier && seq == sequence {
return Some((from_addr, true)); // is_destination = true
}
}
}
icmp::ICMP_TIME_EXCEEDED | icmp::ICMP_DEST_UNREACHABLE => {
const ICMP_ERROR_HEADER_LEN: usize = 8;
if icmp_data.len() >= ICMP_ERROR_HEADER_LEN {
let inner_data = &icmp_data[ICMP_ERROR_HEADER_LEN..];
if let Some((inner_hdr_len, _)) = icmp::parse_ipv4_header(inner_data) {
let inner_icmp_data = &inner_data[inner_hdr_len..];
if inner_icmp_data.len() >= 8 {
let inner_type = inner_icmp_data[0];
if inner_type == icmp::ICMP_ECHO_REQUEST {
let identifier =
u16::from_be_bytes([inner_icmp_data[4], inner_icmp_data[5]]);
let seq =
u16::from_be_bytes([inner_icmp_data[6], inner_icmp_data[7]]);
if identifier == self.icmp_identifier && seq == sequence {
return Some((from_addr, false));
}
}
}
}
}
}
_ => {}
}
None
}
}
impl ProbeSocket for BsdAsyncIcmpSocket {
fn mode(&self) -> ProbeMode {
self.mode
}
fn send_probe_and_recv(
&self,
dest: IpAddr,
probe: ProbeInfo,
) -> Pin<Box<dyn Future<Output = Result<ProbeResponse, TracerouteError>> + Send + '_>> {
Box::pin(async move {
// Increment pending count
self.pending_count.fetch_add(1, Ordering::Relaxed);
// Create raw ICMP socket
let socket =
Socket2::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4)).map_err(|e| {
TracerouteError::SocketError(format!("Failed to create raw ICMP socket: {e}"))
})?;
// Platform-specific socket options could be set here
#[cfg(target_os = "openbsd")]
{
// OpenBSD might need specific socket options
// e.g., different buffer sizes or security flags
}
// Set TTL
socket
.set_ttl_v4(probe.ttl as u32)
.map_err(|e| TracerouteError::SocketError(format!("Failed to set TTL: {e}")))?;
// Set non-blocking
socket.set_nonblocking(true).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set non-blocking: {e}"))
})?;
// Create ICMP echo request packet
let packet = self.create_echo_request(probe.sequence);
// Send packet
let dest_addr: SocketAddr = SocketAddr::new(dest, 0);
let sent_at = Instant::now();
socket.send_to(&packet, &dest_addr.into()).map_err(|e| {
TracerouteError::SocketError(format!("Failed to send ICMP packet: {e}"))
})?;
// Clone necessary data for the spawned task
let destination_reached = self.destination_reached.clone();
let pending_count = self.pending_count.clone();
let sequence = probe.sequence;
let ttl = probe.ttl;
let icmp_identifier = self.icmp_identifier;
let timeout = self.timing_config.socket_read_timeout;
// Create oneshot channel for response
let (tx, rx) = oneshot::channel();
// Spawn task to read responses
let socket = Arc::new(socket);
let socket_clone = socket.clone();
tokio::spawn(async move {
let start = Instant::now();
loop {
// Try to receive response
let mut buf = vec![std::mem::MaybeUninit::uninit(); 1500];
match socket_clone.recv_from(&mut buf) {
Ok((size, addr)) => {
if let Some(from_addr) = addr.as_socket_ipv4() {
let from_ip = IpAddr::V4(*from_addr.ip());
// Convert MaybeUninit buffer to initialized slice
let initialized_buf = unsafe {
std::slice::from_raw_parts(buf.as_ptr() as *const u8, size)
};
// Parse ICMP response
let parser = BsdAsyncIcmpSocket {
mode: ProbeMode::RawIcmp,
icmp_identifier,
destination_reached: Arc::new(AtomicBool::new(false)),
pending_count: Arc::new(AtomicUsize::new(0)),
timing_config: TimingConfig::default(),
};
if let Some((resp_addr, is_destination)) =
parser.parse_icmp_response(initialized_buf, from_ip, sequence)
{
let rtt = Instant::now().duration_since(sent_at);
// Update destination reached
if is_destination {
destination_reached.store(true, Ordering::Relaxed);
}
// Decrement pending count
pending_count.fetch_sub(1, Ordering::Relaxed);
let response = ProbeResponse {
from_addr: resp_addr,
sequence,
ttl,
rtt,
received_at: Instant::now(),
is_destination,
is_timeout: false,
};
let _ = tx.send(response);
break;
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// No data yet, continue
}
Err(_) => {
// Other error
pending_count.fetch_sub(1, Ordering::Relaxed);
break;
}
}
// Check timeout
if start.elapsed() >= timeout {
// Timeout
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: dest,
sequence,
ttl,
rtt: timeout,
received_at: Instant::now(),
is_destination: false,
is_timeout: true,
});
break;
}
// Brief yield before retrying
tokio::time::sleep(Duration::from_millis(1)).await;
}
});
// Wait for response
match rx.await {
Ok(response) => Ok(response),
Err(_) => {
// Channel closed without response
self.pending_count.fetch_sub(1, Ordering::Relaxed);
Err(TracerouteError::SocketError(
"Failed to receive response".to_string(),
))
}
}
})
}
fn destination_reached(&self) -> bool {
self.destination_reached.load(Ordering::Relaxed)
}
fn pending_count(&self) -> usize {
self.pending_count.load(Ordering::Relaxed)
}
}
// Safety: The socket is protected by Arc<AtomicBool> and Arc<AtomicUsize>
unsafe impl Send for BsdAsyncIcmpSocket {}
unsafe impl Sync for BsdAsyncIcmpSocket {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_socket_initialization() {
// BsdAsyncIcmpSocket::new() just initializes the struct, doesn't create actual socket
// The actual socket is created when sending probes
let result = BsdAsyncIcmpSocket::new();
assert!(
result.is_ok(),
"Socket struct initialization should always succeed"
);
let socket = result.unwrap();
assert_eq!(socket.mode, ProbeMode::RawIcmp);
}
}