use anyhow::{Context, anyhow}; use std::{ io::Write, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, time::Duration, }; use crate::cli::Opt; pub fn send_tcp(opt: &Opt, destination: SocketAddr) -> anyhow::Result<()> { eprintln!("Leaking TCP packets to {destination}"); let (family, bind_address) = match &destination { SocketAddr::V4(_) => (socket2::Domain::IPV4, IpAddr::from(Ipv4Addr::UNSPECIFIED)), SocketAddr::V6(_) => (socket2::Domain::IPV6, IpAddr::from(Ipv6Addr::UNSPECIFIED)), }; let bind_address: SocketAddr = SocketAddr::new(bind_address, 0); let sock = socket2::Socket::new(family, socket2::Type::STREAM, Some(socket2::Protocol::TCP)) .context(anyhow!("Failed to create TCP socket"))?; sock.bind(&socket2::SockAddr::from(bind_address)) .context(anyhow!("Failed to bind TCP socket to {bind_address}"))?; let timeout = Duration::from_secs(opt.leak_timeout); sock.set_write_timeout(Some(timeout))?; sock.set_read_timeout(Some(timeout))?; sock.connect_timeout(&socket2::SockAddr::from(destination), timeout) .context(anyhow!("Failed to connect to {destination}"))?; let mut stream = std::net::TcpStream::from(sock); stream .write_all(opt.payload.as_bytes()) .context(anyhow!("Failed to send message to {destination}"))?; Ok(()) } pub fn send_udp(opt: &Opt, destination: SocketAddr) -> Result<(), anyhow::Error> { eprintln!("Leaking UDP packets to {destination}"); let (family, bind_address) = match &destination { SocketAddr::V4(_) => (socket2::Domain::IPV4, IpAddr::from(Ipv4Addr::UNSPECIFIED)), SocketAddr::V6(_) => (socket2::Domain::IPV6, IpAddr::from(Ipv6Addr::UNSPECIFIED)), }; let bind_address: SocketAddr = SocketAddr::new(bind_address, 0); let sock = socket2::Socket::new(family, socket2::Type::DGRAM, Some(socket2::Protocol::UDP)) .context("Failed to create UDP socket")?; sock.bind(&socket2::SockAddr::from(bind_address)) .context(anyhow!("Failed to bind UDP socket to {bind_address}"))?; let std_socket = std::net::UdpSocket::from(sock); std_socket .send_to(opt.payload.as_bytes(), destination) .context(anyhow!("Failed to send message to {destination}"))?; Ok(()) } #[cfg(target_os = "windows")] pub fn send_ping(opt: &Opt, destination: IpAddr) -> anyhow::Result<()> { eprintln!("Leaking ICMP packets to {destination}"); ping::ping( destination, Some(Duration::from_secs(opt.leak_timeout)), None, None, None, None, )?; Ok(()) } #[cfg(target_os = "macos")] pub fn send_ping(opt: &Opt, destination: IpAddr) -> anyhow::Result<()> { eprintln!("Leaking ICMP packets to {destination}"); // On macOS, use dgramsock (SOCK_DGRAM) instead of the default sock type (SOCK_RAW), // so that we don't need root privileges. Naturally, this does not work for Windows. ping::dgramsock::ping( destination, Some(Duration::from_secs(opt.leak_timeout)), None, None, None, None, )?; Ok(()) } // Older Linux distributions don't allow unprivileged users to send ICMP packets, even for // SOCK_DGRAM sockets. We use the ping command (which has capabilities/setuid set) to get around // that. #[cfg(target_os = "linux")] pub fn send_ping(opt: &Opt, destination: IpAddr) -> anyhow::Result<()> { eprintln!("Leaking ICMP packets to {destination}"); let mut cmd = std::process::Command::new("ping"); let timeout_sec = opt.leak_timeout.to_string(); cmd.args(["-c", "1", "-W", &timeout_sec, &destination.to_string()]); let output = cmd.output().context(anyhow!( "Failed to execute ping for destination {destination}" ))?; if !output.status.success() { eprintln!( "ping stdout:\n\n{}", std::str::from_utf8(&output.stdout).unwrap_or("invalid utf8") ); eprintln!( "ping stderr:\n\n{}", std::str::from_utf8(&output.stderr).unwrap_or("invalid utf8") ); return Err(anyhow!("ping for destination {destination} failed")); } Ok(()) }