use super::traits::ProbeSocket;
use super::{ProbeProtocol, SocketMode};
use crate::TimingConfig;
use crate::traceroute::TracerouteError;
use std::net::IpAddr;
pub async fn create_probe_socket_with_options_and_verbose(
target: IpAddr,
timing_config: TimingConfig,
protocol: Option<ProbeProtocol>,
socket_mode: Option<SocketMode>,
verbose: u8,
) -> Result<Box<dyn ProbeSocket>, TracerouteError> {
if let Some(ProbeProtocol::Tcp) = protocol {
return Err(TracerouteError::NotImplemented {
feature: "TCP traceroute".to_string(),
});
}
match target {
IpAddr::V4(_) => {
#[cfg(target_os = "windows")]
{
let _ = protocol;
let _ = socket_mode;
use super::windows::WindowsAsyncIcmpSocket;
let socket =
WindowsAsyncIcmpSocket::new_with_config_and_verbose(timing_config, verbose)?;
if verbose > 0 {
eprintln!("Using Windows ICMP API mode for traceroute");
}
Ok(Box::new(socket))
}
#[cfg(target_os = "macos")]
{
let _ = protocol;
let _ = socket_mode;
use super::macos::MacOSAsyncIcmpSocket;
let socket =
MacOSAsyncIcmpSocket::new_with_config_and_verbose(timing_config, verbose)?;
if verbose > 0 {
eprintln!("Using DGRAM ICMP mode for traceroute (per-probe version)");
}
Ok(Box::new(socket))
}
#[cfg(target_os = "linux")]
{
let use_icmp = matches!(protocol, Some(ProbeProtocol::Icmp))
|| matches!(socket_mode, Some(SocketMode::Raw));
if use_icmp {
use socket2::{Domain, Protocol, Socket, Type};
match Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4)) {
Ok(_) => {
use super::linux::LinuxAsyncIcmpSocket;
let socket = LinuxAsyncIcmpSocket::new_with_config(timing_config)?;
if verbose > 0 {
eprintln!("Using Raw ICMP mode for traceroute");
}
Ok(Box::new(socket))
}
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
Err(TracerouteError::InsufficientPermissions {
required: "root or CAP_NET_RAW".to_string(),
suggestion: "Run with sudo or use UDP mode".to_string(),
})
}
Err(e) => Err(TracerouteError::SocketError(format!(
"Failed to create ICMP socket: {e}"
))),
}
} else {
use super::linux::LinuxAsyncUdpSocket;
let socket = LinuxAsyncUdpSocket::new_with_config(timing_config)?;
if verbose > 0 {
eprintln!("Using UDP with IP_RECVERR (no root required)");
}
Ok(Box::new(socket))
}
}
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
{
let _ = protocol;
let _ = socket_mode;
use super::bsd::BsdAsyncIcmpSocket;
let socket = BsdAsyncIcmpSocket::new_with_config(timing_config)?;
if verbose > 0 {
eprintln!("Using Raw ICMP mode for traceroute");
}
Ok(Box::new(socket))
}
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
{
let _ = protocol;
let _ = verbose;
Err(TracerouteError::SocketError(
"Socket implementation not available for this platform".to_string(),
))
}
}
IpAddr::V6(_) => {
#[cfg(target_os = "macos")]
{
let _ = socket_mode;
if let Some(ProbeProtocol::Udp) = protocol {
return Err(TracerouteError::NotImplemented {
feature: "UDP IPv6 traceroute on macOS".to_string(),
});
}
use super::macos_v6::MacOSAsyncIcmpV6Socket;
let socket =
MacOSAsyncIcmpV6Socket::new_with_config_and_verbose(timing_config, verbose)?;
if verbose > 0 {
eprintln!("Using DGRAM ICMPv6 mode for traceroute (per-probe version)");
}
Ok(Box::new(socket))
}
#[cfg(target_os = "linux")]
{
create_linux_v6_socket(timing_config, protocol, socket_mode, verbose)
}
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
{
let _ = socket_mode;
if let Some(ProbeProtocol::Udp) = protocol {
return Err(TracerouteError::NotImplemented {
feature: "UDP IPv6 traceroute on BSD".to_string(),
});
}
use super::bsd_v6::BsdAsyncIcmpV6Socket;
let socket = BsdAsyncIcmpV6Socket::new_with_config(timing_config)?;
if verbose > 0 {
eprintln!("Using raw ICMPv6 mode for traceroute");
}
Ok(Box::new(socket))
}
#[cfg(target_os = "windows")]
{
let _ = socket_mode;
if let Some(ProbeProtocol::Udp) = protocol {
return Err(TracerouteError::NotImplemented {
feature: "UDP IPv6 traceroute on Windows".to_string(),
});
}
use super::windows_v6::WindowsAsyncIcmpV6Socket;
let socket =
WindowsAsyncIcmpV6Socket::new_with_config_and_verbose(timing_config, verbose)?;
if verbose > 0 {
eprintln!("Using Windows ICMPv6 API mode for traceroute");
}
Ok(Box::new(socket))
}
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
{
let _ = (timing_config, socket_mode, verbose);
Err(TracerouteError::Ipv6NotSupported)
}
}
}
}
#[cfg(target_os = "linux")]
fn create_linux_v6_socket(
timing_config: TimingConfig,
protocol: Option<ProbeProtocol>,
socket_mode: Option<SocketMode>,
verbose: u8,
) -> Result<Box<dyn ProbeSocket>, TracerouteError> {
use super::linux_v6::{
LinuxAsyncPingV6Socket, LinuxAsyncRawIcmpV6Socket, LinuxAsyncUdpV6Socket,
};
let make_udp = |tc: TimingConfig| -> Result<Box<dyn ProbeSocket>, TracerouteError> {
let socket = LinuxAsyncUdpV6Socket::new_with_config(tc)?;
if verbose > 0 {
eprintln!("Using UDP with IPV6_RECVERR (no root required)");
}
Ok(Box::new(socket))
};
let make_ping = |tc: TimingConfig| -> Result<Box<dyn ProbeSocket>, TracerouteError> {
let socket = LinuxAsyncPingV6Socket::new_with_config(tc)?;
if verbose > 0 {
eprintln!("Using ICMPv6 ping-socket mode for traceroute");
}
Ok(Box::new(socket))
};
let make_raw = |tc: TimingConfig| -> Result<Box<dyn ProbeSocket>, TracerouteError> {
let socket = LinuxAsyncRawIcmpV6Socket::new_with_config(tc)?;
if verbose > 0 {
eprintln!("Using raw ICMPv6 mode for traceroute");
}
Ok(Box::new(socket))
};
if matches!(socket_mode, Some(SocketMode::Raw)) {
return make_raw(timing_config);
}
match protocol {
Some(ProbeProtocol::Icmp) => match make_ping(timing_config.clone()) {
Ok(socket) => Ok(socket),
Err(ping_err) => make_raw(timing_config).map_err(|_| ping_err),
},
Some(ProbeProtocol::Udp) => make_udp(timing_config),
_ => match make_udp(timing_config.clone()) {
Ok(socket) => Ok(socket),
Err(udp_err) => match make_ping(timing_config.clone()) {
Ok(socket) => Ok(socket),
Err(_) => make_raw(timing_config).map_err(|_| udp_err),
},
},
}
}