use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; use socket2::{Domain, Protocol, Socket, Type}; use crate::{ config::{AddressFamily, SocketConfig, DEFAULT_PORT}, error::ClientError, receive::configure_receive_metadata, socket_options::apply_ttl_to_socket, }; #[cfg(any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux" ))] use crate::socket_options::apply_routing_options; pub(crate) fn resolve_remote( endpoint: &str, family: AddressFamily, ) -> Result { let addr = normalize_server_addr(endpoint); let mut addrs = addr .to_socket_addrs() .map_err(|_| ClientError::Resolve { addr: addr.clone() })?; addrs .find(|addr| address_family_allowed(family, *addr)) .ok_or(ClientError::Resolve { addr }) } #[cfg(feature = "tokio")] pub(crate) async fn resolve_remote_tokio( endpoint: &str, family: AddressFamily, ) -> Result { let addr = normalize_server_addr(endpoint); if let Ok(remote) = addr.parse::() { return address_family_allowed(family, remote) .then_some(remote) .ok_or(ClientError::Resolve { addr }); } let mut addrs = tokio::net::lookup_host(&addr) .await .map_err(|_| ClientError::Resolve { addr: addr.clone() })?; addrs .find(|remote| address_family_allowed(family, *remote)) .ok_or_else(|| ClientError::Resolve { addr: addr.clone() }) } fn address_family_allowed(family: AddressFamily, remote: SocketAddr) -> bool { match family { AddressFamily::Any => true, AddressFamily::Ipv4 => remote.is_ipv4(), AddressFamily::Ipv6 => remote.is_ipv6(), } } pub(crate) fn normalize_server_addr(addr: &str) -> String { if addr.parse::().is_ok() { return addr.to_owned(); } if addr.starts_with('[') && addr.ends_with(']') { return format!("{addr}:{DEFAULT_PORT}"); } if addr.starts_with('[') { return addr.to_owned(); } if addr.parse::().is_ok() { return format!("[{addr}]:{DEFAULT_PORT}"); } if addr .rsplit_once(':') .is_some_and(|(_, port)| port.parse::().is_ok()) { return addr.to_owned(); } format!("{addr}:{DEFAULT_PORT}") } pub(crate) fn connect_udp_socket( config: &SocketConfig, remote: SocketAddr, family: AddressFamily, ) -> Result { let socket = create_prebind_udp_socket(remote, family)?; #[cfg(any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "linux" ))] apply_routing_options(&socket, config, remote)?; let bind_addr = config.bind_addr.unwrap_or_else(|| { if remote.is_ipv4() { SocketAddr::from(([0, 0, 0, 0], 0)) } else { SocketAddr::from(([0_u16; 8], 0)) } }); socket.bind(&bind_addr.into())?; socket.connect(&remote.into())?; let socket: UdpSocket = socket.into(); configure_receive_metadata(&socket, remote).map_err(|source| ClientError::SocketOption { operation: "enable receive metadata", remote, source, })?; if let Some(ttl) = config.ttl { apply_ttl_to_socket(&socket, remote, ttl)?; } Ok(socket) } fn create_prebind_udp_socket( remote: SocketAddr, family: AddressFamily, ) -> Result { let domain = if remote.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 }; let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?; if family == AddressFamily::Ipv6 && remote.is_ipv6() { socket.set_only_v6(true)?; } Ok(socket) } #[cfg(feature = "tokio")] pub(crate) fn connect_tokio_udp_socket( config: &SocketConfig, remote: SocketAddr, family: AddressFamily, ) -> Result { let socket = connect_udp_socket(config, remote, family)?; socket.set_nonblocking(true)?; Ok(tokio::net::UdpSocket::from_std(socket)?) }