use std::{ io, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, }; use bytes::BytesMut; use tokio::net::UdpSocket; pub(crate) fn enable_recv_pktinfo(socket: &UdpSocket) -> io::Result<()> { use std::os::fd::AsRawFd; use nix::libc; let fd = socket.as_raw_fd(); let enabled: libc::c_int = 1; unsafe { #[cfg(any(target_os = "linux", target_os = "android"))] let _ = libc::setsockopt( fd, libc::IPPROTO_IP, libc::IP_PKTINFO, &enabled as *const _ as *const libc::c_void, std::mem::size_of_val(&enabled) as libc::socklen_t, ); #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos", target_os = "ios" ))] let _ = libc::setsockopt( fd, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, &enabled as *const _ as *const libc::c_void, std::mem::size_of_val(&enabled) as libc::socklen_t, ); let _ = libc::setsockopt( fd, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, &enabled as *const _ as *const libc::c_void, std::mem::size_of_val(&enabled) as libc::socklen_t, ); } Ok(()) } #[cfg(not(any(unix, windows)))] pub(crate) fn enable_recv_pktinfo(_socket: &UdpSocket) -> io::Result<()> { Ok(()) } pub(crate) async fn recv_from_with_dst_ip( socket: &UdpSocket, buf: &mut [u8], ) -> io::Result<(usize, SocketAddr, Option)> { socket .async_io(tokio::io::Interest::READABLE, || { loop { match recv_from_with_dst_ip_once(socket, buf) { Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, ret => break ret, } } }) .await } pub(crate) async fn recv_datagram_with_dst_ip( socket: &UdpSocket, capacity: usize, ) -> io::Result<(BytesMut, SocketAddr, Option)> { let mut payload = BytesMut::with_capacity(capacity); let (len, remote_addr, dst_ip) = socket .async_io(tokio::io::Interest::READABLE, || { loop { let ret = unsafe { recv_from_with_dst_ip_raw(socket, payload.as_mut_ptr(), payload.capacity()) }; match ret { Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, Ok((_len, _remote_addr, _dst_ip, true)) => { tracing::debug!(capacity, "dropping oversized udp session datagram"); } Ok((len, remote_addr, dst_ip, false)) => { break Ok((len, remote_addr, dst_ip)); } Err(err) => break Err(err), } } }) .await?; unsafe { payload.set_len(len); } Ok((payload, remote_addr, dst_ip)) } #[cfg(not(any(unix, windows)))] pub(crate) async fn recv_from_with_dst_ip( socket: &UdpSocket, buf: &mut [u8], ) -> io::Result<(usize, SocketAddr, Option)> { let (len, addr) = socket.recv_from(buf).await?; Ok((len, addr, None)) } fn recv_from_with_dst_ip_once( socket: &UdpSocket, buf: &mut [u8], ) -> io::Result<(usize, SocketAddr, Option)> { unsafe { recv_from_with_dst_ip_raw(socket, buf.as_mut_ptr(), buf.len()) } .map(|(len, remote_addr, dst_ip, _truncated)| (len, remote_addr, dst_ip)) } unsafe fn recv_from_with_dst_ip_raw( socket: &UdpSocket, buf_ptr: *mut u8, buf_len: usize, ) -> io::Result<(usize, SocketAddr, Option, bool)> { use std::{mem, os::fd::AsRawFd}; use nix::libc; #[repr(align(8))] struct ControlBuffer([u8; 256]); fn sockaddr_to_socket_addr( storage: &libc::sockaddr_storage, len: libc::socklen_t, ) -> io::Result { match storage.ss_family as libc::c_int { libc::AF_INET => { if (len as usize) < mem::size_of::() { return Err(io::Error::new( io::ErrorKind::InvalidData, "short IPv4 sockaddr", )); } let addr = unsafe { &*(storage as *const _ as *const libc::sockaddr_in) }; let ip = Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)); let port = u16::from_be(addr.sin_port); Ok(SocketAddr::V4(SocketAddrV4::new(ip, port))) } libc::AF_INET6 => { if (len as usize) < mem::size_of::() { return Err(io::Error::new( io::ErrorKind::InvalidData, "short IPv6 sockaddr", )); } let addr = unsafe { &*(storage as *const _ as *const libc::sockaddr_in6) }; let ip = Ipv6Addr::from(addr.sin6_addr.s6_addr); let port = u16::from_be(addr.sin6_port); Ok(SocketAddr::V6(SocketAddrV6::new( ip, port, addr.sin6_flowinfo, addr.sin6_scope_id, ))) } _ => Err(io::Error::new( io::ErrorKind::InvalidData, "unsupported UDP sockaddr family", )), } } let mut iov = libc::iovec { iov_base: buf_ptr as *mut libc::c_void, iov_len: buf_len, }; let mut name = unsafe { mem::zeroed::() }; let mut control = ControlBuffer([0u8; 256]); let mut msg = unsafe { mem::zeroed::() }; msg.msg_name = &mut name as *mut _ as *mut libc::c_void; msg.msg_namelen = mem::size_of::() as _; msg.msg_iov = &mut iov; msg.msg_iovlen = 1; msg.msg_control = control.0.as_mut_ptr() as *mut libc::c_void; msg.msg_controllen = control.0.len() as _; let len = unsafe { libc::recvmsg(socket.as_raw_fd(), &mut msg, 0) }; if len < 0 { return Err(io::Error::last_os_error()); } let remote_addr = sockaddr_to_socket_addr(&name, msg.msg_namelen)?; let mut dst_ip = None; unsafe { let mut cmsg = libc::CMSG_FIRSTHDR(&msg); while !cmsg.is_null() { #[cfg(any(target_os = "linux", target_os = "android"))] { if (*cmsg).cmsg_level == libc::IPPROTO_IP && (*cmsg).cmsg_type == libc::IP_PKTINFO { let pktinfo = &*(libc::CMSG_DATA(cmsg) as *const libc::in_pktinfo); dst_ip = Some(IpAddr::V4(Ipv4Addr::from(u32::from_be( pktinfo.ipi_addr.s_addr, )))); } } #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos", target_os = "ios" ))] { if (*cmsg).cmsg_level == libc::IPPROTO_IP && (*cmsg).cmsg_type == libc::IP_RECVDSTADDR { let addr = &*(libc::CMSG_DATA(cmsg) as *const libc::in_addr); dst_ip = Some(IpAddr::V4(Ipv4Addr::from(u32::from_be(addr.s_addr)))); } } if (*cmsg).cmsg_level == libc::IPPROTO_IPV6 && (*cmsg).cmsg_type == libc::IPV6_PKTINFO { let pktinfo = &*(libc::CMSG_DATA(cmsg) as *const libc::in6_pktinfo); dst_ip = Some(IpAddr::V6(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr))); } cmsg = libc::CMSG_NXTHDR(&msg, cmsg); } } let truncated = msg.msg_flags & libc::MSG_TRUNC != 0; Ok((len as usize, remote_addr, dst_ip, truncated)) } #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) async fn send_to_with_src_ip( socket: &UdpSocket, src_ip: IpAddr, dst_addr: SocketAddr, buf: &[u8], ) -> io::Result { socket .async_io(tokio::io::Interest::WRITABLE, || { send_to_with_src_ip_raw(socket, src_ip, dst_addr, buf) }) .await } #[cfg(not(any(target_os = "linux", target_os = "android")))] pub(crate) async fn send_to_with_src_ip( socket: &UdpSocket, src_ip: IpAddr, dst_addr: SocketAddr, buf: &[u8], ) -> io::Result { match (src_ip, dst_addr) { (IpAddr::V4(src), SocketAddr::V4(dst)) => { socket .async_io(tokio::io::Interest::WRITABLE, || { send_to_with_src_ipv4_to_addr(socket, src, SocketAddr::V4(dst), buf) }) .await } (IpAddr::V4(src), SocketAddr::V6(dst)) => { if dst.ip().to_ipv4_mapped().is_none() { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("source address {src} does not match destination {dst} family"), )); } socket .async_io(tokio::io::Interest::WRITABLE, || { send_to_with_src_ipv4_to_addr(socket, src, SocketAddr::V6(dst), buf) }) .await } (IpAddr::V6(src), SocketAddr::V6(dst)) => { socket .async_io(tokio::io::Interest::WRITABLE, || { send_to_with_src_ipv6(socket, src, 0, dst, buf) }) .await } (src, dst) => Err(io::Error::new( io::ErrorKind::InvalidInput, format!("source address {src} does not match destination {dst} family"), )), } } #[cfg(not(any(target_os = "linux", target_os = "android")))] fn send_to_with_src_ipv4_to_addr( socket: &UdpSocket, src_ip: Ipv4Addr, dst_addr: SocketAddr, buf: &[u8], ) -> io::Result { match dst_addr { SocketAddr::V4(dst) => send_to_with_src_ipv4(socket, src_ip, dst, buf), SocketAddr::V6(dst) => { let Some(mapped_dst) = dst.ip().to_ipv4_mapped() else { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("source address {src_ip} does not match destination {dst} family"), )); }; send_to_with_src_ipv4_mapped_v6(socket, src_ip, dst, mapped_dst, buf) } } } #[cfg(not(any(target_os = "linux", target_os = "android", windows)))] fn send_to_with_src_ipv4_mapped_v6( socket: &UdpSocket, src_ip: Ipv4Addr, dst_addr: SocketAddrV6, mapped_dst: Ipv4Addr, buf: &[u8], ) -> io::Result { send_to_with_src_ipv4( socket, src_ip, SocketAddrV4::new(mapped_dst, dst_addr.port()), buf, ) } #[cfg(any(target_os = "linux", target_os = "android"))] fn send_to_with_src_ip_raw( socket: &UdpSocket, src_ip: IpAddr, dst_addr: SocketAddr, buf: &[u8], ) -> io::Result { match (src_ip, dst_addr) { (IpAddr::V4(src), SocketAddr::V4(dst)) => send_to_with_src_ipv4(socket, src, dst, buf), (IpAddr::V4(src), SocketAddr::V6(dst)) => { let Some(mapped_dst) = dst.ip().to_ipv4_mapped() else { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("source address {src} does not match destination {dst} family"), )); }; send_to_with_src_ipv4(socket, src, SocketAddrV4::new(mapped_dst, dst.port()), buf) } (IpAddr::V6(src), SocketAddr::V6(dst)) => send_to_with_src_ipv6(socket, src, 0, dst, buf), (src, dst) => Err(io::Error::new( io::ErrorKind::InvalidInput, format!("source address {src} does not match destination {dst} family"), )), } } #[cfg(any(target_os = "linux", target_os = "android"))] fn send_to_with_src_ipv4( socket: &UdpSocket, src_ip: Ipv4Addr, dst_addr: SocketAddrV4, buf: &[u8], ) -> io::Result { use std::{mem, os::fd::AsRawFd, ptr}; use nix::libc; #[repr(align(8))] struct ControlBuffer([u8; 128]); let pktinfo = libc::in_pktinfo { ipi_ifindex: 0, ipi_spec_dst: libc::in_addr { s_addr: u32::from(src_ip).to_be(), }, ipi_addr: libc::in_addr { s_addr: 0 }, }; let mut iov = libc::iovec { iov_base: buf.as_ptr() as *mut libc::c_void, iov_len: buf.len(), }; let dst_addr = socket2::SockAddr::from(std::net::SocketAddr::V4(dst_addr)); let control_len = unsafe { libc::CMSG_SPACE(mem::size_of::() as libc::c_uint) as usize }; let mut control = ControlBuffer([0u8; 128]); if control_len > control.0.len() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "IPv4 packet info control buffer is too small", )); } let mut msg = unsafe { mem::zeroed::() }; msg.msg_name = dst_addr.as_ptr() as *mut libc::c_void; msg.msg_namelen = dst_addr.len() as _; msg.msg_iov = &mut iov; msg.msg_iovlen = 1; msg.msg_control = control.0.as_mut_ptr() as *mut libc::c_void; msg.msg_controllen = control_len as _; unsafe { let cmsg = libc::CMSG_FIRSTHDR(&msg); if cmsg.is_null() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "IPv4 packet info control buffer is invalid", )); } (*cmsg).cmsg_level = libc::IPPROTO_IP; (*cmsg).cmsg_type = libc::IP_PKTINFO; (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::() as libc::c_uint) as _; ptr::write(libc::CMSG_DATA(cmsg) as *mut libc::in_pktinfo, pktinfo); let ret = libc::sendmsg(socket.as_raw_fd(), &msg, 0); if ret < 0 { Err(io::Error::last_os_error()) } else { Ok(ret as usize) } } } #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos", target_os = "ios" ))] fn send_to_with_src_ipv4( socket: &UdpSocket, src_ip: Ipv4Addr, dst_addr: SocketAddrV4, buf: &[u8], ) -> io::Result { use std::{mem, os::fd::AsRawFd, ptr}; use nix::libc; #[repr(align(8))] struct ControlBuffer([u8; 128]); if let Ok(SocketAddr::V4(local_addr)) = socket.local_addr() { if !local_addr.ip().is_unspecified() { return socket.try_send_to(buf, SocketAddr::V4(dst_addr)); } } let src_addr = libc::in_addr { s_addr: u32::from(src_ip).to_be(), }; let mut iov = libc::iovec { iov_base: buf.as_ptr() as *mut libc::c_void, iov_len: buf.len(), }; let dst_addr = socket2::SockAddr::from(std::net::SocketAddr::V4(dst_addr)); let control_len = unsafe { libc::CMSG_SPACE(mem::size_of::() as libc::c_uint) as usize }; let mut control = ControlBuffer([0u8; 128]); if control_len > control.0.len() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "IPv4 source address control buffer is too small", )); } let mut msg = unsafe { mem::zeroed::() }; msg.msg_name = dst_addr.as_ptr() as *mut libc::c_void; msg.msg_namelen = dst_addr.len() as _; msg.msg_iov = &mut iov; msg.msg_iovlen = 1; msg.msg_control = control.0.as_mut_ptr() as *mut libc::c_void; msg.msg_controllen = control_len as _; unsafe { let cmsg = libc::CMSG_FIRSTHDR(&msg); if cmsg.is_null() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "IPv4 source address control buffer is invalid", )); } (*cmsg).cmsg_level = libc::IPPROTO_IP; (*cmsg).cmsg_type = libc::IP_RECVDSTADDR; (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::() as libc::c_uint) as _; ptr::write(libc::CMSG_DATA(cmsg) as *mut libc::in_addr, src_addr); let ret = libc::sendmsg(socket.as_raw_fd(), &msg, 0); if ret < 0 { Err(io::Error::last_os_error()) } else { Ok(ret as usize) } } } #[cfg(not(any( target_os = "linux", target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos", target_os = "ios", windows )))] fn send_to_with_src_ipv4( socket: &UdpSocket, _src_ip: Ipv4Addr, dst_addr: SocketAddrV4, buf: &[u8], ) -> io::Result { socket.try_send_to(buf, SocketAddr::V4(dst_addr)) } pub(crate) fn send_to_with_src_ipv6( socket: &UdpSocket, src_ip: Ipv6Addr, src_ifindex: u32, dst_addr: SocketAddrV6, buf: &[u8], ) -> io::Result { #[cfg(target_env = "ohos")] { let _ = (socket, src_ip, src_ifindex, dst_addr, buf); return Err(io::Error::new( io::ErrorKind::Unsupported, "sending UDP with a selected IPv6 source is not supported on OHOS", )); } #[cfg(not(target_env = "ohos"))] { use std::{mem, os::fd::AsRawFd, ptr}; use nix::libc; #[repr(align(8))] struct ControlBuffer([u8; 128]); #[cfg(target_os = "android")] let ipi6_ifindex: libc::c_int = i32::try_from(src_ifindex).map_err(|_| { io::Error::new( io::ErrorKind::InvalidInput, "IPv6 source interface index is out of range", ) })?; #[cfg(not(target_os = "android"))] let ipi6_ifindex: libc::c_uint = src_ifindex; let pktinfo = libc::in6_pktinfo { ipi6_addr: libc::in6_addr { s6_addr: src_ip.octets(), }, ipi6_ifindex, }; let mut iov = libc::iovec { iov_base: buf.as_ptr() as *mut libc::c_void, iov_len: buf.len(), }; let dst_addr = socket2::SockAddr::from(std::net::SocketAddr::V6(dst_addr)); let control_len = unsafe { libc::CMSG_SPACE(mem::size_of::() as libc::c_uint) as usize }; let mut control = ControlBuffer([0u8; 128]); if control_len > control.0.len() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "IPv6 packet info control buffer is too small", )); } let mut msg = unsafe { mem::zeroed::() }; msg.msg_name = dst_addr.as_ptr() as *mut libc::c_void; msg.msg_namelen = dst_addr.len() as _; msg.msg_iov = &mut iov; msg.msg_iovlen = 1; msg.msg_control = control.0.as_mut_ptr() as *mut libc::c_void; msg.msg_controllen = control_len as _; msg.msg_flags = 0; unsafe { let cmsg = libc::CMSG_FIRSTHDR(&msg); if cmsg.is_null() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "IPv6 packet info control buffer is invalid", )); } (*cmsg).cmsg_level = libc::IPPROTO_IPV6; (*cmsg).cmsg_type = libc::IPV6_PKTINFO; (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::() as libc::c_uint) as _; ptr::write(libc::CMSG_DATA(cmsg) as *mut libc::in6_pktinfo, pktinfo); let ret = libc::sendmsg(socket.as_raw_fd(), &msg, 0); if ret < 0 { Err(io::Error::last_os_error()) } else { Ok(ret as usize) } } } } #[cfg(not(any(unix, windows)))] pub(crate) fn send_to_with_src_ipv6( _socket: &UdpSocket, _src_ip: Ipv6Addr, _src_ifindex: u32, _dst_addr: SocketAddrV6, _buf: &[u8], ) -> io::Result { Err(io::Error::new( io::ErrorKind::Unsupported, "sending UDP with a selected IPv6 source is not supported on this platform", )) }