From eaaf574989a9aba8c2bfbe01a7d94e8bf4b63904 Mon Sep 17 00:00:00 2001 From: Brian May Date: Sun, 24 Jul 2022 09:31:19 +1000 Subject: [PATCH] Add support for RecvOrigDstAddr on Linux Fixes #1767 --- src/sys/socket/mod.rs | 22 +++++ src/sys/socket/sockopt.rs | 14 ++++ test/sys/test_socket.rs | 172 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index e789c116c3..b12b38d602 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -757,6 +757,14 @@ pub enum ControlMessageOwned { #[cfg(feature = "net")] #[cfg_attr(docsrs, doc(cfg(feature = "net")))] Ipv4RecvDstAddr(libc::in_addr), + #[cfg(all(target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv4RecvOrigDstAddr(libc::in_addr), + #[cfg(all(target_os = "linux"))] + #[cfg(feature = "net")] + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + Ipv6RecvOrigDstAddr(libc::in_addr), /// UDP Generic Receive Offload (GRO) allows receiving multiple UDP /// packets from a single sender. @@ -918,6 +926,12 @@ impl ControlMessageOwned { }, #[cfg(target_os = "linux")] #[cfg(feature = "net")] + (libc::IPPROTO_IP, libc::IP_RECVORIGDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::in_addr); + ControlMessageOwned::Ipv4RecvOrigDstAddr(dl) + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] (libc::SOL_UDP, libc::UDP_GRO) => { let gso_size: u16 = ptr::read_unaligned(p as *const _); ControlMessageOwned::UdpGroSegments(gso_size) @@ -939,6 +953,14 @@ impl ControlMessageOwned { let (err, addr) = Self::recv_err_helper::(p, len); ControlMessageOwned::Ipv6RecvErr(err, addr) }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] + (libc::IPPROTO_IPV6, libc::IPV6_RECVORIGDSTADDR) => { + let dl = ptr::read_unaligned(p as *const libc::in_addr); + ControlMessageOwned::Ipv6RecvOrigDstAddr(dl) + }, + #[cfg(target_os = "linux")] + #[cfg(feature = "net")] (_, _) => { let sl = slice::from_raw_parts(p, len); let ucmsg = UnknownCmsg(*header, Vec::::from(sl)); diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index 8f85748f20..857861726a 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -576,6 +576,13 @@ sockopt_impl!( Ipv4RecvDstAddr, Both, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, bool); #[cfg(target_os = "linux")] #[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv4RecvOrigDstAddr, Both, libc::IPPROTO_IP, libc::IP_RECVORIGDSTADDR, bool); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] sockopt_impl!( #[cfg_attr(docsrs, doc(cfg(feature = "net")))] #[allow(missing_docs)] @@ -621,6 +628,13 @@ sockopt_impl!( sockopt_impl!( /// Set the unicast hop limit for the socket. Ipv6Ttl, Both, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS, libc::c_int); +#[cfg(target_os = "linux")] +#[cfg(feature = "net")] +sockopt_impl!( + #[cfg_attr(docsrs, doc(cfg(feature = "net")))] + /// The `recvmsg(2)` call will return the destination IP address for a UDP + /// datagram. + Ipv6RecvOrigDstAddr, Both, libc::IPPROTO_IPV6, libc::IPV6_RECVORIGDSTADDR, bool); #[cfg(any(target_os = "ios", target_os = "macos"))] sockopt_impl!( /// Set "don't fragment packet" flag on the IP packet. diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 56819c9f0d..2f44d0f41b 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -1745,6 +1745,178 @@ pub fn test_recvif() { } } +#[cfg(any( + target_os = "linux", +))] +#[test] +pub fn test_recvif_ipv4() { + use nix::sys::socket::sockopt::{Ipv4RecvOrigDstAddr}; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn}; + use nix::sys::socket::{getsockname, setsockopt, socket}; + use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; + use std::io::{IoSlice, IoSliceMut}; + + let lo_ifaddr = loopback_address(AddressFamily::Inet); + let (_lo_name, lo) = match lo_ifaddr { + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv4 address on interface"), + ), + None => return, + }; + let receive = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive, &lo).expect("bind failed"); + let sa: SockaddrIn = getsockname(receive).expect("getsockname failed"); + setsockopt(receive, Ipv4RecvOrigDstAddr, &true) + .expect("setsockopt IP_RECVORIGDSTADDR failed"); + + { + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let send = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); + } + + { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!(libc::in_addr); + let msg = recvmsg::<()>( + receive, + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .expect("recvmsg failed"); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + + let mut rx_recvorigdstaddr = false; + for cmsg in msg.cmsgs() { + match cmsg { + ControlMessageOwned::Ipv4RecvOrigDstAddr(addr) => { + rx_recvorigdstaddr = true; + if let Some(sin) = lo.as_sockaddr_in() { + assert_eq!(sin.as_ref().sin_addr.s_addr, + addr.s_addr, + "unexpected destination address (expected {}, got {})", + sin.as_ref().sin_addr.s_addr, + addr.s_addr); + } else { + panic!("unexpected Sockaddr"); + } + } + _ => panic!("unexpected additional control msg"), + } + } + assert!(rx_recvorigdstaddr); + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + } +} + +#[cfg(any( + target_os = "linux", +))] +#[test] +pub fn test_recvif_ipv6() { + use nix::sys::socket::sockopt::{Ipv6RecvOrigDstAddr}; + use nix::sys::socket::{bind, SockFlag, SockType, SockaddrIn6}; + use nix::sys::socket::{getsockname, setsockopt, socket}; + use nix::sys::socket::{recvmsg, sendmsg, ControlMessageOwned, MsgFlags}; + use std::io::{IoSlice, IoSliceMut}; + + let lo_ifaddr = loopback_address(AddressFamily::Inet6); + let (_lo_name, lo) = match lo_ifaddr { + Some(ifaddr) => ( + ifaddr.interface_name, + ifaddr.address.expect("Expect IPv6 address on interface"), + ), + None => return, + }; + let receive = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("receive socket failed"); + bind(receive, &lo).expect("bind failed"); + let sa: SockaddrIn6 = getsockname(receive).expect("getsockname failed"); + setsockopt(receive, Ipv6RecvOrigDstAddr, &true) + .expect("setsockopt IP_RECVORIGDSTADDR failed"); + + { + let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let iov = [IoSlice::new(&slice)]; + + let send = socket( + AddressFamily::Inet6, + SockType::Datagram, + SockFlag::empty(), + None, + ) + .expect("send socket failed"); + sendmsg(send, &iov, &[], MsgFlags::empty(), Some(&sa)) + .expect("sendmsg failed"); + } + + { + let mut buf = [0u8; 8]; + let mut iovec = [IoSliceMut::new(&mut buf)]; + let mut space = cmsg_space!(libc::in6_addr); + let msg = recvmsg::<()>( + receive, + &mut iovec, + Some(&mut space), + MsgFlags::empty(), + ) + .expect("recvmsg failed"); + assert!(!msg + .flags + .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); + assert_eq!(msg.cmsgs().count(), 1, "expected 1 cmsgs"); + + let mut rx_recvorigdstaddr = false; + for cmsg in msg.cmsgs() { + match cmsg { + ControlMessageOwned::Ipv6RecvOrigDstAddr(addr) => { + rx_recvorigdstaddr = true; + if let Some(sin) = lo.as_sockaddr_in() { + assert_eq!(sin.as_ref().sin_addr.s_addr, + addr.s_addr, + "unexpected destination address (expected {}, got {})", + sin.as_ref().sin_addr.s_addr, + addr.s_addr); + } else { + panic!("unexpected Sockaddr"); + } + } + _ => panic!("unexpected additional control msg"), + } + } + assert!(rx_recvorigdstaddr); + assert_eq!(msg.bytes, 8); + assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); + } +} + #[cfg(any( target_os = "android", target_os = "freebsd",