use super::traits::{ProbeMode, ProbeSocket};
use crate::probe::{ProbeInfo, ProbeResponse};
use crate::socket::icmpv6;
use crate::traceroute::TracerouteError;
use socket2::{Domain, Protocol, SockAddr, Socket as Socket2, Type};
use std::future::Future;
use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6};
use std::os::unix::io::AsRawFd;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use tokio::net::UdpSocket;
use tokio::sync::oneshot;
const IPV6_RECVERR: libc::c_int = 25;
const SO_EE_ORIGIN_ICMP6: u8 = 3;
const ICMPV6_FILTER: libc::c_int = 1;
const ICMPV6_UNREACH_PORT: u8 = 4;
const UDP_DEST_PORT: u16 = 33434;
const MAX_RETRIES: u32 = 1000;
const POLL_INTERVAL: Duration = Duration::from_millis(1);
const RECV_BUFFER_SIZE: usize = 1500;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct SockExtendedErr {
ee_errno: u32,
ee_origin: u8,
ee_type: u8,
ee_code: u8,
ee_pad: u8,
ee_info: u32,
ee_data: u32,
}
const SOCK_EXTENDED_ERR_SIZE: usize = std::mem::size_of::<SockExtendedErr>();
const SIN6_ADDR_OFFSET: usize = 8;
const SIN6_SCOPE_ID_OFFSET: usize = 24;
const SOCKADDR_IN6_SIZE: usize = std::mem::size_of::<libc::sockaddr_in6>();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RecvErrV6 {
ee_errno: u32,
ee_origin: u8,
ee_type: u8,
ee_code: u8,
offender: Option<(Ipv6Addr, u32)>,
}
fn parse_recverr_payload(payload: &[u8]) -> Option<RecvErrV6> {
if payload.len() < SOCK_EXTENDED_ERR_SIZE {
return None;
}
let ee_errno = u32::from_ne_bytes(payload[0..4].try_into().ok()?);
let ee_origin = payload[4];
let ee_type = payload[5];
let ee_code = payload[6];
let offender_bytes = &payload[SOCK_EXTENDED_ERR_SIZE..];
let offender = if offender_bytes.len() >= SOCKADDR_IN6_SIZE {
let family = u16::from_ne_bytes(offender_bytes[0..2].try_into().ok()?);
if family == libc::AF_INET6 as u16 {
let addr = Ipv6Addr::from(
<[u8; 16]>::try_from(&offender_bytes[SIN6_ADDR_OFFSET..SIN6_ADDR_OFFSET + 16])
.ok()?,
);
let scope_id = u32::from_ne_bytes(
offender_bytes[SIN6_SCOPE_ID_OFFSET..SIN6_SCOPE_ID_OFFSET + 4]
.try_into()
.ok()?,
);
Some((addr, scope_id))
} else {
None
}
} else {
None
};
Some(RecvErrV6 {
ee_errno,
ee_origin,
ee_type,
ee_code,
offender,
})
}
fn is_v6_destination_error(ee_type: u8, ee_code: u8) -> bool {
ee_type == icmpv6::ICMPV6_DEST_UNREACHABLE && ee_code == ICMPV6_UNREACH_PORT
}
enum ErrqueueCheck {
Found(RecvErrV6),
NoData,
Error,
}
fn check_errqueue_v6(fd: i32) -> ErrqueueCheck {
let mut buf = [0u8; 512];
let mut control_buf = [0u8; 512];
let mut from_addr: libc::sockaddr_in6 = unsafe { std::mem::zeroed() };
let mut iovec = libc::iovec {
iov_base: buf.as_mut_ptr().cast(),
iov_len: buf.len(),
};
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
msg.msg_name = (&raw mut from_addr).cast();
msg.msg_namelen = SOCKADDR_IN6_SIZE as libc::socklen_t;
msg.msg_iov = &raw mut iovec;
msg.msg_iovlen = 1;
msg.msg_control = control_buf.as_mut_ptr().cast();
msg.msg_controllen = control_buf.len() as _; msg.msg_flags = 0;
let ret = unsafe { libc::recvmsg(fd, &raw mut msg, libc::MSG_ERRQUEUE | libc::MSG_DONTWAIT) };
if ret >= 0 {
let mut cmsg: *const libc::cmsghdr = unsafe { libc::CMSG_FIRSTHDR(&msg) };
while !cmsg.is_null() {
let cmsg_hdr = unsafe { std::ptr::read_unaligned(cmsg) };
if cmsg_hdr.cmsg_level == libc::IPPROTO_IPV6 && cmsg_hdr.cmsg_type == IPV6_RECVERR {
let data_ptr = unsafe { libc::CMSG_DATA(cmsg) };
let header_span = data_ptr as usize - cmsg as usize;
let payload_len = (cmsg_hdr.cmsg_len as usize).saturating_sub(header_span);
let payload = unsafe { std::slice::from_raw_parts(data_ptr, payload_len) };
if let Some(err) = parse_recverr_payload(payload) {
return ErrqueueCheck::Found(err);
}
}
cmsg = unsafe { libc::CMSG_NXTHDR(&msg, cmsg) };
}
ErrqueueCheck::NoData
} else {
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EAGAIN) {
ErrqueueCheck::NoData
} else {
ErrqueueCheck::Error
}
}
}
fn set_recverr_v6(fd: i32) -> std::io::Result<()> {
let enable: libc::c_int = 1;
let ret = unsafe {
libc::setsockopt(
fd,
libc::IPPROTO_IPV6,
IPV6_RECVERR,
(&raw const enable).cast(),
std::mem::size_of::<libc::c_int>() as libc::socklen_t,
)
};
if ret == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[repr(C)]
struct Icmp6FilterLinux {
icmp6_filt: [u32; 8],
}
impl Icmp6FilterLinux {
fn block_all() -> Self {
Icmp6FilterLinux {
icmp6_filt: [u32::MAX; 8],
}
}
fn pass(mut self, ty: u8) -> Self {
self.icmp6_filt[(ty >> 5) as usize] &= !(1u32 << (ty & 31));
self
}
#[cfg(test)]
fn passes(&self, ty: u8) -> bool {
self.icmp6_filt[(ty >> 5) as usize] & (1u32 << (ty & 31)) == 0
}
}
fn apply_icmp6_filter_raw(fd: i32) {
let filter = Icmp6FilterLinux::block_all()
.pass(icmpv6::ICMPV6_DEST_UNREACHABLE)
.pass(icmpv6::ICMPV6_TIME_EXCEEDED)
.pass(icmpv6::ICMPV6_ECHO_REPLY);
let _ = unsafe {
libc::setsockopt(
fd,
libc::IPPROTO_ICMPV6,
ICMPV6_FILTER,
(&raw const filter).cast(),
std::mem::size_of::<Icmp6FilterLinux>() as libc::socklen_t,
)
};
}
fn require_v6(dest: IpAddr) -> Result<Ipv6Addr, TracerouteError> {
match dest {
IpAddr::V6(addr) => Ok(addr),
IpAddr::V4(_) => Err(TracerouteError::SocketError(
"IPv6 probe socket cannot probe an IPv4 destination".to_string(),
)),
}
}
pub struct LinuxAsyncUdpV6Socket {
mode: ProbeMode,
destination_reached: Arc<AtomicBool>,
pending_count: Arc<AtomicUsize>,
dest_port: u16,
}
impl LinuxAsyncUdpV6Socket {
pub fn new_with_config(_timing_config: crate::TimingConfig) -> Result<Self, TracerouteError> {
let probe = Socket2::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP)).map_err(|e| {
TracerouteError::SocketError(format!("Failed to create UDP6 socket: {e}"))
})?;
set_recverr_v6(probe.as_raw_fd()).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set IPV6_RECVERR: {e}"))
})?;
Ok(LinuxAsyncUdpV6Socket {
mode: ProbeMode::UdpWithRecverr,
destination_reached: Arc::new(AtomicBool::new(false)),
pending_count: Arc::new(AtomicUsize::new(0)),
dest_port: UDP_DEST_PORT,
})
}
}
impl ProbeSocket for LinuxAsyncUdpV6Socket {
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 {
require_v6(dest)?;
self.pending_count.fetch_add(1, Ordering::Relaxed);
let socket =
Socket2::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP)).map_err(|e| {
self.pending_count.fetch_sub(1, Ordering::Relaxed);
TracerouteError::SocketError(format!("Failed to create UDP6 socket: {e}"))
})?;
let setup = (|| -> Result<(), TracerouteError> {
socket
.bind(&SockAddr::from(SocketAddrV6::new(
Ipv6Addr::UNSPECIFIED,
0,
0,
0,
)))
.map_err(|e| {
TracerouteError::SocketError(format!("Failed to bind UDP6 socket: {e}"))
})?;
socket.set_unicast_hops_v6(probe.ttl as u32).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set IPV6_UNICAST_HOPS: {e}"))
})?;
set_recverr_v6(socket.as_raw_fd()).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set IPV6_RECVERR: {e}"))
})?;
socket.set_nonblocking(true).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set non-blocking: {e}"))
})?;
Ok(())
})();
if let Err(e) = setup {
self.pending_count.fetch_sub(1, Ordering::Relaxed);
return Err(e);
}
let async_socket = UdpSocket::from_std(socket.into()).map_err(|e| {
self.pending_count.fetch_sub(1, Ordering::Relaxed);
TracerouteError::SocketError(format!("Failed to convert to async socket: {e}"))
})?;
let target_addr = SocketAddr::new(dest, self.dest_port);
async_socket.connect(target_addr).await.map_err(|e| {
self.pending_count.fetch_sub(1, Ordering::Relaxed);
TracerouteError::SocketError(format!("Failed to connect to destination: {e}"))
})?;
let identifier = std::process::id() as u16;
let mut payload = Vec::with_capacity(32);
payload.extend_from_slice(&identifier.to_be_bytes());
payload.extend_from_slice(&probe.sequence.to_be_bytes());
payload.extend_from_slice(b"ftr-traceroute-probe-padding");
let sent_at = Instant::now();
async_socket.send(&payload).await.map_err(|e| {
self.pending_count.fetch_sub(1, Ordering::Relaxed);
TracerouteError::SocketError(format!("Failed to send UDP6 probe: {e}"))
})?;
let destination_reached = self.destination_reached.clone();
let pending_count = self.pending_count.clone();
let sequence = probe.sequence;
let ttl = probe.ttl;
let (tx, rx) = oneshot::channel();
let fd = async_socket.as_raw_fd();
tokio::spawn(async move {
let _socket_guard = async_socket;
let mut retry_count = 0;
loop {
match check_errqueue_v6(fd) {
ErrqueueCheck::Found(err) if err.ee_origin == SO_EE_ORIGIN_ICMP6 => {
if let Some((offender, _scope_id)) = err.offender {
let rtt = Instant::now().duration_since(sent_at);
let is_destination =
is_v6_destination_error(err.ee_type, err.ee_code);
if is_destination {
destination_reached.store(true, Ordering::Relaxed);
}
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: IpAddr::V6(offender),
sequence,
ttl,
rtt,
received_at: Instant::now(),
is_destination,
is_timeout: false,
});
break;
}
}
ErrqueueCheck::Found(_) => {
}
ErrqueueCheck::Error => {
pending_count.fetch_sub(1, Ordering::Relaxed);
break;
}
ErrqueueCheck::NoData => {}
}
retry_count += 1;
if retry_count >= MAX_RETRIES {
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: dest,
sequence,
ttl,
rtt: Duration::from_secs(1),
received_at: Instant::now(),
is_destination: false,
is_timeout: true,
});
break;
}
tokio::time::sleep(POLL_INTERVAL).await;
}
});
match rx.await {
Ok(response) => Ok(response),
Err(_) => {
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)
}
}
pub struct LinuxAsyncPingV6Socket {
destination_reached: Arc<AtomicBool>,
pending_count: Arc<AtomicUsize>,
}
impl LinuxAsyncPingV6Socket {
pub fn new_with_config(_timing_config: crate::TimingConfig) -> Result<Self, TracerouteError> {
Socket2::new(Domain::IPV6, Type::DGRAM, Some(Protocol::ICMPV6)).map_err(|e| {
if e.kind() == std::io::ErrorKind::PermissionDenied {
TracerouteError::InsufficientPermissions {
required: "net.ipv4.ping_group_range covering this gid".to_string(),
suggestion: "sudo sysctl -w net.ipv4.ping_group_range=\"0 2147483647\""
.to_string(),
}
} else {
TracerouteError::SocketError(format!("Failed to create ICMPv6 ping socket: {e}"))
}
})?;
Ok(LinuxAsyncPingV6Socket {
destination_reached: Arc::new(AtomicBool::new(false)),
pending_count: Arc::new(AtomicUsize::new(0)),
})
}
}
impl ProbeSocket for LinuxAsyncPingV6Socket {
fn mode(&self) -> ProbeMode {
ProbeMode::DgramIcmpv6
}
fn send_probe_and_recv(
&self,
dest: IpAddr,
probe: ProbeInfo,
) -> Pin<Box<dyn Future<Output = Result<ProbeResponse, TracerouteError>> + Send + '_>> {
Box::pin(async move {
let dest_v6 = require_v6(dest)?;
self.pending_count.fetch_add(1, Ordering::Relaxed);
let result = async {
let socket = Socket2::new(Domain::IPV6, Type::DGRAM, Some(Protocol::ICMPV6))
.map_err(|e| {
TracerouteError::SocketError(format!(
"Failed to create ICMPv6 ping socket: {e}"
))
})?;
socket.set_unicast_hops_v6(probe.ttl as u32).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set IPV6_UNICAST_HOPS: {e}"))
})?;
set_recverr_v6(socket.as_raw_fd()).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set IPV6_RECVERR: {e}"))
})?;
socket.set_nonblocking(true).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set non-blocking: {e}"))
})?;
let mut payload = [0u8; 16];
let tag = b"ftr-traceroute";
payload[..tag.len()].copy_from_slice(tag);
let pkt = icmpv6::build_echo_request_v6(0, probe.sequence, &payload);
let dest_sockaddr = SockAddr::from(SocketAddrV6::new(dest_v6, 0, 0, 0));
let sent_at = Instant::now();
socket.send_to(&pkt, &dest_sockaddr).map_err(|e| {
TracerouteError::ProbeSendError(format!("Failed to send ICMPv6 echo: {e}"))
})?;
Ok::<_, TracerouteError>((socket, sent_at))
}
.await;
let (socket, sent_at) = match result {
Ok(pair) => pair,
Err(e) => {
self.pending_count.fetch_sub(1, Ordering::Relaxed);
return Err(e);
}
};
let destination_reached = self.destination_reached.clone();
let pending_count = self.pending_count.clone();
let sequence = probe.sequence;
let ttl = probe.ttl;
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
let fd = socket.as_raw_fd();
let mut retry_count = 0;
loop {
match check_errqueue_v6(fd) {
ErrqueueCheck::Found(err) if err.ee_origin == SO_EE_ORIGIN_ICMP6 => {
if let Some((offender, _scope_id)) = err.offender {
let rtt = Instant::now().duration_since(sent_at);
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: IpAddr::V6(offender),
sequence,
ttl,
rtt,
received_at: Instant::now(),
is_destination: false,
is_timeout: false,
});
break;
}
}
ErrqueueCheck::Found(_) => {}
ErrqueueCheck::Error => {
pending_count.fetch_sub(1, Ordering::Relaxed);
break;
}
ErrqueueCheck::NoData => {}
}
let mut buf = [std::mem::MaybeUninit::uninit(); RECV_BUFFER_SIZE];
match socket.recv_from(&mut buf) {
Ok((size, from)) => {
let data = unsafe {
std::slice::from_raw_parts(buf.as_ptr().cast::<u8>(), size)
};
if let Some((_kernel_id, reply_seq)) = icmpv6::parse_echo_reply_v6(data)
{
if reply_seq == sequence {
let from_ip =
from.as_socket_ipv6().map(|sa| *sa.ip()).unwrap_or(dest_v6);
let rtt = Instant::now().duration_since(sent_at);
let is_destination = from_ip == dest_v6;
if is_destination {
destination_reached.store(true, Ordering::Relaxed);
}
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: IpAddr::V6(from_ip),
sequence,
ttl,
rtt,
received_at: Instant::now(),
is_destination,
is_timeout: false,
});
break;
}
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
Err(_) => {
}
}
retry_count += 1;
if retry_count >= MAX_RETRIES {
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: dest,
sequence,
ttl,
rtt: Duration::from_secs(1),
received_at: Instant::now(),
is_destination: false,
is_timeout: true,
});
break;
}
tokio::time::sleep(POLL_INTERVAL).await;
}
});
match rx.await {
Ok(response) => Ok(response),
Err(_) => {
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)
}
}
pub struct LinuxAsyncRawIcmpV6Socket {
icmp_identifier: u16,
destination_reached: Arc<AtomicBool>,
pending_count: Arc<AtomicUsize>,
}
impl LinuxAsyncRawIcmpV6Socket {
pub fn new_with_config(_timing_config: crate::TimingConfig) -> Result<Self, TracerouteError> {
Socket2::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6)).map_err(|e| {
if e.kind() == std::io::ErrorKind::PermissionDenied {
TracerouteError::InsufficientPermissions {
required: "root or CAP_NET_RAW".to_string(),
suggestion: "Run with sudo, or use the default UDP mode".to_string(),
}
} else {
TracerouteError::SocketError(format!("Failed to create raw ICMPv6 socket: {e}"))
}
})?;
Ok(LinuxAsyncRawIcmpV6Socket {
icmp_identifier: std::process::id() as u16,
destination_reached: Arc::new(AtomicBool::new(false)),
pending_count: Arc::new(AtomicUsize::new(0)),
})
}
fn parse_raw_response(
icmp_identifier: u16,
data: &[u8],
from_addr: Ipv6Addr,
sequence: u16,
dest: Ipv6Addr,
) -> Option<(Ipv6Addr, bool)> {
let hdr = icmpv6::parse_icmpv6_header(data)?;
if icmpv6::is_ndp(hdr.icmpv6_type) {
return None; }
match hdr.icmpv6_type {
icmpv6::ICMPV6_ECHO_REPLY => {
let (reply_id, reply_seq) = icmpv6::parse_echo_reply_v6(data)?;
if reply_id == icmp_identifier && reply_seq == sequence {
return Some((from_addr, true));
}
}
icmpv6::ICMPV6_TIME_EXCEEDED | icmpv6::ICMPV6_DEST_UNREACHABLE => {
let embedded = icmpv6::parse_embedded_probe(data)?;
if embedded.identifier == icmp_identifier
&& embedded.sequence == sequence
&& embedded.destination == dest
{
return Some((from_addr, false));
}
}
_ => {}
}
None
}
}
impl ProbeSocket for LinuxAsyncRawIcmpV6Socket {
fn mode(&self) -> ProbeMode {
ProbeMode::RawIcmp
}
fn send_probe_and_recv(
&self,
dest: IpAddr,
probe: ProbeInfo,
) -> Pin<Box<dyn Future<Output = Result<ProbeResponse, TracerouteError>> + Send + '_>> {
Box::pin(async move {
let dest_v6 = require_v6(dest)?;
self.pending_count.fetch_add(1, Ordering::Relaxed);
let result = (|| {
let socket = Socket2::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))
.map_err(|e| {
TracerouteError::SocketError(format!(
"Failed to create raw ICMPv6 socket: {e}"
))
})?;
socket.set_unicast_hops_v6(probe.ttl as u32).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set IPV6_UNICAST_HOPS: {e}"))
})?;
socket.set_nonblocking(true).map_err(|e| {
TracerouteError::SocketError(format!("Failed to set non-blocking: {e}"))
})?;
apply_icmp6_filter_raw(socket.as_raw_fd());
let mut payload = [0u8; 16];
let tag = b"ftr-traceroute";
payload[..tag.len()].copy_from_slice(tag);
let pkt =
icmpv6::build_echo_request_v6(self.icmp_identifier, probe.sequence, &payload);
let dest_sockaddr = SockAddr::from(SocketAddrV6::new(dest_v6, 0, 0, 0));
let sent_at = Instant::now();
socket.send_to(&pkt, &dest_sockaddr).map_err(|e| {
TracerouteError::ProbeSendError(format!("Failed to send ICMPv6 packet: {e}"))
})?;
Ok::<_, TracerouteError>((socket, sent_at))
})();
let (socket, sent_at) = match result {
Ok(pair) => pair,
Err(e) => {
self.pending_count.fetch_sub(1, Ordering::Relaxed);
return Err(e);
}
};
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 (tx, rx) = oneshot::channel();
tokio::spawn(async move {
let mut retry_count = 0;
loop {
let mut buf = [std::mem::MaybeUninit::uninit(); RECV_BUFFER_SIZE];
match socket.recv_from(&mut buf) {
Ok((size, from)) => {
if let Some(from_sa) = from.as_socket_ipv6() {
let data = unsafe {
std::slice::from_raw_parts(buf.as_ptr().cast::<u8>(), size)
};
if let Some((resp_addr, is_destination)) =
LinuxAsyncRawIcmpV6Socket::parse_raw_response(
icmp_identifier,
data,
*from_sa.ip(),
sequence,
dest_v6,
)
{
let rtt = Instant::now().duration_since(sent_at);
if is_destination {
destination_reached.store(true, Ordering::Relaxed);
}
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: IpAddr::V6(resp_addr),
sequence,
ttl,
rtt,
received_at: Instant::now(),
is_destination,
is_timeout: false,
});
break;
}
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
Err(_) => {
pending_count.fetch_sub(1, Ordering::Relaxed);
break;
}
}
retry_count += 1;
if retry_count >= MAX_RETRIES {
pending_count.fetch_sub(1, Ordering::Relaxed);
let _ = tx.send(ProbeResponse {
from_addr: dest,
sequence,
ttl,
rtt: Duration::from_secs(1),
received_at: Instant::now(),
is_destination: false,
is_timeout: true,
});
break;
}
tokio::time::sleep(POLL_INTERVAL).await;
}
});
match rx.await {
Ok(response) => Ok(response),
Err(_) => {
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)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_payload(
ee_errno: u32,
ee_origin: u8,
ee_type: u8,
ee_code: u8,
offender: Option<(Ipv6Addr, u32)>,
offender_family: u16,
) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&ee_errno.to_ne_bytes());
buf.extend_from_slice(&[ee_origin, ee_type, ee_code, 0 ]);
buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&0u32.to_ne_bytes()); assert_eq!(buf.len(), SOCK_EXTENDED_ERR_SIZE);
if let Some((addr, scope_id)) = offender {
let mut sa = vec![0u8; SOCKADDR_IN6_SIZE];
sa[0..2].copy_from_slice(&offender_family.to_ne_bytes());
sa[SIN6_ADDR_OFFSET..SIN6_ADDR_OFFSET + 16].copy_from_slice(&addr.octets());
sa[SIN6_SCOPE_ID_OFFSET..SIN6_SCOPE_ID_OFFSET + 4]
.copy_from_slice(&scope_id.to_ne_bytes());
buf.extend_from_slice(&sa);
}
buf
}
const HOP_ROUTER: Ipv6Addr = Ipv6Addr::new(0x2001, 0x5a8, 0x657, 0x21, 0, 0, 0xf0, 4);
const GOOGLE_V6: Ipv6Addr = Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888);
#[test]
fn test_parse_recverr_time_exceeded() {
let payload = build_payload(
113,
SO_EE_ORIGIN_ICMP6,
icmpv6::ICMPV6_TIME_EXCEEDED,
0,
Some((HOP_ROUTER, 0)),
libc::AF_INET6 as u16,
);
let err = parse_recverr_payload(&payload).expect("valid TE payload must parse");
assert_eq!(err.ee_errno, 113);
assert_eq!(err.ee_origin, SO_EE_ORIGIN_ICMP6);
assert_eq!(err.ee_type, icmpv6::ICMPV6_TIME_EXCEEDED);
assert_eq!(err.ee_code, 0);
assert_eq!(err.offender, Some((HOP_ROUTER, 0)));
assert!(!is_v6_destination_error(err.ee_type, err.ee_code));
}
#[test]
fn test_parse_recverr_destination_port_unreachable() {
let payload = build_payload(
111,
SO_EE_ORIGIN_ICMP6,
icmpv6::ICMPV6_DEST_UNREACHABLE,
ICMPV6_UNREACH_PORT,
Some((GOOGLE_V6, 0)),
libc::AF_INET6 as u16,
);
let err = parse_recverr_payload(&payload).expect("valid unreachable payload must parse");
assert_eq!(err.offender, Some((GOOGLE_V6, 0)));
assert!(is_v6_destination_error(err.ee_type, err.ee_code));
assert!(!is_v6_destination_error(icmpv6::ICMPV6_DEST_UNREACHABLE, 0));
assert!(!is_v6_destination_error(icmpv6::ICMPV6_TIME_EXCEEDED, 4));
}
#[test]
fn test_parse_recverr_offender_scope_id_preserved() {
let ll = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
let payload = build_payload(
113,
SO_EE_ORIGIN_ICMP6,
icmpv6::ICMPV6_TIME_EXCEEDED,
0,
Some((ll, 3)),
libc::AF_INET6 as u16,
);
let err = parse_recverr_payload(&payload).expect("must parse");
assert_eq!(err.offender, Some((ll, 3)));
}
#[test]
fn test_parse_recverr_no_offender() {
let payload = build_payload(111, SO_EE_ORIGIN_ICMP6, 1, 4, None, 0);
let err = parse_recverr_payload(&payload).expect("must parse without offender");
assert_eq!(err.offender, None);
}
#[test]
fn test_parse_recverr_wrong_family_offender_ignored() {
let payload = build_payload(
113,
SO_EE_ORIGIN_ICMP6,
icmpv6::ICMPV6_TIME_EXCEEDED,
0,
Some((HOP_ROUTER, 0)),
libc::AF_INET as u16,
);
let err = parse_recverr_payload(&payload).expect("must parse");
assert_eq!(err.offender, None);
}
#[test]
fn test_parse_recverr_truncated() {
let payload = build_payload(113, SO_EE_ORIGIN_ICMP6, 3, 0, None, 0);
assert!(parse_recverr_payload(&payload[..SOCK_EXTENDED_ERR_SIZE - 1]).is_none());
assert!(parse_recverr_payload(&[]).is_none());
}
#[test]
fn test_parse_recverr_non_icmp6_origin_still_parses() {
let payload = build_payload(101, 1 , 0, 0, None, 0);
let err = parse_recverr_payload(&payload).expect("must parse");
assert_eq!(err.ee_origin, 1);
assert_ne!(err.ee_origin, SO_EE_ORIGIN_ICMP6);
}
#[test]
fn test_icmp6_filter_linux_inverted_semantics() {
let filter = Icmp6FilterLinux::block_all()
.pass(icmpv6::ICMPV6_DEST_UNREACHABLE)
.pass(icmpv6::ICMPV6_TIME_EXCEEDED)
.pass(icmpv6::ICMPV6_ECHO_REPLY);
assert!(filter.passes(icmpv6::ICMPV6_DEST_UNREACHABLE));
assert!(filter.passes(icmpv6::ICMPV6_TIME_EXCEEDED));
assert!(filter.passes(icmpv6::ICMPV6_ECHO_REPLY));
assert!(!filter.passes(icmpv6::ICMPV6_ECHO_REQUEST));
for ty in 133..=137u8 {
assert!(!filter.passes(ty), "NDP type {ty} must remain blocked");
}
}
#[test]
fn test_parse_raw_response_demux() {
let id = 0x4242u16;
let seq = 9u16;
let mut reply = icmpv6::build_echo_request_v6(id, seq, &[]);
reply[0] = icmpv6::ICMPV6_ECHO_REPLY;
assert_eq!(
LinuxAsyncRawIcmpV6Socket::parse_raw_response(id, &reply, GOOGLE_V6, seq, GOOGLE_V6),
Some((GOOGLE_V6, true))
);
let mut foreign = icmpv6::build_echo_request_v6(0x9999, seq, &[]);
foreign[0] = icmpv6::ICMPV6_ECHO_REPLY;
assert_eq!(
LinuxAsyncRawIcmpV6Socket::parse_raw_response(id, &foreign, GOOGLE_V6, seq, GOOGLE_V6),
None
);
let mut te = vec![0u8; 56];
te[0] = icmpv6::ICMPV6_TIME_EXCEEDED;
te[8] = 6 << 4; te[14] = icmpv6::IPV6_NEXT_HEADER_ICMPV6;
te[32..48].copy_from_slice(&GOOGLE_V6.octets());
te[48] = icmpv6::ICMPV6_ECHO_REQUEST;
te[52..54].copy_from_slice(&id.to_be_bytes());
te[54..56].copy_from_slice(&seq.to_be_bytes());
assert_eq!(
LinuxAsyncRawIcmpV6Socket::parse_raw_response(id, &te, HOP_ROUTER, seq, GOOGLE_V6),
Some((HOP_ROUTER, false))
);
let other = Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111);
let mut wrong_dst = te.clone();
wrong_dst[32..48].copy_from_slice(&other.octets());
assert_eq!(
LinuxAsyncRawIcmpV6Socket::parse_raw_response(
id, &wrong_dst, HOP_ROUTER, seq, GOOGLE_V6
),
None
);
let ra = [134u8, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
LinuxAsyncRawIcmpV6Socket::parse_raw_response(id, &ra, HOP_ROUTER, seq, GOOGLE_V6),
None
);
}
}