diff options
Diffstat (limited to 'test/sys/test_socket.rs')
-rw-r--r-- | test/sys/test_socket.rs | 634 |
1 files changed, 559 insertions, 75 deletions
diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index b4638485..2b0bc831 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -1,14 +1,15 @@ -use nix::ifaddrs::InterfaceAddress; -use nix::sys::socket::{AddressFamily, InetAddr, UnixAddr, getsockname}; +use nix::sys::socket::{AddressFamily, InetAddr, SockAddr, UnixAddr, getsockname, sockaddr, sockaddr_in6, sockaddr_storage_to_addr}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use std::mem::{self, MaybeUninit}; use std::net::{self, Ipv6Addr, SocketAddr, SocketAddrV6}; use std::os::unix::io::RawFd; use std::path::Path; use std::slice; use std::str::FromStr; -use libc::c_char; -use tempfile; +use libc::{c_char, sockaddr_storage}; +#[cfg(any(target_os = "linux", target_os= "android"))] +use crate::*; #[test] pub fn test_inetv4_addr_to_sock_addr() { @@ -27,13 +28,36 @@ pub fn test_inetv4_addr_to_sock_addr() { _ => panic!("nope"), } - assert_eq!(addr.to_str(), "127.0.0.1:3000"); + assert_eq!(addr.to_string(), "127.0.0.1:3000"); let inet = addr.to_std(); assert_eq!(actual, inet); } #[test] +pub fn test_inetv4_addr_roundtrip_sockaddr_storage_to_addr() { + let actual: net::SocketAddr = FromStr::from_str("127.0.0.1:3000").unwrap(); + let addr = InetAddr::from_std(&actual); + let sockaddr = SockAddr::new_inet(addr); + + let (storage, ffi_size) = { + let mut storage = MaybeUninit::<sockaddr_storage>::zeroed(); + let storage_ptr = storage.as_mut_ptr().cast::<sockaddr>(); + let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); + assert_eq!(mem::size_of::<sockaddr>(), ffi_size as usize); + unsafe { + storage_ptr.copy_from_nonoverlapping(ffi_ptr as *const sockaddr, 1); + (storage.assume_init(), ffi_size) + } + }; + + let from_storage = sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap(); + assert_eq!(from_storage, sockaddr); + let from_storage = sockaddr_storage_to_addr(&storage, mem::size_of::<sockaddr_storage>()).unwrap(); + assert_eq!(from_storage, sockaddr); +} + +#[test] pub fn test_inetv6_addr_to_sock_addr() { let port: u16 = 3000; let flowinfo: u32 = 1; @@ -54,6 +78,33 @@ pub fn test_inetv6_addr_to_sock_addr() { assert_eq!(actual, addr.to_std()); } +#[test] +pub fn test_inetv6_addr_roundtrip_sockaddr_storage_to_addr() { + let port: u16 = 3000; + let flowinfo: u32 = 1; + let scope_id: u32 = 2; + let ip: Ipv6Addr = "fe80::1".parse().unwrap(); + + let actual = SocketAddr::V6(SocketAddrV6::new(ip, port, flowinfo, scope_id)); + let addr = InetAddr::from_std(&actual); + let sockaddr = SockAddr::new_inet(addr); + + let (storage, ffi_size) = { + let mut storage = MaybeUninit::<sockaddr_storage>::zeroed(); + let storage_ptr = storage.as_mut_ptr().cast::<sockaddr_in6>(); + let (ffi_ptr, ffi_size) = sockaddr.as_ffi_pair(); + assert_eq!(mem::size_of::<sockaddr_in6>(), ffi_size as usize); + unsafe { + storage_ptr.copy_from_nonoverlapping((ffi_ptr as *const sockaddr).cast::<sockaddr_in6>(), 1); + (storage.assume_init(), ffi_size) + } + }; + + let from_storage = sockaddr_storage_to_addr(&storage, ffi_size as usize).unwrap(); + assert_eq!(from_storage, sockaddr); + let from_storage = sockaddr_storage_to_addr(&storage, mem::size_of::<sockaddr_storage>()).unwrap(); + assert_eq!(from_storage, sockaddr); +} #[test] pub fn test_path_to_sock_addr() { @@ -80,7 +131,7 @@ pub fn test_addr_equality_path() { let path = "/foo/bar"; let actual = Path::new(path); let addr1 = UnixAddr::new(actual).unwrap(); - let mut addr2 = addr1.clone(); + let mut addr2 = addr1; addr2.0.sun_path[10] = 127; @@ -143,8 +194,8 @@ pub fn test_getsockname() { .expect("socket failed"); let sockaddr = SockAddr::new_unix(&sockname).unwrap(); bind(sock, &sockaddr).expect("bind failed"); - assert_eq!(sockaddr.to_str(), - getsockname(sock).expect("getsockname failed").to_str()); + assert_eq!(sockaddr.to_string(), + getsockname(sock).expect("getsockname failed").to_string()); } #[test] @@ -167,7 +218,7 @@ mod recvfrom { use std::thread; use super::*; - const MSG: &'static [u8] = b"Hello, World!"; + const MSG: &[u8] = b"Hello, World!"; fn sendrecv<Fs, Fr>(rsock: RawFd, ssock: RawFd, f_send: Fs, mut f_recv: Fr) -> Option<SockAddr> where @@ -237,6 +288,9 @@ mod recvfrom { use nix::sys::socket::sockopt::{UdpGroSegment, UdpGsoSegment}; #[test] + // Disable the test under emulation because it fails in Cirrus-CI. Lack + // of QEMU support is suspected. + #[cfg_attr(qemu, ignore)] pub fn gso() { require_kernel_version!(udp_offload::gso, ">= 4.18"); @@ -288,6 +342,9 @@ mod recvfrom { } #[test] + // Disable the test on emulated platforms because it fails in Cirrus-CI. + // Lack of QEMU support is suspected. + #[cfg_attr(qemu, ignore)] pub fn gro() { require_kernel_version!(udp_offload::gro, ">= 5.3"); @@ -311,6 +368,7 @@ mod recvfrom { target_os = "freebsd", target_os = "netbsd", ))] + #[allow(clippy::vec_init_then_push)] #[test] pub fn udp_sendmmsg() { use nix::sys::uio::IoVec; @@ -360,7 +418,7 @@ mod recvfrom { } sendmmsg(s, msgs.iter(), flags) .map(move |sent_bytes| { - assert!(sent_bytes.len() >= 1); + assert!(!sent_bytes.is_empty()); for sent in &sent_bytes { assert_eq!(*sent, m.len()); } @@ -418,7 +476,7 @@ mod recvfrom { for iov in &iovs { msgs.push_back(RecvMmsgData { - iov: iov, + iov, cmsg_buffer: None, }) }; @@ -437,12 +495,81 @@ mod recvfrom { send_thread.join().unwrap(); } + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + ))] + #[test] + pub fn udp_recvmmsg_dontwait_short_read() { + use nix::sys::uio::IoVec; + use nix::sys::socket::{MsgFlags, recvmmsg}; + + const NUM_MESSAGES_SENT: usize = 2; + const DATA: [u8; 4] = [1,2,3,4]; + + let std_sa = SocketAddr::from_str("127.0.0.1:6799").unwrap(); + let inet_addr = InetAddr::from_std(&std_sa); + let sock_addr = SockAddr::new_inet(inet_addr); + + let rsock = socket(AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None + ).unwrap(); + bind(rsock, &sock_addr).unwrap(); + let ssock = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None, + ).expect("send socket failed"); + + let send_thread = thread::spawn(move || { + for _ in 0..NUM_MESSAGES_SENT { + sendto(ssock, &DATA[..], &sock_addr, MsgFlags::empty()).unwrap(); + } + }); + // Ensure we've sent all the messages before continuing so `recvmmsg` + // will return right away + send_thread.join().unwrap(); + + let mut msgs = std::collections::LinkedList::new(); + + // Buffers to receive >`NUM_MESSAGES_SENT` messages to ensure `recvmmsg` + // will return when there are fewer than requested messages in the + // kernel buffers when using `MSG_DONTWAIT`. + let mut receive_buffers = [[0u8; 32]; NUM_MESSAGES_SENT + 2]; + let iovs: Vec<_> = receive_buffers.iter_mut().map(|buf| { + [IoVec::from_mut_slice(&mut buf[..])] + }).collect(); + + for iov in &iovs { + msgs.push_back(RecvMmsgData { + iov, + cmsg_buffer: None, + }) + }; + + let res = recvmmsg(rsock, &mut msgs, MsgFlags::MSG_DONTWAIT, None).expect("recvmmsg"); + assert_eq!(res.len(), NUM_MESSAGES_SENT); + + for RecvMsg { address, bytes, .. } in res.into_iter() { + assert_eq!(AddressFamily::Inet, address.unwrap().family()); + assert_eq!(DATA.len(), bytes); + } + + for buf in &receive_buffers[..NUM_MESSAGES_SENT] { + assert_eq!(&buf[..DATA.len()], DATA); + } + } } // Test error handling of our recvmsg wrapper #[test] pub fn test_recvmsg_ebadf() { - use nix::Error; use nix::errno::Errno; use nix::sys::socket::{MsgFlags, recvmsg}; use nix::sys::uio::IoVec; @@ -451,12 +578,12 @@ pub fn test_recvmsg_ebadf() { let iov = [IoVec::from_mut_slice(&mut buf[..])]; let fd = -1; // Bad file descriptor let r = recvmsg(fd, &iov, None, MsgFlags::empty()); - assert_eq!(r.err().unwrap(), Error::Sys(Errno::EBADF)); + assert_eq!(r.err().unwrap(), Errno::EBADF); } // Disable the test on emulated platforms due to a bug in QEMU versions < // 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch="i686")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] pub fn test_scm_rights() { use nix::sys::uio::IoVec; @@ -510,8 +637,8 @@ pub fn test_scm_rights() { } // Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "i686")), ignore)] #[cfg(any(target_os = "linux", target_os= "android"))] +#[cfg_attr(qemu, ignore)] #[test] pub fn test_af_alg_cipher() { use libc; @@ -522,12 +649,13 @@ pub fn test_af_alg_cipher() { ControlMessage, MsgFlags}; use nix::sys::socket::sockopt::AlgSetKey; + skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352"); // Travis's seccomp profile blocks AF_ALG // https://docs.docker.com/engine/security/seccomp/ skip_if_seccomp!(test_af_alg_cipher); let alg_type = "skcipher"; - let alg_name = "ctr(aes)"; + let alg_name = "ctr-aes-aesni"; // 256-bits secret key let key = vec![0u8; 32]; // 16-bytes IV @@ -577,9 +705,10 @@ pub fn test_af_alg_cipher() { assert_eq!(decrypted, payload); } -// Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "i686")), ignore)] +// Disable the test on emulated platforms due to not enabled support of AF_ALG +// in QEMU from rust cross #[cfg(any(target_os = "linux", target_os= "android"))] +#[cfg_attr(qemu, ignore)] #[test] pub fn test_af_alg_aead() { use libc::{ALG_OP_DECRYPT, ALG_OP_ENCRYPT}; @@ -591,6 +720,7 @@ pub fn test_af_alg_aead() { ControlMessage, MsgFlags}; use nix::sys::socket::sockopt::{AlgSetKey, AlgSetAeadAuthSize}; + skip_if_cirrus!("Fails for an unknown reason Cirrus CI. Bug #1352"); // Travis's seccomp profile blocks AF_ALG // https://docs.docker.com/engine/security/seccomp/ skip_if_seccomp!(test_af_alg_aead); @@ -684,6 +814,7 @@ pub fn test_af_alg_aead() { target_os = "netbsd"))] #[test] pub fn test_sendmsg_ipv4packetinfo() { + use cfg_if::cfg_if; use nix::sys::uio::IoVec; use nix::sys::socket::{socket, sendmsg, bind, AddressFamily, SockType, SockFlag, SockAddr, @@ -705,11 +836,21 @@ pub fn test_sendmsg_ipv4packetinfo() { let iov = [IoVec::from_slice(&slice)]; if let InetAddr::V4(sin) = inet_addr { - let pi = libc::in_pktinfo { - ipi_ifindex: 0, /* Unspecified interface */ - ipi_addr: libc::in_addr { s_addr: 0 }, - ipi_spec_dst: sin.sin_addr, - }; + cfg_if! { + if #[cfg(target_os = "netbsd")] { + drop(sin); + let pi = libc::in_pktinfo { + ipi_ifindex: 0, /* Unspecified interface */ + ipi_addr: libc::in_addr { s_addr: 0 }, + }; + } else { + let pi = libc::in_pktinfo { + ipi_ifindex: 0, /* Unspecified interface */ + ipi_addr: libc::in_addr { s_addr: 0 }, + ipi_spec_dst: sin.sin_addr, + }; + } + } let cmsg = [ControlMessage::Ipv4PacketInfo(&pi)]; @@ -734,7 +875,6 @@ pub fn test_sendmsg_ipv4packetinfo() { target_os = "freebsd"))] #[test] pub fn test_sendmsg_ipv6packetinfo() { - use nix::Error; use nix::errno::Errno; use nix::sys::uio::IoVec; use nix::sys::socket::{socket, sendmsg, bind, @@ -751,12 +891,9 @@ pub fn test_sendmsg_ipv6packetinfo() { let inet_addr = InetAddr::from_std(&std_sa); let sock_addr = SockAddr::new_inet(inet_addr); - match bind(sock, &sock_addr) { - Err(Error::Sys(Errno::EADDRNOTAVAIL)) => { - println!("IPv6 not available, skipping test."); - return; - }, - _ => (), + if let Err(Errno::EADDRNOTAVAIL) = bind(sock, &sock_addr) { + println!("IPv6 not available, skipping test."); + return; } let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; @@ -780,7 +917,7 @@ pub fn test_sendmsg_ipv6packetinfo() { /// Tests that passing multiple fds using a single `ControlMessage` works. // Disable the test on emulated platforms due to a bug in QEMU versions < // 2.12.0. https://bugs.launchpad.net/qemu/+bug/1701808 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch="i686")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] fn test_scm_rights_single_cmsg_multiple_fds() { use std::os::unix::net::UnixDatagram; @@ -927,9 +1064,9 @@ fn test_scm_credentials() { /// Ensure that we can send `SCM_CREDENTIALS` and `SCM_RIGHTS` with a single /// `sendmsg` call. #[cfg(any(target_os = "android", target_os = "linux"))] -// qemu's handling of multiple cmsgs is bugged, ignore tests on non-x86 +// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation // see https://bugs.launchpad.net/qemu/+bug/1781280 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "x86")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] fn test_scm_credentials_and_rights() { use libc; @@ -941,9 +1078,9 @@ fn test_scm_credentials_and_rights() { /// Ensure that passing a an oversized control message buffer to recvmsg /// still works. #[cfg(any(target_os = "android", target_os = "linux"))] -// qemu's handling of multiple cmsgs is bugged, ignore tests on non-x86 +// qemu's handling of multiple cmsgs is bugged, ignore tests under emulation // see https://bugs.launchpad.net/qemu/+bug/1781280 -#[cfg_attr(not(any(target_arch = "x86_64", target_arch = "x86")), ignore)] +#[cfg_attr(qemu, ignore)] #[test] fn test_too_large_cmsgspace() { let space = vec![0u8; 1024]; @@ -954,7 +1091,7 @@ fn test_too_large_cmsgspace() { fn test_impl_scm_credentials_and_rights(mut space: Vec<u8>) { use libc::ucred; use nix::sys::uio::IoVec; - use nix::unistd::{pipe, read, write, close, getpid, getuid, getgid}; + use nix::unistd::{pipe, write, close, getpid, getuid, getgid}; use nix::sys::socket::{socketpair, sendmsg, recvmsg, setsockopt, SockType, SockFlag, ControlMessage, ControlMessageOwned, MsgFlags}; @@ -1064,7 +1201,6 @@ pub fn test_unixdomain() { #[cfg(any(target_os = "macos", target_os = "ios"))] #[test] pub fn test_syscontrol() { - use nix::Error; use nix::errno::Errno; use nix::sys::socket::{socket, SockAddr, SockType, SockFlag, SockProtocol}; @@ -1072,7 +1208,7 @@ pub fn test_syscontrol() { SockFlag::empty(), SockProtocol::KextControl) .expect("socket failed"); let _sockaddr = SockAddr::new_sys_control(fd, "com.apple.net.utun_control", 0).expect("resolving sys_control name failed"); - assert_eq!(SockAddr::new_sys_control(fd, "foo.bar.lol", 0).err(), Some(Error::Sys(Errno::ENOENT))); + assert_eq!(SockAddr::new_sys_control(fd, "foo.bar.lol", 0).err(), Some(Errno::ENOENT)); // requires root privileges // connect(fd, &sockaddr).expect("connect failed"); @@ -1087,11 +1223,10 @@ pub fn test_syscontrol() { target_os = "netbsd", target_os = "openbsd", ))] -fn loopback_address(family: AddressFamily) -> Option<InterfaceAddress> { +fn loopback_address(family: AddressFamily) -> Option<nix::ifaddrs::InterfaceAddress> { use std::io; use std::io::Write; use nix::ifaddrs::getifaddrs; - use nix::sys::socket::SockAddr; use nix::net::if_::*; let addrs = match getifaddrs() { @@ -1134,10 +1269,13 @@ fn loopback_address(family: AddressFamily) -> Option<InterfaceAddress> { target_os = "netbsd", ))] // qemu doesn't seem to be emulating this correctly in these architectures -#[cfg_attr(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc64", +#[cfg_attr(all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) ), ignore)] #[test] pub fn test_recv_ipv4pktinfo() { @@ -1224,10 +1362,13 @@ pub fn test_recv_ipv4pktinfo() { target_os = "openbsd", ))] // qemu doesn't seem to be emulating this correctly in these architectures -#[cfg_attr(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc64", +#[cfg_attr(all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) ), ignore)] #[test] pub fn test_recvif() { @@ -1315,8 +1456,8 @@ pub fn test_recvif() { _ => panic!("unexpected additional control msg"), } } - assert_eq!(rx_recvif, true); - assert_eq!(rx_recvdstaddr, true); + assert!(rx_recvif); + assert!(rx_recvdstaddr); assert_eq!(msg.bytes, 8); assert_eq!( iovec[0].as_slice(), @@ -1335,10 +1476,13 @@ pub fn test_recvif() { target_os = "openbsd", ))] // qemu doesn't seem to be emulating this correctly in these architectures -#[cfg_attr(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "powerpc64", +#[cfg_attr(all( + qemu, + any( + target_arch = "mips", + target_arch = "mips64", + target_arch = "powerpc64", + ) ), ignore)] #[test] pub fn test_recv_ipv6pktinfo() { @@ -1395,18 +1539,16 @@ pub fn test_recv_ipv6pktinfo() { ); let mut cmsgs = msg.cmsgs(); - match cmsgs.next() { - Some(ControlMessageOwned::Ipv6PacketInfo(pktinfo)) => { - let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); - assert_eq!( - pktinfo.ipi6_ifindex, - i, - "unexpected ifindex (expected {}, got {})", - i, - pktinfo.ipi6_ifindex - ); - } - _ => (), + if let Some(ControlMessageOwned::Ipv6PacketInfo(pktinfo)) = cmsgs.next() + { + let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); + assert_eq!( + pktinfo.ipi6_ifindex as libc::c_uint, + i, + "unexpected ifindex (expected {}, got {})", + i, + pktinfo.ipi6_ifindex + ); } assert!(cmsgs.next().is_none(), "unexpected additional control msg"); assert_eq!(msg.bytes, 8); @@ -1418,10 +1560,10 @@ pub fn test_recv_ipv6pktinfo() { } #[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg_attr(graviton, ignore = "Not supported by the CI environment")] #[test] pub fn test_vsock() { use libc; - use nix::Error; use nix::errno::Errno; use nix::sys::socket::{AddressFamily, socket, bind, connect, listen, SockAddr, SockType, SockFlag}; @@ -1434,16 +1576,10 @@ pub fn test_vsock() { SockFlag::empty(), None) .expect("socket failed"); - // VMADDR_CID_HYPERVISOR and VMADDR_CID_LOCAL are reserved, so we expect - // an EADDRNOTAVAIL error. + // VMADDR_CID_HYPERVISOR is reserved, so we expect an EADDRNOTAVAIL error. let sockaddr = SockAddr::new_vsock(libc::VMADDR_CID_HYPERVISOR, port); assert_eq!(bind(s1, &sockaddr).err(), - Some(Error::Sys(Errno::EADDRNOTAVAIL))); - - let sockaddr = SockAddr::new_vsock(libc::VMADDR_CID_LOCAL, port); - assert_eq!(bind(s1, &sockaddr).err(), - Some(Error::Sys(Errno::EADDRNOTAVAIL))); - + Some(Errno::EADDRNOTAVAIL)); let sockaddr = SockAddr::new_vsock(libc::VMADDR_CID_ANY, port); assert_eq!(bind(s1, &sockaddr), Ok(())); @@ -1468,3 +1604,351 @@ pub fn test_vsock() { close(s1).unwrap(); thr.join().unwrap(); } + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +#[cfg(all(target_os = "linux"))] +#[test] +fn test_recvmsg_timestampns() { + use nix::sys::socket::*; + use nix::sys::uio::IoVec; + use nix::sys::time::*; + use std::time::*; + + // Set up + let message = "Ohayō!".as_bytes(); + let in_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None).unwrap(); + setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); + bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); + let address = getsockname(in_socket).unwrap(); + // Get initial time + let time0 = SystemTime::now(); + // Send the message + let iov = [IoVec::from_slice(message)]; + let flags = MsgFlags::empty(); + let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + assert_eq!(message.len(), l); + // Receive the message + let mut buffer = vec![0u8; message.len()]; + let mut cmsgspace = nix::cmsg_space!(TimeSpec); + let iov = [IoVec::from_mut_slice(&mut buffer)]; + let r = recvmsg(in_socket, &iov, Some(&mut cmsgspace), flags).unwrap(); + let rtime = match r.cmsgs().next() { + Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, + Some(_) => panic!("Unexpected control message"), + None => panic!("No control message") + }; + // Check the final time + let time1 = SystemTime::now(); + // the packet's received timestamp should lie in-between the two system + // times, unless the system clock was adjusted in the meantime. + let rduration = Duration::new(rtime.tv_sec() as u64, + rtime.tv_nsec() as u32); + assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration); + assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap()); + // Close socket + nix::unistd::close(in_socket).unwrap(); +} + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +#[cfg(all(target_os = "linux"))] +#[test] +fn test_recvmmsg_timestampns() { + use nix::sys::socket::*; + use nix::sys::uio::IoVec; + use nix::sys::time::*; + use std::time::*; + + // Set up + let message = "Ohayō!".as_bytes(); + let in_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None).unwrap(); + setsockopt(in_socket, sockopt::ReceiveTimestampns, &true).unwrap(); + let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); + bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); + let address = getsockname(in_socket).unwrap(); + // Get initial time + let time0 = SystemTime::now(); + // Send the message + let iov = [IoVec::from_slice(message)]; + let flags = MsgFlags::empty(); + let l = sendmsg(in_socket, &iov, &[], flags, Some(&address)).unwrap(); + assert_eq!(message.len(), l); + // Receive the message + let mut buffer = vec![0u8; message.len()]; + let mut cmsgspace = nix::cmsg_space!(TimeSpec); + let iov = [IoVec::from_mut_slice(&mut buffer)]; + let mut data = vec![ + RecvMmsgData { + iov, + cmsg_buffer: Some(&mut cmsgspace), + }, + ]; + let r = recvmmsg(in_socket, &mut data, flags, None).unwrap(); + let rtime = match r[0].cmsgs().next() { + Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime, + Some(_) => panic!("Unexpected control message"), + None => panic!("No control message") + }; + // Check the final time + let time1 = SystemTime::now(); + // the packet's received timestamp should lie in-between the two system + // times, unless the system clock was adjusted in the meantime. + let rduration = Duration::new(rtime.tv_sec() as u64, + rtime.tv_nsec() as u32); + assert!(time0.duration_since(UNIX_EPOCH).unwrap() <= rduration); + assert!(rduration <= time1.duration_since(UNIX_EPOCH).unwrap()); + // Close socket + nix::unistd::close(in_socket).unwrap(); +} + +// Disable the test on emulated platforms because it fails in Cirrus-CI. Lack +// of QEMU support is suspected. +#[cfg_attr(qemu, ignore)] +#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] +#[test] +fn test_recvmsg_rxq_ovfl() { + use nix::Error; + use nix::sys::socket::*; + use nix::sys::uio::IoVec; + use nix::sys::socket::sockopt::{RxqOvfl, RcvBuf}; + + let message = [0u8; 2048]; + let bufsize = message.len() * 2; + + let in_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None).unwrap(); + let out_socket = socket( + AddressFamily::Inet, + SockType::Datagram, + SockFlag::empty(), + None).unwrap(); + + let localhost = InetAddr::new(IpAddr::new_v4(127, 0, 0, 1), 0); + bind(in_socket, &SockAddr::new_inet(localhost)).unwrap(); + + let address = getsockname(in_socket).unwrap(); + connect(out_socket, &address).unwrap(); + + // Set SO_RXQ_OVFL flag. + setsockopt(in_socket, RxqOvfl, &1).unwrap(); + + // Set the receiver buffer size to hold only 2 messages. + setsockopt(in_socket, RcvBuf, &bufsize).unwrap(); + + let mut drop_counter = 0; + + for _ in 0..2 { + let iov = [IoVec::from_slice(&message)]; + let flags = MsgFlags::empty(); + + // Send the 3 messages (the receiver buffer can only hold 2 messages) + // to create an overflow. + for _ in 0..3 { + let l = sendmsg(out_socket, &iov, &[], flags, Some(&address)).unwrap(); + assert_eq!(message.len(), l); + } + + // Receive the message and check the drop counter if any. + loop { + let mut buffer = vec![0u8; message.len()]; + let mut cmsgspace = nix::cmsg_space!(u32); + + let iov = [IoVec::from_mut_slice(&mut buffer)]; + + match recvmsg( + in_socket, + &iov, + Some(&mut cmsgspace), + MsgFlags::MSG_DONTWAIT) { + Ok(r) => { + drop_counter = match r.cmsgs().next() { + Some(ControlMessageOwned::RxqOvfl(drop_counter)) => drop_counter, + Some(_) => panic!("Unexpected control message"), + None => 0, + }; + }, + Err(Error::EAGAIN) => { break; }, + _ => { panic!("unknown recvmsg() error"); }, + } + } + } + + // One packet lost. + assert_eq!(drop_counter, 1); + + // Close sockets + nix::unistd::close(in_socket).unwrap(); + nix::unistd::close(out_socket).unwrap(); +} + +#[cfg(any( + target_os = "linux", + target_os = "android", +))] +mod linux_errqueue { + use nix::sys::socket::*; + use super::{FromStr, SocketAddr}; + + // Send a UDP datagram to a bogus destination address and observe an ICMP error (v4). + // + // Disable the test on QEMU because QEMU emulation of IP_RECVERR is broken (as documented on PR + // #1514). + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recverr_v4() { + #[repr(u8)] + enum IcmpTypes { + DestUnreach = 3, // ICMP_DEST_UNREACH + } + #[repr(u8)] + enum IcmpUnreachCodes { + PortUnreach = 3, // ICMP_PORT_UNREACH + } + + test_recverr_impl::<sockaddr_in, _, _>( + "127.0.0.1:6800", + AddressFamily::Inet, + sockopt::Ipv4RecvErr, + libc::SO_EE_ORIGIN_ICMP, + IcmpTypes::DestUnreach as u8, + IcmpUnreachCodes::PortUnreach as u8, + // Closure handles protocol-specific testing and returns generic sock_extended_err for + // protocol-independent test impl. + |cmsg| { + if let ControlMessageOwned::Ipv4RecvErr(ext_err, err_addr) = cmsg { + if let Some(origin) = err_addr { + // Validate that our network error originated from 127.0.0.1:0. + assert_eq!(origin.sin_family, AddressFamily::Inet as _); + assert_eq!(Ipv4Addr(origin.sin_addr), Ipv4Addr::new(127, 0, 0, 1)); + assert_eq!(origin.sin_port, 0); + } else { + panic!("Expected some error origin"); + } + return *ext_err + } else { + panic!("Unexpected control message {:?}", cmsg); + } + }, + ) + } + + // Essentially the same test as v4. + // + // Disable the test on QEMU because QEMU emulation of IPV6_RECVERR is broken (as documented on + // PR #1514). + #[cfg_attr(qemu, ignore)] + #[test] + fn test_recverr_v6() { + #[repr(u8)] + enum IcmpV6Types { + DestUnreach = 1, // ICMPV6_DEST_UNREACH + } + #[repr(u8)] + enum IcmpV6UnreachCodes { + PortUnreach = 4, // ICMPV6_PORT_UNREACH + } + + test_recverr_impl::<sockaddr_in6, _, _>( + "[::1]:6801", + AddressFamily::Inet6, + sockopt::Ipv6RecvErr, + libc::SO_EE_ORIGIN_ICMP6, + IcmpV6Types::DestUnreach as u8, + IcmpV6UnreachCodes::PortUnreach as u8, + // Closure handles protocol-specific testing and returns generic sock_extended_err for + // protocol-independent test impl. + |cmsg| { + if let ControlMessageOwned::Ipv6RecvErr(ext_err, err_addr) = cmsg { + if let Some(origin) = err_addr { + // Validate that our network error originated from localhost:0. + assert_eq!(origin.sin6_family, AddressFamily::Inet6 as _); + assert_eq!( + Ipv6Addr(origin.sin6_addr), + Ipv6Addr::from_std(&"::1".parse().unwrap()), + ); + assert_eq!(origin.sin6_port, 0); + } else { + panic!("Expected some error origin"); + } + return *ext_err + } else { + panic!("Unexpected control message {:?}", cmsg); + } + }, + ) + } + + fn test_recverr_impl<SA, OPT, TESTF>(sa: &str, + af: AddressFamily, + opt: OPT, + ee_origin: u8, + ee_type: u8, + ee_code: u8, + testf: TESTF) + where + OPT: SetSockOpt<Val = bool>, + TESTF: FnOnce(&ControlMessageOwned) -> libc::sock_extended_err, + { + use nix::errno::Errno; + use nix::sys::uio::IoVec; + + const MESSAGE_CONTENTS: &str = "ABCDEF"; + + let sock_addr = { + let std_sa = SocketAddr::from_str(sa).unwrap(); + let inet_addr = InetAddr::from_std(&std_sa); + SockAddr::new_inet(inet_addr) + }; + let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None).unwrap(); + setsockopt(sock, opt, &true).unwrap(); + if let Err(e) = sendto(sock, MESSAGE_CONTENTS.as_bytes(), &sock_addr, MsgFlags::empty()) { + assert_eq!(e, Errno::EADDRNOTAVAIL); + println!("{:?} not available, skipping test.", af); + return; + } + + let mut buf = [0u8; 8]; + let iovec = [IoVec::from_mut_slice(&mut buf)]; + let mut cspace = cmsg_space!(libc::sock_extended_err, SA); + + let msg = recvmsg(sock, &iovec, Some(&mut cspace), MsgFlags::MSG_ERRQUEUE).unwrap(); + // The sent message / destination associated with the error is returned: + assert_eq!(msg.bytes, MESSAGE_CONTENTS.as_bytes().len()); + assert_eq!(&buf[..msg.bytes], MESSAGE_CONTENTS.as_bytes()); + // recvmsg(2): "The original destination address of the datagram that caused the error is + // supplied via msg_name;" however, this is not literally true. E.g., an earlier version + // of this test used 0.0.0.0 (::0) as the destination address, which was mutated into + // 127.0.0.1 (::1). + assert_eq!(msg.address, Some(sock_addr)); + + // Check for expected control message. + let ext_err = match msg.cmsgs().next() { + Some(cmsg) => testf(&cmsg), + None => panic!("No control message"), + }; + + assert_eq!(ext_err.ee_errno, libc::ECONNREFUSED as u32); + assert_eq!(ext_err.ee_origin, ee_origin); + // ip(7): ee_type and ee_code are set from the type and code fields of the ICMP (ICMPv6) + // header. + assert_eq!(ext_err.ee_type, ee_type); + assert_eq!(ext_err.ee_code, ee_code); + // ip(7): ee_info contains the discovered MTU for EMSGSIZE errors. + assert_eq!(ext_err.ee_info, 0); + } +} |