| #[cfg(linux_android)] |
| use crate::*; |
| use libc::c_char; |
| use nix::sys::socket::{getsockname, AddressFamily, UnixAddr}; |
| use std::collections::hash_map::DefaultHasher; |
| use std::hash::{Hash, Hasher}; |
| use std::net::{SocketAddrV4, SocketAddrV6}; |
| use std::os::unix::io::{AsRawFd, RawFd}; |
| use std::path::Path; |
| use std::slice; |
| use std::str::FromStr; |
| |
| #[cfg(target_os = "linux")] |
| #[cfg_attr(qemu, ignore)] |
| #[test] |
| pub fn test_timestamping() { |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, setsockopt, socket, sockopt::Timestamping, |
| ControlMessageOwned, MsgFlags, SockFlag, SockType, SockaddrIn, |
| TimestampingFlag, |
| }; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| let sock_addr = SockaddrIn::from_str("127.0.0.1:6797").unwrap(); |
| |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| nix::sys::socket::bind(rsock.as_raw_fd(), &sock_addr).unwrap(); |
| |
| setsockopt(&rsock, Timestamping, &TimestampingFlag::all()).unwrap(); |
| |
| let sbuf = [0u8; 2048]; |
| let mut rbuf = [0u8; 2048]; |
| let flags = MsgFlags::empty(); |
| let iov1 = [IoSlice::new(&sbuf)]; |
| let mut iov2 = [IoSliceMut::new(&mut rbuf)]; |
| |
| let mut cmsg = cmsg_space!(nix::sys::socket::Timestamps); |
| sendmsg(ssock.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); |
| let recv = |
| recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, Some(&mut cmsg), flags) |
| .unwrap(); |
| |
| let mut ts = None; |
| for c in recv.cmsgs().unwrap() { |
| if let ControlMessageOwned::ScmTimestampsns(timestamps) = c { |
| ts = Some(timestamps.system); |
| } |
| } |
| let ts = ts.expect("ScmTimestampns is present"); |
| let sys_time = |
| ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_REALTIME) |
| .unwrap(); |
| let diff = if ts > sys_time { |
| ts - sys_time |
| } else { |
| sys_time - ts |
| }; |
| assert!(std::time::Duration::from(diff).as_secs() < 60); |
| } |
| |
| #[cfg(target_os = "freebsd")] |
| #[test] |
| pub fn test_timestamping_realtime() { |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, setsockopt, socket, sockopt::ReceiveTimestamp, |
| sockopt::TsClock, ControlMessageOwned, MsgFlags, SockFlag, SockType, |
| SockaddrIn, SocketTimestamp, |
| }; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| let sock_addr = SockaddrIn::from_str("127.0.0.1:6792").unwrap(); |
| |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| nix::sys::socket::bind(rsock.as_raw_fd(), &sock_addr).unwrap(); |
| |
| setsockopt(&rsock, ReceiveTimestamp, &true).unwrap(); |
| setsockopt(&rsock, TsClock, &SocketTimestamp::SO_TS_REALTIME).unwrap(); |
| |
| let sbuf = [0u8; 2048]; |
| let mut rbuf = [0u8; 2048]; |
| let flags = MsgFlags::empty(); |
| let iov1 = [IoSlice::new(&sbuf)]; |
| let mut iov2 = [IoSliceMut::new(&mut rbuf)]; |
| |
| let mut cmsg = cmsg_space!(nix::sys::time::TimeVal); |
| sendmsg(ssock.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); |
| let recv = |
| recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, Some(&mut cmsg), flags) |
| .unwrap(); |
| |
| let mut ts = None; |
| for c in recv.cmsgs().unwrap() { |
| if let ControlMessageOwned::ScmRealtime(timeval) = c { |
| ts = Some(timeval); |
| } |
| } |
| let ts = ts.expect("ScmRealtime is present"); |
| let sys_time = |
| ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_REALTIME) |
| .unwrap(); |
| let diff = if ts > sys_time { |
| ts - sys_time |
| } else { |
| sys_time - ts |
| }; |
| assert!(std::time::Duration::from(diff).as_secs() < 60); |
| } |
| |
| #[cfg(target_os = "freebsd")] |
| #[test] |
| pub fn test_timestamping_monotonic() { |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, setsockopt, socket, sockopt::ReceiveTimestamp, |
| sockopt::TsClock, ControlMessageOwned, MsgFlags, SockFlag, SockType, |
| SockaddrIn, SocketTimestamp, |
| }; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| let sock_addr = SockaddrIn::from_str("127.0.0.1:6803").unwrap(); |
| |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| nix::sys::socket::bind(rsock.as_raw_fd(), &sock_addr).unwrap(); |
| |
| setsockopt(&rsock, ReceiveTimestamp, &true).unwrap(); |
| setsockopt(&rsock, TsClock, &SocketTimestamp::SO_TS_MONOTONIC).unwrap(); |
| |
| let sbuf = [0u8; 2048]; |
| let mut rbuf = [0u8; 2048]; |
| let flags = MsgFlags::empty(); |
| let iov1 = [IoSlice::new(&sbuf)]; |
| let mut iov2 = [IoSliceMut::new(&mut rbuf)]; |
| |
| let mut cmsg = cmsg_space!(nix::sys::time::TimeVal); |
| sendmsg(ssock.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); |
| let recv = |
| recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, Some(&mut cmsg), flags) |
| .unwrap(); |
| |
| let mut ts = None; |
| for c in recv.cmsgs().unwrap() { |
| if let ControlMessageOwned::ScmMonotonic(timeval) = c { |
| ts = Some(timeval); |
| } |
| } |
| let ts = ts.expect("ScmMonotonic is present"); |
| let sys_time = |
| ::nix::time::clock_gettime(::nix::time::ClockId::CLOCK_MONOTONIC) |
| .unwrap(); |
| let diff = sys_time - ts; // Monotonic clock sys_time must be greater |
| assert!(std::time::Duration::from(diff).as_secs() < 60); |
| } |
| |
| #[test] |
| pub fn test_path_to_sock_addr() { |
| let path = "/foo/bar"; |
| let actual = Path::new(path); |
| let addr = UnixAddr::new(actual).unwrap(); |
| |
| let expect: &[c_char] = |
| unsafe { slice::from_raw_parts(path.as_ptr().cast(), path.len()) }; |
| assert_eq!(unsafe { &(*addr.as_ptr()).sun_path[..8] }, expect); |
| |
| assert_eq!(addr.path(), Some(actual)); |
| } |
| |
| fn calculate_hash<T: Hash>(t: &T) -> u64 { |
| let mut s = DefaultHasher::new(); |
| t.hash(&mut s); |
| s.finish() |
| } |
| |
| #[test] |
| 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; |
| |
| unsafe { (*addr2.as_mut_ptr()).sun_path[10] = 127 }; |
| |
| assert_eq!(addr1, addr2); |
| assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); |
| } |
| |
| #[cfg(linux_android)] |
| #[test] |
| pub fn test_abstract_sun_path_too_long() { |
| let name = String::from("nix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0tesnix\0abstract\0testttttnix\0abstract\0test\0make\0sure\0this\0is\0long\0enough"); |
| let addr = UnixAddr::new_abstract(name.as_bytes()); |
| addr.expect_err("assertion failed"); |
| } |
| |
| #[cfg(linux_android)] |
| #[test] |
| pub fn test_addr_equality_abstract() { |
| let name = String::from("nix\0abstract\0test"); |
| let addr1 = UnixAddr::new_abstract(name.as_bytes()).unwrap(); |
| let mut addr2 = addr1; |
| |
| assert_eq!(addr1, addr2); |
| assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); |
| |
| unsafe { (*addr2.as_mut_ptr()).sun_path[17] = 127 }; |
| assert_ne!(addr1, addr2); |
| assert_ne!(calculate_hash(&addr1), calculate_hash(&addr2)); |
| } |
| |
| // Test getting/setting abstract addresses (without unix socket creation) |
| #[cfg(linux_android)] |
| #[test] |
| pub fn test_abstract_uds_addr() { |
| let empty = String::new(); |
| let addr = UnixAddr::new_abstract(empty.as_bytes()).unwrap(); |
| let sun_path: [u8; 0] = []; |
| assert_eq!(addr.as_abstract(), Some(&sun_path[..])); |
| |
| let name = String::from("nix\0abstract\0test"); |
| let addr = UnixAddr::new_abstract(name.as_bytes()).unwrap(); |
| let sun_path = [ |
| 110u8, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, 116, 101, |
| 115, 116, |
| ]; |
| assert_eq!(addr.as_abstract(), Some(&sun_path[..])); |
| assert_eq!(addr.path(), None); |
| |
| // Internally, name is null-prefixed (abstract namespace) |
| assert_eq!(unsafe { (*addr.as_ptr()).sun_path[0] }, 0); |
| } |
| |
| // Test getting an unnamed address (without unix socket creation) |
| #[cfg(linux_android)] |
| #[test] |
| pub fn test_unnamed_uds_addr() { |
| use crate::nix::sys::socket::SockaddrLike; |
| |
| let addr = UnixAddr::new_unnamed(); |
| |
| assert!(addr.is_unnamed()); |
| assert_eq!(addr.len(), 2); |
| assert!(addr.path().is_none()); |
| assert_eq!(addr.path_len(), 0); |
| |
| assert!(addr.as_abstract().is_none()); |
| } |
| |
| #[test] |
| pub fn test_getsockname() { |
| use nix::sys::socket::bind; |
| use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; |
| |
| let tempdir = tempfile::tempdir().unwrap(); |
| let sockname = tempdir.path().join("sock"); |
| let sock = socket( |
| AddressFamily::Unix, |
| SockType::Stream, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| let sockaddr = UnixAddr::new(&sockname).unwrap(); |
| bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); |
| assert_eq!( |
| sockaddr, |
| getsockname(sock.as_raw_fd()).expect("getsockname failed") |
| ); |
| } |
| |
| #[test] |
| pub fn test_socketpair() { |
| use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType}; |
| use nix::unistd::{read, write}; |
| |
| let (fd1, fd2) = socketpair( |
| AddressFamily::Unix, |
| SockType::Stream, |
| None, |
| SockFlag::empty(), |
| ) |
| .unwrap(); |
| write(&fd1, b"hello").unwrap(); |
| let mut buf = [0; 5]; |
| read(fd2.as_raw_fd(), &mut buf).unwrap(); |
| |
| assert_eq!(&buf[..], b"hello"); |
| } |
| |
| #[test] |
| pub fn test_recvmsg_sockaddr_un() { |
| use nix::sys::socket::{ |
| self, bind, socket, AddressFamily, MsgFlags, SockFlag, SockType, |
| }; |
| |
| let tempdir = tempfile::tempdir().unwrap(); |
| let sockname = tempdir.path().join("sock"); |
| let sock = socket( |
| AddressFamily::Unix, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| let sockaddr = UnixAddr::new(&sockname).unwrap(); |
| bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); |
| |
| // Send a message |
| let send_buffer = "hello".as_bytes(); |
| if let Err(e) = socket::sendmsg( |
| sock.as_raw_fd(), |
| &[std::io::IoSlice::new(send_buffer)], |
| &[], |
| MsgFlags::empty(), |
| Some(&sockaddr), |
| ) { |
| crate::skip!("Couldn't send ({e:?}), so skipping test"); |
| } |
| |
| // Receive the message |
| let mut recv_buffer = [0u8; 32]; |
| let mut iov = [std::io::IoSliceMut::new(&mut recv_buffer)]; |
| let received = |
| socket::recvmsg(sock.as_raw_fd(), &mut iov, None, MsgFlags::empty()) |
| .unwrap(); |
| // Check the address in the received message |
| assert_eq!(sockaddr, received.address.unwrap()); |
| } |
| |
| #[test] |
| pub fn test_std_conversions() { |
| use nix::sys::socket::*; |
| |
| let std_sa = SocketAddrV4::from_str("127.0.0.1:6789").unwrap(); |
| let sock_addr = SockaddrIn::from(std_sa); |
| assert_eq!(std_sa, sock_addr.into()); |
| |
| let std_sa = SocketAddrV6::from_str("[::1]:6000").unwrap(); |
| let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa); |
| assert_eq!(std_sa, sock_addr.into()); |
| } |
| |
| mod recvfrom { |
| use super::*; |
| use nix::sys::socket::*; |
| use nix::{errno::Errno, Result}; |
| use std::thread; |
| |
| const MSG: &[u8] = b"Hello, World!"; |
| |
| fn sendrecv<Fs, Fr>( |
| rsock: RawFd, |
| ssock: RawFd, |
| f_send: Fs, |
| mut f_recv: Fr, |
| ) -> Option<SockaddrStorage> |
| where |
| Fs: Fn(RawFd, &[u8], MsgFlags) -> Result<usize> + Send + 'static, |
| Fr: FnMut(usize, Option<SockaddrStorage>), |
| { |
| let mut buf: [u8; 13] = [0u8; 13]; |
| let mut l = 0; |
| let mut from = None; |
| |
| let send_thread = thread::spawn(move || { |
| let mut l = 0; |
| while l < std::mem::size_of_val(MSG) { |
| l += f_send(ssock, &MSG[l..], MsgFlags::empty()).unwrap(); |
| } |
| }); |
| |
| while l < std::mem::size_of_val(MSG) { |
| let (len, from_) = recvfrom(rsock, &mut buf[l..]).unwrap(); |
| f_recv(len, from_); |
| from = from_; |
| l += len; |
| } |
| assert_eq!(&buf, MSG); |
| send_thread.join().unwrap(); |
| from |
| } |
| |
| #[test] |
| pub fn stream() { |
| let (fd2, fd1) = socketpair( |
| AddressFamily::Unix, |
| SockType::Stream, |
| None, |
| SockFlag::empty(), |
| ) |
| .unwrap(); |
| // Ignore from for stream sockets |
| let _ = sendrecv(fd1.as_raw_fd(), fd2.as_raw_fd(), send, |_, _| {}); |
| } |
| |
| #[test] |
| pub fn udp() { |
| let std_sa = SocketAddrV4::from_str("127.0.0.1:6795").unwrap(); |
| let sock_addr = SockaddrIn::from(std_sa); |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| bind(rsock.as_raw_fd(), &sock_addr).unwrap(); |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| let from = sendrecv( |
| rsock.as_raw_fd(), |
| ssock.as_raw_fd(), |
| move |s, m, flags| sendto(s.as_raw_fd(), m, &sock_addr, flags), |
| |_, _| {}, |
| ); |
| // UDP sockets should set the from address |
| assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap()); |
| } |
| |
| #[cfg(target_os = "linux")] |
| mod udp_offload { |
| use super::*; |
| use nix::sys::socket::sockopt::{UdpGroSegment, UdpGsoSegment}; |
| use std::io::IoSlice; |
| |
| #[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"); |
| |
| // In this test, we send the data and provide a GSO segment size. |
| // Since we are sending the buffer of size 13, six UDP packets |
| // with size 2 and two UDP packet with size 1 will be sent. |
| let segment_size: u16 = 2; |
| |
| let sock_addr = SockaddrIn::new(127, 0, 0, 1, 6791); |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| |
| setsockopt(&rsock, UdpGsoSegment, &(segment_size as _)) |
| .expect("setsockopt UDP_SEGMENT failed"); |
| |
| bind(rsock.as_raw_fd(), &sock_addr).unwrap(); |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| |
| let mut num_packets_received: i32 = 0; |
| |
| sendrecv( |
| rsock.as_raw_fd(), |
| ssock.as_raw_fd(), |
| move |s, m, flags| { |
| let iov = [IoSlice::new(m)]; |
| let cmsg = ControlMessage::UdpGsoSegments(&segment_size); |
| sendmsg( |
| s.as_raw_fd(), |
| &iov, |
| &[cmsg], |
| flags, |
| Some(&sock_addr), |
| ) |
| }, |
| { |
| let num_packets_received_ref = &mut num_packets_received; |
| |
| move |len, _| { |
| // check that we receive UDP packets with payload size |
| // less or equal to segment size |
| assert!(len <= segment_size as usize); |
| *num_packets_received_ref += 1; |
| } |
| }, |
| ); |
| |
| // Buffer size is 13, we will receive six packets of size 2, |
| // and one packet of size 1. |
| assert_eq!(7, num_packets_received); |
| } |
| |
| #[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"); |
| |
| // It's hard to guarantee receiving GRO packets. Just checking |
| // that `setsockopt` doesn't fail with error |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| |
| setsockopt(&rsock, UdpGroSegment, &true) |
| .expect("setsockopt UDP_GRO failed"); |
| } |
| } |
| |
| #[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] |
| #[test] |
| pub fn udp_sendmmsg() { |
| use std::io::IoSlice; |
| |
| let std_sa = SocketAddrV4::from_str("127.0.0.1:6793").unwrap(); |
| let std_sa2 = SocketAddrV4::from_str("127.0.0.1:6794").unwrap(); |
| let sock_addr = SockaddrIn::from(std_sa); |
| let sock_addr2 = SockaddrIn::from(std_sa2); |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| bind(rsock.as_raw_fd(), &sock_addr).unwrap(); |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| |
| let from = sendrecv( |
| rsock.as_raw_fd(), |
| ssock.as_raw_fd(), |
| move |s, m, flags| { |
| let batch_size = 15; |
| let mut iovs = Vec::with_capacity(1 + batch_size); |
| let mut addrs = Vec::with_capacity(1 + batch_size); |
| let mut data = MultiHeaders::preallocate(1 + batch_size, None); |
| let iov = IoSlice::new(m); |
| // first chunk: |
| iovs.push([iov]); |
| addrs.push(Some(sock_addr)); |
| |
| for _ in 0..batch_size { |
| iovs.push([iov]); |
| addrs.push(Some(sock_addr2)); |
| } |
| |
| let res = sendmmsg(s, &mut data, &iovs, addrs, [], flags)?; |
| let mut sent_messages = 0; |
| let mut sent_bytes = 0; |
| for item in res { |
| sent_messages += 1; |
| sent_bytes += item.bytes; |
| } |
| // |
| assert_eq!(sent_messages, iovs.len()); |
| assert_eq!(sent_bytes, sent_messages * m.len()); |
| Ok(sent_messages) |
| }, |
| |_, _| {}, |
| ); |
| // UDP sockets should set the from address |
| assert_eq!(AddressFamily::Inet, from.unwrap().family().unwrap()); |
| } |
| |
| #[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] |
| #[test] |
| pub fn udp_recvmmsg() { |
| use nix::sys::socket::{recvmmsg, MsgFlags}; |
| use std::io::IoSliceMut; |
| |
| const NUM_MESSAGES_SENT: usize = 2; |
| const DATA: [u8; 2] = [1, 2]; |
| |
| let inet_addr = SocketAddrV4::from_str("127.0.0.1:6798").unwrap(); |
| let sock_addr = SockaddrIn::from(inet_addr); |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| bind(rsock.as_raw_fd(), &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.as_raw_fd(), |
| &DATA[..], |
| &sock_addr, |
| MsgFlags::empty(), |
| ) |
| .unwrap(); |
| } |
| }); |
| |
| let mut msgs = std::collections::LinkedList::new(); |
| |
| // Buffers to receive exactly `NUM_MESSAGES_SENT` messages |
| let mut receive_buffers = [[0u8; 32]; NUM_MESSAGES_SENT]; |
| msgs.extend( |
| receive_buffers |
| .iter_mut() |
| .map(|buf| [IoSliceMut::new(&mut buf[..])]), |
| ); |
| |
| let mut data = |
| MultiHeaders::<SockaddrIn>::preallocate(msgs.len(), None); |
| |
| let res: Vec<RecvMsg<SockaddrIn>> = recvmmsg( |
| rsock.as_raw_fd(), |
| &mut data, |
| msgs.iter_mut(), |
| MsgFlags::empty(), |
| None, |
| ) |
| .expect("recvmmsg") |
| .collect(); |
| assert_eq!(res.len(), DATA.len()); |
| |
| for RecvMsg { address, bytes, .. } in res.into_iter() { |
| assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap()); |
| assert_eq!(DATA.len(), bytes); |
| } |
| |
| for buf in &receive_buffers { |
| assert_eq!(&buf[..DATA.len()], DATA); |
| } |
| |
| send_thread.join().unwrap(); |
| } |
| |
| #[cfg(any(linux_android, target_os = "freebsd", target_os = "netbsd"))] |
| #[test] |
| pub fn udp_recvmmsg_dontwait_short_read() { |
| use nix::sys::socket::{recvmmsg, MsgFlags}; |
| use std::io::IoSliceMut; |
| |
| const NUM_MESSAGES_SENT: usize = 2; |
| const DATA: [u8; 4] = [1, 2, 3, 4]; |
| |
| let inet_addr = SocketAddrV4::from_str("127.0.0.1:6799").unwrap(); |
| let sock_addr = SockaddrIn::from(inet_addr); |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| bind(rsock.as_raw_fd(), &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.as_raw_fd(), |
| &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]; |
| msgs.extend( |
| receive_buffers |
| .iter_mut() |
| .map(|buf| [IoSliceMut::new(&mut buf[..])]), |
| ); |
| |
| let mut data = MultiHeaders::<SockaddrIn>::preallocate( |
| NUM_MESSAGES_SENT + 2, |
| None, |
| ); |
| |
| let res: Vec<RecvMsg<SockaddrIn>> = recvmmsg( |
| rsock.as_raw_fd(), |
| &mut data, |
| msgs.iter_mut(), |
| MsgFlags::MSG_DONTWAIT, |
| None, |
| ) |
| .expect("recvmmsg") |
| .collect(); |
| assert_eq!(res.len(), NUM_MESSAGES_SENT); |
| |
| for RecvMsg { address, bytes, .. } in res.into_iter() { |
| assert_eq!(AddressFamily::Inet, address.unwrap().family().unwrap()); |
| assert_eq!(DATA.len(), bytes); |
| } |
| |
| for buf in &receive_buffers[..NUM_MESSAGES_SENT] { |
| assert_eq!(&buf[..DATA.len()], DATA); |
| } |
| } |
| |
| #[test] |
| pub fn udp_inet6() { |
| let addr = std::net::Ipv6Addr::from_str("::1").unwrap(); |
| let rport = 6796; |
| let rstd_sa = SocketAddrV6::new(addr, rport, 0, 0); |
| let raddr = SockaddrIn6::from(rstd_sa); |
| let sport = 6798; |
| let sstd_sa = SocketAddrV6::new(addr, sport, 0, 0); |
| let saddr = SockaddrIn6::from(sstd_sa); |
| let rsock = socket( |
| AddressFamily::Inet6, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("receive socket failed"); |
| match bind(rsock.as_raw_fd(), &raddr) { |
| Err(Errno::EADDRNOTAVAIL) => { |
| println!("IPv6 not available, skipping test."); |
| return; |
| } |
| Err(e) => panic!("bind: {e}"), |
| Ok(()) => (), |
| } |
| let ssock = socket( |
| AddressFamily::Inet6, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| bind(ssock.as_raw_fd(), &saddr).unwrap(); |
| let from = sendrecv( |
| rsock.as_raw_fd(), |
| ssock.as_raw_fd(), |
| move |s, m, flags| sendto(s.as_raw_fd(), m, &raddr, flags), |
| |_, _| {}, |
| ); |
| assert_eq!(AddressFamily::Inet6, from.unwrap().family().unwrap()); |
| let osent_addr = from.unwrap(); |
| let sent_addr = osent_addr.as_sockaddr_in6().unwrap(); |
| assert_eq!(sent_addr.ip(), addr); |
| assert_eq!(sent_addr.port(), sport); |
| } |
| } |
| |
| // Test error handling of our recvmsg wrapper |
| #[test] |
| pub fn test_recvmsg_ebadf() { |
| use nix::errno::Errno; |
| use nix::sys::socket::{recvmsg, MsgFlags}; |
| use std::io::IoSliceMut; |
| |
| let mut buf = [0u8; 5]; |
| let mut iov = [IoSliceMut::new(&mut buf[..])]; |
| |
| let fd = -1; // Bad file descriptor |
| let r = recvmsg::<()>(fd.as_raw_fd(), &mut iov, None, MsgFlags::empty()); |
| |
| 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(qemu, ignore)] |
| #[test] |
| pub fn test_scm_rights() { |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage, |
| ControlMessageOwned, MsgFlags, SockFlag, SockType, |
| }; |
| use nix::unistd::{close, pipe, read, write}; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| let (fd1, fd2) = socketpair( |
| AddressFamily::Unix, |
| SockType::Stream, |
| None, |
| SockFlag::empty(), |
| ) |
| .unwrap(); |
| let (r, w) = pipe().unwrap(); |
| let mut received_r: Option<RawFd> = None; |
| |
| { |
| let iov = [IoSlice::new(b"hello")]; |
| let fds = [r.as_raw_fd()]; |
| let cmsg = ControlMessage::ScmRights(&fds); |
| assert_eq!( |
| sendmsg::<()>( |
| fd1.as_raw_fd(), |
| &iov, |
| &[cmsg], |
| MsgFlags::empty(), |
| None |
| ) |
| .unwrap(), |
| 5 |
| ); |
| } |
| |
| { |
| let mut buf = [0u8; 5]; |
| |
| let mut iov = [IoSliceMut::new(&mut buf[..])]; |
| let mut cmsgspace = cmsg_space!([RawFd; 1]); |
| let msg = recvmsg::<()>( |
| fd2.as_raw_fd(), |
| &mut iov, |
| Some(&mut cmsgspace), |
| MsgFlags::empty(), |
| ) |
| .unwrap(); |
| |
| for cmsg in msg.cmsgs().unwrap() { |
| if let ControlMessageOwned::ScmRights(fd) = cmsg { |
| assert_eq!(received_r, None); |
| assert_eq!(fd.len(), 1); |
| received_r = Some(fd[0]); |
| } else { |
| panic!("unexpected cmsg"); |
| } |
| } |
| assert_eq!(msg.bytes, 5); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| } |
| |
| let received_r = received_r.expect("Did not receive passed fd"); |
| // Ensure that the received file descriptor works |
| write(&w, b"world").unwrap(); |
| let mut buf = [0u8; 5]; |
| read(received_r.as_raw_fd(), &mut buf).unwrap(); |
| assert_eq!(&buf[..], b"world"); |
| close(received_r).unwrap(); |
| } |
| |
| // Disable the test on emulated platforms due to not enabled support of AF_ALG in QEMU from rust cross |
| #[cfg(linux_android)] |
| #[cfg_attr(qemu, ignore)] |
| #[test] |
| pub fn test_af_alg_cipher() { |
| use nix::sys::socket::sockopt::AlgSetKey; |
| use nix::sys::socket::{ |
| accept, bind, sendmsg, setsockopt, socket, AddressFamily, AlgAddr, |
| ControlMessage, MsgFlags, SockFlag, SockType, |
| }; |
| use nix::unistd::read; |
| use std::io::IoSlice; |
| |
| 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-aesni"; |
| // 256-bits secret key |
| let key = vec![0u8; 32]; |
| // 16-bytes IV |
| let iv_len = 16; |
| let iv = vec![1u8; iv_len]; |
| // 256-bytes plain payload |
| let payload_len = 256; |
| let payload = vec![2u8; payload_len]; |
| |
| let sock = socket( |
| AddressFamily::Alg, |
| SockType::SeqPacket, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| |
| let sockaddr = AlgAddr::new(alg_type, alg_name); |
| bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); |
| |
| assert_eq!(sockaddr.alg_name().to_string_lossy(), alg_name); |
| assert_eq!(sockaddr.alg_type().to_string_lossy(), alg_type); |
| |
| setsockopt(&sock, AlgSetKey::default(), &key).expect("setsockopt"); |
| let session_socket = accept(sock.as_raw_fd()).expect("accept failed"); |
| |
| let msgs = [ |
| ControlMessage::AlgSetOp(&libc::ALG_OP_ENCRYPT), |
| ControlMessage::AlgSetIv(iv.as_slice()), |
| ]; |
| let iov = IoSlice::new(&payload); |
| sendmsg::<()>( |
| session_socket.as_raw_fd(), |
| &[iov], |
| &msgs, |
| MsgFlags::empty(), |
| None, |
| ) |
| .expect("sendmsg encrypt"); |
| |
| // allocate buffer for encrypted data |
| let mut encrypted = vec![0u8; payload_len]; |
| let num_bytes = |
| read(session_socket.as_raw_fd(), &mut encrypted).expect("read encrypt"); |
| assert_eq!(num_bytes, payload_len); |
| |
| let iov = IoSlice::new(&encrypted); |
| |
| let iv = vec![1u8; iv_len]; |
| |
| let msgs = [ |
| ControlMessage::AlgSetOp(&libc::ALG_OP_DECRYPT), |
| ControlMessage::AlgSetIv(iv.as_slice()), |
| ]; |
| sendmsg::<()>( |
| session_socket.as_raw_fd(), |
| &[iov], |
| &msgs, |
| MsgFlags::empty(), |
| None, |
| ) |
| .expect("sendmsg decrypt"); |
| |
| // allocate buffer for decrypted data |
| let mut decrypted = vec![0u8; payload_len]; |
| let num_bytes = |
| read(session_socket.as_raw_fd(), &mut decrypted).expect("read decrypt"); |
| |
| assert_eq!(num_bytes, payload_len); |
| assert_eq!(decrypted, payload); |
| } |
| |
| // Disable the test on emulated platforms due to not enabled support of AF_ALG |
| // in QEMU from rust cross |
| #[cfg(linux_android)] |
| #[cfg_attr(qemu, ignore)] |
| #[test] |
| pub fn test_af_alg_aead() { |
| use libc::{ALG_OP_DECRYPT, ALG_OP_ENCRYPT}; |
| use nix::fcntl::{fcntl, FcntlArg, OFlag}; |
| use nix::sys::socket::sockopt::{AlgSetAeadAuthSize, AlgSetKey}; |
| use nix::sys::socket::{ |
| accept, bind, sendmsg, setsockopt, socket, AddressFamily, AlgAddr, |
| ControlMessage, MsgFlags, SockFlag, SockType, |
| }; |
| use nix::unistd::read; |
| use std::io::IoSlice; |
| |
| 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); |
| |
| let auth_size = 4usize; |
| let assoc_size = 16u32; |
| |
| let alg_type = "aead"; |
| let alg_name = "gcm(aes)"; |
| // 256-bits secret key |
| let key = vec![0u8; 32]; |
| // 12-bytes IV |
| let iv_len = 12; |
| let iv = vec![1u8; iv_len]; |
| // 256-bytes plain payload |
| let payload_len = 256; |
| let mut payload = |
| vec![2u8; payload_len + (assoc_size as usize) + auth_size]; |
| |
| for i in 0..assoc_size { |
| payload[i as usize] = 10; |
| } |
| |
| let len = payload.len(); |
| |
| for i in 0..auth_size { |
| payload[len - 1 - i] = 0; |
| } |
| |
| let sock = socket( |
| AddressFamily::Alg, |
| SockType::SeqPacket, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| |
| let sockaddr = AlgAddr::new(alg_type, alg_name); |
| bind(sock.as_raw_fd(), &sockaddr).expect("bind failed"); |
| |
| setsockopt(&sock, AlgSetAeadAuthSize, &auth_size) |
| .expect("setsockopt AlgSetAeadAuthSize"); |
| setsockopt(&sock, AlgSetKey::default(), &key) |
| .expect("setsockopt AlgSetKey"); |
| let session_socket = accept(sock.as_raw_fd()).expect("accept failed"); |
| |
| let msgs = [ |
| ControlMessage::AlgSetOp(&ALG_OP_ENCRYPT), |
| ControlMessage::AlgSetIv(iv.as_slice()), |
| ControlMessage::AlgSetAeadAssoclen(&assoc_size), |
| ]; |
| |
| let iov = IoSlice::new(&payload); |
| sendmsg::<()>( |
| session_socket.as_raw_fd(), |
| &[iov], |
| &msgs, |
| MsgFlags::empty(), |
| None, |
| ) |
| .expect("sendmsg encrypt"); |
| |
| // allocate buffer for encrypted data |
| let mut encrypted = |
| vec![0u8; (assoc_size as usize) + payload_len + auth_size]; |
| let num_bytes = |
| read(session_socket.as_raw_fd(), &mut encrypted).expect("read encrypt"); |
| assert_eq!(num_bytes, payload_len + auth_size + (assoc_size as usize)); |
| |
| for i in 0..assoc_size { |
| encrypted[i as usize] = 10; |
| } |
| |
| let iov = IoSlice::new(&encrypted); |
| |
| let iv = vec![1u8; iv_len]; |
| |
| let session_socket = accept(sock.as_raw_fd()).expect("accept failed"); |
| |
| let msgs = [ |
| ControlMessage::AlgSetOp(&ALG_OP_DECRYPT), |
| ControlMessage::AlgSetIv(iv.as_slice()), |
| ControlMessage::AlgSetAeadAssoclen(&assoc_size), |
| ]; |
| sendmsg::<()>( |
| session_socket.as_raw_fd(), |
| &[iov], |
| &msgs, |
| MsgFlags::empty(), |
| None, |
| ) |
| .expect("sendmsg decrypt"); |
| |
| // allocate buffer for decrypted data |
| let mut decrypted = |
| vec![0u8; payload_len + (assoc_size as usize) + auth_size]; |
| // Starting with kernel 4.9, the interface changed slightly such that the |
| // authentication tag memory is only needed in the output buffer for encryption |
| // and in the input buffer for decryption. |
| // Do not block on read, as we may have fewer bytes than buffer size |
| fcntl(session_socket, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) |
| .expect("fcntl non_blocking"); |
| let num_bytes = |
| read(session_socket.as_raw_fd(), &mut decrypted).expect("read decrypt"); |
| |
| assert!(num_bytes >= payload_len + (assoc_size as usize)); |
| assert_eq!( |
| decrypted[(assoc_size as usize)..(payload_len + (assoc_size as usize))], |
| payload[(assoc_size as usize)..payload_len + (assoc_size as usize)] |
| ); |
| } |
| |
| // Verify `ControlMessage::Ipv4PacketInfo` for `sendmsg`. |
| // This creates a (udp) socket bound to localhost, then sends a message to |
| // itself but uses Ipv4PacketInfo to force the source address to be localhost. |
| // |
| // This would be a more interesting test if we could assume that the test host |
| // has more than one IP address (since we could select a different address to |
| // test from). |
| #[cfg(any(target_os = "linux", apple_targets, target_os = "netbsd"))] |
| #[test] |
| pub fn test_sendmsg_ipv4packetinfo() { |
| use cfg_if::cfg_if; |
| use nix::sys::socket::{ |
| bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags, |
| SockFlag, SockType, SockaddrIn, |
| }; |
| use std::io::IoSlice; |
| |
| let sock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| |
| let sock_addr = SockaddrIn::new(127, 0, 0, 1, 4000); |
| |
| bind(sock.as_raw_fd(), &sock_addr).expect("bind failed"); |
| |
| let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; |
| let iov = [IoSlice::new(&slice)]; |
| |
| cfg_if! { |
| if #[cfg(target_os = "netbsd")] { |
| 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: sock_addr.as_ref().sin_addr, |
| }; |
| } |
| } |
| |
| let cmsg = [ControlMessage::Ipv4PacketInfo(&pi)]; |
| |
| sendmsg( |
| sock.as_raw_fd(), |
| &iov, |
| &cmsg, |
| MsgFlags::empty(), |
| Some(&sock_addr), |
| ) |
| .expect("sendmsg"); |
| } |
| |
| // Verify `ControlMessage::Ipv6PacketInfo` for `sendmsg`. |
| // This creates a (udp) socket bound to ip6-localhost, then sends a message to |
| // itself but uses Ipv6PacketInfo to force the source address to be |
| // ip6-localhost. |
| // |
| // This would be a more interesting test if we could assume that the test host |
| // has more than one IP address (since we could select a different address to |
| // test from). |
| #[cfg(any( |
| target_os = "linux", |
| apple_targets, |
| target_os = "netbsd", |
| target_os = "freebsd" |
| ))] |
| #[test] |
| pub fn test_sendmsg_ipv6packetinfo() { |
| use nix::errno::Errno; |
| use nix::sys::socket::{ |
| bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags, |
| SockFlag, SockType, SockaddrIn6, |
| }; |
| use std::io::IoSlice; |
| |
| let sock = socket( |
| AddressFamily::Inet6, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| |
| let std_sa = SocketAddrV6::from_str("[::1]:6000").unwrap(); |
| let sock_addr: SockaddrIn6 = SockaddrIn6::from(std_sa); |
| |
| if let Err(Errno::EADDRNOTAVAIL) = bind(sock.as_raw_fd(), &sock_addr) { |
| println!("IPv6 not available, skipping test."); |
| return; |
| } |
| |
| let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; |
| let iov = [IoSlice::new(&slice)]; |
| |
| let pi = libc::in6_pktinfo { |
| ipi6_ifindex: 0, /* Unspecified interface */ |
| ipi6_addr: sock_addr.as_ref().sin6_addr, |
| }; |
| |
| let cmsg = [ControlMessage::Ipv6PacketInfo(&pi)]; |
| |
| sendmsg::<SockaddrIn6>( |
| sock.as_raw_fd(), |
| &iov, |
| &cmsg, |
| MsgFlags::empty(), |
| Some(&sock_addr), |
| ) |
| .expect("sendmsg"); |
| } |
| |
| // Verify that ControlMessage::Ipv4SendSrcAddr works for sendmsg. This |
| // creates a UDP socket bound to all local interfaces (0.0.0.0). It then |
| // sends message to itself at 127.0.0.1 while explicitly specifying |
| // 127.0.0.1 as the source address through an Ipv4SendSrcAddr |
| // (IP_SENDSRCADDR) control message. |
| // |
| // Note that binding to 0.0.0.0 is *required* on FreeBSD; sendmsg |
| // returns EINVAL otherwise. (See FreeBSD's ip(4) man page.) |
| #[cfg(any(freebsdlike, netbsdlike))] |
| #[test] |
| pub fn test_sendmsg_ipv4sendsrcaddr() { |
| use nix::sys::socket::{ |
| bind, sendmsg, socket, AddressFamily, ControlMessage, MsgFlags, |
| SockFlag, SockType, SockaddrIn, |
| }; |
| use std::io::IoSlice; |
| |
| let sock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| |
| let unspec_sock_addr = SockaddrIn::new(0, 0, 0, 0, 0); |
| bind(sock.as_raw_fd(), &unspec_sock_addr).expect("bind failed"); |
| let bound_sock_addr: SockaddrIn = getsockname(sock.as_raw_fd()).unwrap(); |
| let localhost_sock_addr: SockaddrIn = |
| SockaddrIn::new(127, 0, 0, 1, bound_sock_addr.port()); |
| |
| let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; |
| let iov = [IoSlice::new(&slice)]; |
| let cmsg = [ControlMessage::Ipv4SendSrcAddr( |
| &localhost_sock_addr.as_ref().sin_addr, |
| )]; |
| |
| sendmsg( |
| sock.as_raw_fd(), |
| &iov, |
| &cmsg, |
| MsgFlags::empty(), |
| Some(&localhost_sock_addr), |
| ) |
| .expect("sendmsg"); |
| } |
| |
| /// 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(qemu, ignore)] |
| #[test] |
| fn test_scm_rights_single_cmsg_multiple_fds() { |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags, |
| }; |
| use std::io::{IoSlice, IoSliceMut}; |
| use std::os::unix::io::{AsRawFd, RawFd}; |
| use std::os::unix::net::UnixDatagram; |
| use std::thread; |
| |
| let (send, receive) = UnixDatagram::pair().unwrap(); |
| let thread = thread::spawn(move || { |
| let mut buf = [0u8; 8]; |
| let mut iovec = [IoSliceMut::new(&mut buf)]; |
| |
| let mut space = cmsg_space!([RawFd; 2]); |
| let msg = recvmsg::<()>( |
| receive.as_raw_fd(), |
| &mut iovec, |
| Some(&mut space), |
| MsgFlags::empty(), |
| ) |
| .unwrap(); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| |
| let mut cmsgs = msg.cmsgs().unwrap(); |
| match cmsgs.next() { |
| Some(ControlMessageOwned::ScmRights(fds)) => { |
| assert_eq!( |
| fds.len(), |
| 2, |
| "unexpected fd count (expected 2 fds, got {})", |
| fds.len() |
| ); |
| } |
| _ => panic!(), |
| } |
| assert!(cmsgs.next().is_none(), "unexpected control msg"); |
| |
| assert_eq!(msg.bytes, 8); |
| assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); |
| }); |
| |
| let slice = [1u8, 2, 3, 4, 5, 6, 7, 8]; |
| let iov = [IoSlice::new(&slice)]; |
| let fds = [libc::STDIN_FILENO, libc::STDOUT_FILENO]; // pass stdin and stdout |
| let cmsg = [ControlMessage::ScmRights(&fds)]; |
| sendmsg::<()>(send.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), None) |
| .unwrap(); |
| thread.join().unwrap(); |
| } |
| |
| // Verify `sendmsg` builds a valid `msghdr` when passing an empty |
| // `cmsgs` argument. This should result in a msghdr with a nullptr |
| // msg_control field and a msg_controllen of 0 when calling into the |
| // raw `sendmsg`. |
| #[test] |
| pub fn test_sendmsg_empty_cmsgs() { |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, socketpair, AddressFamily, MsgFlags, SockFlag, |
| SockType, |
| }; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| let (fd1, fd2) = socketpair( |
| AddressFamily::Unix, |
| SockType::Stream, |
| None, |
| SockFlag::empty(), |
| ) |
| .unwrap(); |
| |
| { |
| let iov = [IoSlice::new(b"hello")]; |
| assert_eq!( |
| sendmsg::<()>(fd1.as_raw_fd(), &iov, &[], MsgFlags::empty(), None) |
| .unwrap(), |
| 5 |
| ); |
| } |
| |
| { |
| let mut buf = [0u8; 5]; |
| let mut iov = [IoSliceMut::new(&mut buf[..])]; |
| |
| let mut cmsgspace = cmsg_space!([RawFd; 1]); |
| let msg = recvmsg::<()>( |
| fd2.as_raw_fd(), |
| &mut iov, |
| Some(&mut cmsgspace), |
| MsgFlags::empty(), |
| ) |
| .unwrap(); |
| |
| if msg.cmsgs().unwrap().next().is_some() { |
| panic!("unexpected cmsg"); |
| } |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| assert_eq!(msg.bytes, 5); |
| } |
| } |
| |
| #[cfg(any(linux_android, freebsdlike))] |
| #[test] |
| fn test_scm_credentials() { |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, socketpair, AddressFamily, ControlMessage, |
| ControlMessageOwned, MsgFlags, SockFlag, SockType, UnixCredentials, |
| }; |
| #[cfg(linux_android)] |
| use nix::sys::socket::{setsockopt, sockopt::PassCred}; |
| use nix::unistd::{getgid, getpid, getuid}; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| let (send, recv) = socketpair( |
| AddressFamily::Unix, |
| SockType::Stream, |
| None, |
| SockFlag::empty(), |
| ) |
| .unwrap(); |
| #[cfg(linux_android)] |
| setsockopt(&recv, PassCred, &true).unwrap(); |
| |
| { |
| let iov = [IoSlice::new(b"hello")]; |
| #[cfg(linux_android)] |
| let cred = UnixCredentials::new(); |
| #[cfg(linux_android)] |
| let cmsg = ControlMessage::ScmCredentials(&cred); |
| #[cfg(freebsdlike)] |
| let cmsg = ControlMessage::ScmCreds; |
| assert_eq!( |
| sendmsg::<()>( |
| send.as_raw_fd(), |
| &iov, |
| &[cmsg], |
| MsgFlags::empty(), |
| None |
| ) |
| .unwrap(), |
| 5 |
| ); |
| } |
| |
| { |
| let mut buf = [0u8; 5]; |
| let mut iov = [IoSliceMut::new(&mut buf[..])]; |
| |
| let mut cmsgspace = cmsg_space!(UnixCredentials); |
| let msg = recvmsg::<()>( |
| recv.as_raw_fd(), |
| &mut iov, |
| Some(&mut cmsgspace), |
| MsgFlags::empty(), |
| ) |
| .unwrap(); |
| let mut received_cred = None; |
| |
| for cmsg in msg.cmsgs().unwrap() { |
| let cred = match cmsg { |
| #[cfg(linux_android)] |
| ControlMessageOwned::ScmCredentials(cred) => cred, |
| #[cfg(freebsdlike)] |
| ControlMessageOwned::ScmCreds(cred) => cred, |
| other => panic!("unexpected cmsg {other:?}"), |
| }; |
| assert!(received_cred.is_none()); |
| assert_eq!(cred.pid(), getpid().as_raw()); |
| assert_eq!(cred.uid(), getuid().as_raw()); |
| assert_eq!(cred.gid(), getgid().as_raw()); |
| received_cred = Some(cred); |
| } |
| received_cred.expect("no creds received"); |
| assert_eq!(msg.bytes, 5); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| } |
| } |
| |
| /// Ensure that we can send `SCM_CREDENTIALS` and `SCM_RIGHTS` with a single |
| /// `sendmsg` call. |
| #[cfg(linux_android)] |
| // qemu's handling of multiple cmsgs is bugged, ignore tests under emulation |
| // see https://bugs.launchpad.net/qemu/+bug/1781280 |
| #[cfg_attr(qemu, ignore)] |
| #[test] |
| fn test_scm_credentials_and_rights() { |
| let space = cmsg_space!(libc::ucred, RawFd); |
| test_impl_scm_credentials_and_rights(space).unwrap(); |
| } |
| |
| /// Ensure that passing a an oversized control message buffer to recvmsg |
| /// still works. |
| #[cfg(linux_android)] |
| // qemu's handling of multiple cmsgs is bugged, ignore tests under emulation |
| // see https://bugs.launchpad.net/qemu/+bug/1781280 |
| #[cfg_attr(qemu, ignore)] |
| #[test] |
| fn test_too_large_cmsgspace() { |
| let space = vec![0u8; 1024]; |
| test_impl_scm_credentials_and_rights(space).unwrap(); |
| } |
| |
| #[cfg(linux_android)] |
| #[test] |
| fn test_too_small_cmsgspace() { |
| let space = vec![0u8; 4]; |
| assert_eq!( |
| test_impl_scm_credentials_and_rights(space), |
| Err(nix::errno::Errno::ENOBUFS) |
| ); |
| } |
| |
| #[cfg(linux_android)] |
| fn test_impl_scm_credentials_and_rights( |
| mut space: Vec<u8>, |
| ) -> Result<(), nix::errno::Errno> { |
| use libc::ucred; |
| use nix::sys::socket::sockopt::PassCred; |
| use nix::sys::socket::{ |
| recvmsg, sendmsg, setsockopt, socketpair, ControlMessage, |
| ControlMessageOwned, MsgFlags, SockFlag, SockType, |
| }; |
| use nix::unistd::{close, getgid, getpid, getuid, pipe, write}; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| let (send, recv) = socketpair( |
| AddressFamily::Unix, |
| SockType::Stream, |
| None, |
| SockFlag::empty(), |
| ) |
| .unwrap(); |
| setsockopt(&recv, PassCred, &true).unwrap(); |
| |
| let (r, w) = pipe().unwrap(); |
| let mut received_r: Option<RawFd> = None; |
| |
| { |
| let iov = [IoSlice::new(b"hello")]; |
| let cred = ucred { |
| pid: getpid().as_raw(), |
| uid: getuid().as_raw(), |
| gid: getgid().as_raw(), |
| } |
| .into(); |
| let fds = [r.as_raw_fd()]; |
| let cmsgs = [ |
| ControlMessage::ScmCredentials(&cred), |
| ControlMessage::ScmRights(&fds), |
| ]; |
| assert_eq!( |
| sendmsg::<()>( |
| send.as_raw_fd(), |
| &iov, |
| &cmsgs, |
| MsgFlags::empty(), |
| None |
| ) |
| .unwrap(), |
| 5 |
| ); |
| } |
| |
| { |
| let mut buf = [0u8; 5]; |
| let mut iov = [IoSliceMut::new(&mut buf[..])]; |
| let msg = recvmsg::<()>( |
| recv.as_raw_fd(), |
| &mut iov, |
| Some(&mut space), |
| MsgFlags::empty(), |
| ) |
| .unwrap(); |
| let mut received_cred = None; |
| |
| assert_eq!(msg.cmsgs()?.count(), 2, "expected 2 cmsgs"); |
| |
| for cmsg in msg.cmsgs()? { |
| match cmsg { |
| ControlMessageOwned::ScmRights(fds) => { |
| assert_eq!(received_r, None, "already received fd"); |
| assert_eq!(fds.len(), 1); |
| received_r = Some(fds[0]); |
| } |
| ControlMessageOwned::ScmCredentials(cred) => { |
| assert!(received_cred.is_none()); |
| assert_eq!(cred.pid(), getpid().as_raw()); |
| assert_eq!(cred.uid(), getuid().as_raw()); |
| assert_eq!(cred.gid(), getgid().as_raw()); |
| received_cred = Some(cred); |
| } |
| _ => panic!("unexpected cmsg"), |
| } |
| } |
| received_cred.expect("no creds received"); |
| assert_eq!(msg.bytes, 5); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| } |
| |
| let received_r = received_r.expect("Did not receive passed fd"); |
| // Ensure that the received file descriptor works |
| write(&w, b"world").unwrap(); |
| let mut buf = [0u8; 5]; |
| read(received_r.as_raw_fd(), &mut buf).unwrap(); |
| assert_eq!(&buf[..], b"world"); |
| close(received_r).unwrap(); |
| |
| Ok(()) |
| } |
| |
| // Test creating and using named unix domain sockets |
| #[test] |
| pub fn test_named_unixdomain() { |
| use nix::sys::socket::{ |
| accept, bind, connect, listen, socket, Backlog, UnixAddr, |
| }; |
| use nix::sys::socket::{SockFlag, SockType}; |
| use nix::unistd::{read, write}; |
| use std::thread; |
| |
| let tempdir = tempfile::tempdir().unwrap(); |
| let sockname = tempdir.path().join("sock"); |
| let s1 = socket( |
| AddressFamily::Unix, |
| SockType::Stream, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| let sockaddr = UnixAddr::new(&sockname).unwrap(); |
| bind(s1.as_raw_fd(), &sockaddr).expect("bind failed"); |
| listen(&s1, Backlog::new(10).unwrap()).expect("listen failed"); |
| |
| let thr = thread::spawn(move || { |
| let s2 = socket( |
| AddressFamily::Unix, |
| SockType::Stream, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| connect(s2.as_raw_fd(), &sockaddr).expect("connect failed"); |
| write(&s2, b"hello").expect("write failed"); |
| }); |
| |
| let s3 = accept(s1.as_raw_fd()).expect("accept failed"); |
| |
| let mut buf = [0; 5]; |
| read(s3.as_raw_fd(), &mut buf).unwrap(); |
| thr.join().unwrap(); |
| |
| assert_eq!(&buf[..], b"hello"); |
| } |
| |
| #[test] |
| pub fn test_listen_wrongbacklog() { |
| use nix::sys::socket::Backlog; |
| |
| assert!(Backlog::new(libc::SOMAXCONN + 1).is_err()); |
| assert!(Backlog::new(-2).is_err()); |
| } |
| |
| // Test using unnamed unix domain addresses |
| #[cfg(linux_android)] |
| #[test] |
| pub fn test_unnamed_unixdomain() { |
| use nix::sys::socket::{getsockname, socketpair}; |
| use nix::sys::socket::{SockFlag, SockType}; |
| |
| let (fd_1, _fd_2) = socketpair( |
| AddressFamily::Unix, |
| SockType::Stream, |
| None, |
| SockFlag::empty(), |
| ) |
| .expect("socketpair failed"); |
| |
| let addr_1: UnixAddr = |
| getsockname(fd_1.as_raw_fd()).expect("getsockname failed"); |
| assert!(addr_1.is_unnamed()); |
| } |
| |
| // Test creating and using unnamed unix domain addresses for autobinding sockets |
| #[cfg(linux_android)] |
| #[test] |
| pub fn test_unnamed_unixdomain_autobind() { |
| use nix::sys::socket::{bind, getsockname, socket}; |
| use nix::sys::socket::{SockFlag, SockType}; |
| |
| let fd = socket( |
| AddressFamily::Unix, |
| SockType::Stream, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("socket failed"); |
| |
| // unix(7): "If a bind(2) call specifies addrlen as `sizeof(sa_family_t)`, or [...], then the |
| // socket is autobound to an abstract address" |
| bind(fd.as_raw_fd(), &UnixAddr::new_unnamed()).expect("bind failed"); |
| |
| let addr: UnixAddr = |
| getsockname(fd.as_raw_fd()).expect("getsockname failed"); |
| let addr = addr.as_abstract().unwrap(); |
| |
| // changed from 8 to 5 bytes in Linux 2.3.15, and rust's minimum supported Linux version is 3.2 |
| // (as of 2022-11) |
| assert_eq!(addr.len(), 5); |
| } |
| |
| // Test creating and using named system control sockets |
| #[cfg(apple_targets)] |
| #[test] |
| pub fn test_syscontrol() { |
| use nix::errno::Errno; |
| use nix::sys::socket::{ |
| socket, SockFlag, SockProtocol, SockType, SysControlAddr, |
| }; |
| |
| let fd = socket( |
| AddressFamily::System, |
| SockType::Datagram, |
| SockFlag::empty(), |
| SockProtocol::KextControl, |
| ) |
| .expect("socket failed"); |
| SysControlAddr::from_name(fd.as_raw_fd(), "com.apple.net.utun_control", 0) |
| .expect("resolving sys_control name failed"); |
| assert_eq!( |
| SysControlAddr::from_name(fd.as_raw_fd(), "foo.bar.lol", 0).err(), |
| Some(Errno::ENOENT) |
| ); |
| |
| // requires root privileges |
| // connect(fd.as_raw_fd(), &sockaddr).expect("connect failed"); |
| } |
| |
| #[cfg(any(bsd, linux_android))] |
| fn loopback_address( |
| family: AddressFamily, |
| ) -> Option<nix::ifaddrs::InterfaceAddress> { |
| use nix::ifaddrs::getifaddrs; |
| use nix::net::if_::*; |
| use nix::sys::socket::SockaddrLike; |
| use std::io; |
| use std::io::Write; |
| |
| let mut addrs = match getifaddrs() { |
| Ok(iter) => iter, |
| Err(e) => { |
| let stdioerr = io::stderr(); |
| let mut handle = stdioerr.lock(); |
| writeln!(handle, "getifaddrs: {e:?}").unwrap(); |
| return None; |
| } |
| }; |
| // return first address matching family |
| addrs.find(|ifaddr| { |
| ifaddr.flags.contains(InterfaceFlags::IFF_LOOPBACK) |
| && ifaddr.address.as_ref().and_then(SockaddrLike::family) |
| == Some(family) |
| }) |
| } |
| |
| #[cfg(any(linux_android, apple_targets, target_os = "netbsd"))] |
| // qemu doesn't seem to be emulating this correctly in these architectures |
| #[cfg_attr( |
| all( |
| qemu, |
| any( |
| target_arch = "mips", |
| target_arch = "mips32r6", |
| target_arch = "mips64", |
| target_arch = "mips64r6", |
| target_arch = "powerpc64", |
| ) |
| ), |
| ignore |
| )] |
| #[test] |
| pub fn test_recv_ipv4pktinfo() { |
| use nix::net::if_::*; |
| use nix::sys::socket::sockopt::Ipv4PacketInfo; |
| 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.as_raw_fd(), &lo).expect("bind failed"); |
| let sa: SockaddrIn = |
| getsockname(receive.as_raw_fd()).expect("getsockname failed"); |
| setsockopt(&receive, Ipv4PacketInfo, &true).expect("setsockopt 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.as_raw_fd(), &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_pktinfo); |
| let msg = recvmsg::<()>( |
| receive.as_raw_fd(), |
| &mut iovec, |
| Some(&mut space), |
| MsgFlags::empty(), |
| ) |
| .expect("recvmsg failed"); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| |
| let mut cmsgs = msg.cmsgs().unwrap(); |
| if let Some(ControlMessageOwned::Ipv4PacketInfo(pktinfo)) = cmsgs.next() |
| { |
| let i = if_nametoindex(lo_name.as_bytes()).expect("if_nametoindex"); |
| assert_eq!( |
| pktinfo.ipi_ifindex as libc::c_uint, i, |
| "unexpected ifindex (expected {}, got {})", |
| i, pktinfo.ipi_ifindex |
| ); |
| } |
| assert!(cmsgs.next().is_none(), "unexpected additional control msg"); |
| assert_eq!(msg.bytes, 8); |
| assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); |
| } |
| } |
| |
| #[cfg(bsd)] |
| // qemu doesn't seem to be emulating this correctly in these architectures |
| #[cfg_attr( |
| all( |
| qemu, |
| any( |
| target_arch = "mips", |
| target_arch = "mips32r6", |
| target_arch = "mips64", |
| target_arch = "mips64r6", |
| target_arch = "powerpc64", |
| ) |
| ), |
| ignore |
| )] |
| #[test] |
| pub fn test_recvif() { |
| use nix::net::if_::*; |
| use nix::sys::socket::sockopt::{Ipv4RecvDstAddr, Ipv4RecvIf}; |
| 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.as_raw_fd(), &lo).expect("bind failed"); |
| let sa: SockaddrIn = |
| getsockname(receive.as_raw_fd()).expect("getsockname failed"); |
| setsockopt(&receive, Ipv4RecvIf, &true) |
| .expect("setsockopt IP_RECVIF failed"); |
| setsockopt(&receive, Ipv4RecvDstAddr, &true) |
| .expect("setsockopt IP_RECVDSTADDR 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.as_raw_fd(), &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::sockaddr_dl, libc::in_addr); |
| let msg = recvmsg::<()>( |
| receive.as_raw_fd(), |
| &mut iovec, |
| Some(&mut space), |
| MsgFlags::empty(), |
| ) |
| .expect("recvmsg failed"); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| assert_eq!(msg.cmsgs().unwrap().count(), 2, "expected 2 cmsgs"); |
| |
| let mut rx_recvif = false; |
| let mut rx_recvdstaddr = false; |
| for cmsg in msg.cmsgs().unwrap() { |
| match cmsg { |
| ControlMessageOwned::Ipv4RecvIf(dl) => { |
| rx_recvif = true; |
| let i = if_nametoindex(lo_name.as_bytes()) |
| .expect("if_nametoindex"); |
| assert_eq!( |
| dl.sdl_index as libc::c_uint, i, |
| "unexpected ifindex (expected {}, got {})", |
| i, dl.sdl_index |
| ); |
| } |
| ControlMessageOwned::Ipv4RecvDstAddr(addr) => { |
| rx_recvdstaddr = 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_recvif); |
| assert!(rx_recvdstaddr); |
| assert_eq!(msg.bytes, 8); |
| assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); |
| } |
| } |
| |
| #[cfg(any(linux_android, target_os = "freebsd"))] |
| #[cfg_attr(qemu, ignore)] |
| #[test] |
| pub fn test_recvif_ipv4() { |
| use nix::sys::socket::sockopt::Ipv4OrigDstAddr; |
| 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.as_raw_fd(), &lo).expect("bind failed"); |
| let sa: SockaddrIn = |
| getsockname(receive.as_raw_fd()).expect("getsockname failed"); |
| setsockopt(&receive, Ipv4OrigDstAddr, &true) |
| .expect("setsockopt IP_ORIGDSTADDR 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.as_raw_fd(), &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::sockaddr_in); |
| let msg = recvmsg::<()>( |
| receive.as_raw_fd(), |
| &mut iovec, |
| Some(&mut space), |
| MsgFlags::empty(), |
| ) |
| .expect("recvmsg failed"); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| assert_eq!(msg.cmsgs().unwrap().count(), 1, "expected 1 cmsgs"); |
| |
| let mut rx_recvorigdstaddr = false; |
| for cmsg in msg.cmsgs().unwrap() { |
| match cmsg { |
| ControlMessageOwned::Ipv4OrigDstAddr(addr) => { |
| rx_recvorigdstaddr = true; |
| if let Some(sin) = lo.as_sockaddr_in() { |
| assert_eq!(sin.as_ref().sin_addr.s_addr, |
| addr.sin_addr.s_addr, |
| "unexpected destination address (expected {}, got {})", |
| sin.as_ref().sin_addr.s_addr, |
| addr.sin_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(linux_android, target_os = "freebsd"))] |
| #[cfg_attr(qemu, ignore)] |
| #[test] |
| pub fn test_recvif_ipv6() { |
| use nix::sys::socket::sockopt::Ipv6OrigDstAddr; |
| 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.as_raw_fd(), &lo).expect("bind failed"); |
| let sa: SockaddrIn6 = |
| getsockname(receive.as_raw_fd()).expect("getsockname failed"); |
| setsockopt(&receive, Ipv6OrigDstAddr, &true) |
| .expect("setsockopt IP_ORIGDSTADDR 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.as_raw_fd(), &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::sockaddr_in6); |
| let msg = recvmsg::<()>( |
| receive.as_raw_fd(), |
| &mut iovec, |
| Some(&mut space), |
| MsgFlags::empty(), |
| ) |
| .expect("recvmsg failed"); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| assert_eq!(msg.cmsgs().unwrap().count(), 1, "expected 1 cmsgs"); |
| |
| let mut rx_recvorigdstaddr = false; |
| for cmsg in msg.cmsgs().unwrap() { |
| match cmsg { |
| ControlMessageOwned::Ipv6OrigDstAddr(addr) => { |
| rx_recvorigdstaddr = true; |
| if let Some(sin) = lo.as_sockaddr_in6() { |
| assert_eq!(sin.as_ref().sin6_addr.s6_addr, |
| addr.sin6_addr.s6_addr, |
| "unexpected destination address (expected {:?}, got {:?})", |
| sin.as_ref().sin6_addr.s6_addr, |
| addr.sin6_addr.s6_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(linux_android, target_os = "freebsd", apple_targets, netbsdlike))] |
| // qemu doesn't seem to be emulating this correctly in these architectures |
| #[cfg_attr( |
| all( |
| qemu, |
| any( |
| target_arch = "mips", |
| target_arch = "mips32r6", |
| target_arch = "mips64", |
| target_arch = "mips64r6", |
| target_arch = "powerpc64", |
| ) |
| ), |
| ignore |
| )] |
| #[test] |
| pub fn test_recv_ipv6pktinfo() { |
| use nix::net::if_::*; |
| use nix::sys::socket::sockopt::Ipv6RecvPacketInfo; |
| 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.as_raw_fd(), &lo).expect("bind failed"); |
| let sa: SockaddrIn6 = |
| getsockname(receive.as_raw_fd()).expect("getsockname failed"); |
| setsockopt(&receive, Ipv6RecvPacketInfo, &true).expect("setsockopt 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.as_raw_fd(), &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_pktinfo); |
| let msg = recvmsg::<()>( |
| receive.as_raw_fd(), |
| &mut iovec, |
| Some(&mut space), |
| MsgFlags::empty(), |
| ) |
| .expect("recvmsg failed"); |
| assert!(!msg |
| .flags |
| .intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC)); |
| |
| let mut cmsgs = msg.cmsgs().unwrap(); |
| 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); |
| assert_eq!(*iovec[0], [1u8, 2, 3, 4, 5, 6, 7, 8]); |
| } |
| } |
| |
| #[cfg(linux_android)] |
| #[test] |
| pub fn test_vsock() { |
| use nix::sys::socket::SockaddrLike; |
| use nix::sys::socket::{AddressFamily, VsockAddr}; |
| use std::mem; |
| |
| let port: u32 = 3000; |
| |
| let addr_local = VsockAddr::new(libc::VMADDR_CID_LOCAL, port); |
| assert_eq!(addr_local.cid(), libc::VMADDR_CID_LOCAL); |
| assert_eq!(addr_local.port(), port); |
| |
| let addr_any = VsockAddr::new(libc::VMADDR_CID_ANY, libc::VMADDR_PORT_ANY); |
| assert_eq!(addr_any.cid(), libc::VMADDR_CID_ANY); |
| assert_eq!(addr_any.port(), libc::VMADDR_PORT_ANY); |
| |
| assert_ne!(addr_local, addr_any); |
| assert_ne!(calculate_hash(&addr_local), calculate_hash(&addr_any)); |
| |
| let addr1 = VsockAddr::new(libc::VMADDR_CID_HOST, port); |
| let addr2 = VsockAddr::new(libc::VMADDR_CID_HOST, port); |
| assert_eq!(addr1, addr2); |
| assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); |
| |
| let addr3 = unsafe { |
| VsockAddr::from_raw( |
| addr2.as_ref() as *const libc::sockaddr_vm as *const libc::sockaddr, |
| Some(mem::size_of::<libc::sockaddr_vm>().try_into().unwrap()), |
| ) |
| } |
| .unwrap(); |
| assert_eq!( |
| addr3.as_ref().svm_family, |
| AddressFamily::Vsock as libc::sa_family_t |
| ); |
| assert_eq!(addr3.as_ref().svm_cid, addr1.cid()); |
| assert_eq!(addr3.as_ref().svm_port, addr1.port()); |
| } |
| |
| #[cfg(apple_targets)] |
| #[test] |
| pub fn test_vsock() { |
| use nix::sys::socket::SockaddrLike; |
| use nix::sys::socket::{AddressFamily, VsockAddr}; |
| use std::mem; |
| |
| let port: u32 = 3000; |
| |
| // macOS doesn't have a VMADDR_CID_LOCAL, so test with host again |
| let addr_host = VsockAddr::new(libc::VMADDR_CID_HOST, port); |
| assert_eq!(addr_host.cid(), libc::VMADDR_CID_HOST); |
| assert_eq!(addr_host.port(), port); |
| |
| let addr_any = VsockAddr::new(libc::VMADDR_CID_ANY, libc::VMADDR_PORT_ANY); |
| assert_eq!(addr_any.cid(), libc::VMADDR_CID_ANY); |
| assert_eq!(addr_any.port(), libc::VMADDR_PORT_ANY); |
| |
| assert_ne!(addr_host, addr_any); |
| assert_ne!(calculate_hash(&addr_host), calculate_hash(&addr_any)); |
| |
| let addr1 = VsockAddr::new(libc::VMADDR_CID_HOST, port); |
| let addr2 = VsockAddr::new(libc::VMADDR_CID_HOST, port); |
| assert_eq!(addr1, addr2); |
| assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2)); |
| |
| let addr3 = unsafe { |
| VsockAddr::from_raw( |
| addr2.as_ref() as *const libc::sockaddr_vm as *const libc::sockaddr, |
| Some(mem::size_of::<libc::sockaddr_vm>().try_into().unwrap()), |
| ) |
| } |
| .unwrap(); |
| assert_eq!( |
| addr3.as_ref().svm_family, |
| AddressFamily::Vsock as libc::sa_family_t |
| ); |
| let cid = addr3.as_ref().svm_cid; |
| let port = addr3.as_ref().svm_port; |
| assert_eq!(cid, addr1.cid()); |
| assert_eq!(port, addr1.port()); |
| } |
| |
| // Disable the test on emulated platforms because it fails in Cirrus-CI. Lack |
| // of QEMU support is suspected. |
| #[cfg_attr(qemu, ignore)] |
| #[cfg(target_os = "linux")] |
| #[test] |
| fn test_recvmsg_timestampns() { |
| use nix::sys::socket::*; |
| use nix::sys::time::*; |
| use std::io::{IoSlice, IoSliceMut}; |
| 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 = SockaddrIn::new(127, 0, 0, 1, 0); |
| bind(in_socket.as_raw_fd(), &localhost).unwrap(); |
| let address: SockaddrIn = getsockname(in_socket.as_raw_fd()).unwrap(); |
| // Get initial time |
| let time0 = SystemTime::now(); |
| // Send the message |
| let iov = [IoSlice::new(message)]; |
| let flags = MsgFlags::empty(); |
| let l = sendmsg(in_socket.as_raw_fd(), &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 mut iov = [IoSliceMut::new(&mut buffer)]; |
| let r = recvmsg::<()>( |
| in_socket.as_raw_fd(), |
| &mut iov, |
| Some(&mut cmsgspace), |
| flags, |
| ) |
| .unwrap(); |
| let rtime = match r.cmsgs().unwrap().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()); |
| } |
| |
| // Disable the test on emulated platforms because it fails in Cirrus-CI. Lack |
| // of QEMU support is suspected. |
| #[cfg_attr(qemu, ignore)] |
| #[cfg(target_os = "linux")] |
| #[test] |
| fn test_recvmmsg_timestampns() { |
| use nix::sys::socket::*; |
| use nix::sys::time::*; |
| use std::io::{IoSlice, IoSliceMut}; |
| 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 = SockaddrIn::from_str("127.0.0.1:0").unwrap(); |
| bind(in_socket.as_raw_fd(), &localhost).unwrap(); |
| let address: SockaddrIn = getsockname(in_socket.as_raw_fd()).unwrap(); |
| // Get initial time |
| let time0 = SystemTime::now(); |
| // Send the message |
| let iov = [IoSlice::new(message)]; |
| let flags = MsgFlags::empty(); |
| let l = sendmsg(in_socket.as_raw_fd(), &iov, &[], flags, Some(&address)) |
| .unwrap(); |
| assert_eq!(message.len(), l); |
| // Receive the message |
| let mut buffer = vec![0u8; message.len()]; |
| let cmsgspace = nix::cmsg_space!(TimeSpec); |
| let mut iov = [[IoSliceMut::new(&mut buffer)]]; |
| let mut data = MultiHeaders::preallocate(1, Some(cmsgspace)); |
| let r: Vec<RecvMsg<()>> = recvmmsg( |
| in_socket.as_raw_fd(), |
| &mut data, |
| iov.iter_mut(), |
| flags, |
| None, |
| ) |
| .unwrap() |
| .collect(); |
| let rtime = match r[0].cmsgs().unwrap().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()); |
| } |
| |
| // Disable the test on emulated platforms because it fails in Cirrus-CI. Lack |
| // of QEMU support is suspected. |
| #[cfg_attr(qemu, ignore)] |
| #[cfg(any(linux_android, target_os = "fuchsia"))] |
| #[test] |
| fn test_recvmsg_rxq_ovfl() { |
| use nix::sys::socket::sockopt::{RcvBuf, RxqOvfl}; |
| use nix::sys::socket::*; |
| use nix::Error; |
| use std::io::{IoSlice, IoSliceMut}; |
| |
| 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 = SockaddrIn::from_str("127.0.0.1:0").unwrap(); |
| bind(in_socket.as_raw_fd(), &localhost).unwrap(); |
| |
| let address: SockaddrIn = getsockname(in_socket.as_raw_fd()).unwrap(); |
| connect(out_socket.as_raw_fd(), &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 = [IoSlice::new(&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.as_raw_fd(), |
| &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 mut iov = [IoSliceMut::new(&mut buffer)]; |
| |
| match recvmsg::<()>( |
| in_socket.as_raw_fd(), |
| &mut iov, |
| Some(&mut cmsgspace), |
| MsgFlags::MSG_DONTWAIT, |
| ) { |
| Ok(r) => { |
| drop_counter = match r.cmsgs().unwrap().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); |
| } |
| |
| #[cfg(linux_android)] |
| mod linux_errqueue { |
| use super::FromStr; |
| use nix::sys::socket::*; |
| use std::os::unix::io::AsRawFd; |
| |
| // 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!( |
| origin.sin_addr.s_addr, |
| u32::from_be(0x7f000001) |
| ); |
| assert_eq!(origin.sin_port, 0); |
| } else { |
| panic!("Expected some error origin"); |
| } |
| *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!( |
| origin.sin6_addr.s6_addr, |
| std::net::Ipv6Addr::LOCALHOST.octets() |
| ); |
| assert_eq!(origin.sin6_port, 0); |
| } else { |
| panic!("Expected some error origin"); |
| } |
| *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 std::io::IoSliceMut; |
| |
| const MESSAGE_CONTENTS: &str = "ABCDEF"; |
| let std_sa = std::net::SocketAddr::from_str(sa).unwrap(); |
| let sock_addr = SockaddrStorage::from(std_sa); |
| let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None) |
| .unwrap(); |
| setsockopt(&sock, opt, &true).unwrap(); |
| if let Err(e) = sendto( |
| sock.as_raw_fd(), |
| MESSAGE_CONTENTS.as_bytes(), |
| &sock_addr, |
| MsgFlags::empty(), |
| ) { |
| assert_eq!(e, Errno::EADDRNOTAVAIL); |
| println!("{af:?} not available, skipping test."); |
| return; |
| } |
| |
| let mut buf = [0u8; 8]; |
| let mut iovec = [IoSliceMut::new(&mut buf)]; |
| let mut cspace = cmsg_space!(libc::sock_extended_err, SA); |
| |
| let msg = recvmsg( |
| sock.as_raw_fd(), |
| &mut 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()); |
| // 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().unwrap().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); |
| |
| let bytes = msg.bytes; |
| assert_eq!(&buf[..bytes], MESSAGE_CONTENTS.as_bytes()); |
| } |
| } |
| |
| // Disable the test on emulated platforms because it fails in Cirrus-CI. Lack |
| // of QEMU support is suspected. |
| #[cfg_attr(qemu, ignore)] |
| #[cfg(target_os = "linux")] |
| #[test] |
| pub fn test_txtime() { |
| use nix::sys::socket::{ |
| bind, recvmsg, sendmsg, setsockopt, socket, sockopt, ControlMessage, |
| MsgFlags, SockFlag, SockType, SockaddrIn, |
| }; |
| use nix::sys::time::TimeValLike; |
| use nix::time::{clock_gettime, ClockId}; |
| |
| require_kernel_version!(test_txtime, ">= 5.8"); |
| |
| let sock_addr = SockaddrIn::from_str("127.0.0.1:6802").unwrap(); |
| |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .expect("send socket failed"); |
| |
| let txtime_cfg = libc::sock_txtime { |
| clockid: libc::CLOCK_MONOTONIC, |
| flags: 0, |
| }; |
| setsockopt(&ssock, sockopt::TxTime, &txtime_cfg).unwrap(); |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| ) |
| .unwrap(); |
| bind(rsock.as_raw_fd(), &sock_addr).unwrap(); |
| |
| let sbuf = [0u8; 2048]; |
| let iov1 = [std::io::IoSlice::new(&sbuf)]; |
| |
| let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap(); |
| let delay = std::time::Duration::from_secs(1).into(); |
| let txtime = (now + delay).num_nanoseconds() as u64; |
| |
| let cmsg = ControlMessage::TxTime(&txtime); |
| sendmsg( |
| ssock.as_raw_fd(), |
| &iov1, |
| &[cmsg], |
| MsgFlags::empty(), |
| Some(&sock_addr), |
| ) |
| .unwrap(); |
| |
| let mut rbuf = [0u8; 2048]; |
| let mut iov2 = [std::io::IoSliceMut::new(&mut rbuf)]; |
| recvmsg::<()>(rsock.as_raw_fd(), &mut iov2, None, MsgFlags::empty()) |
| .unwrap(); |
| } |
| |
| // cfg needed for capability check. |
| #[cfg(linux_android)] |
| #[test] |
| fn test_icmp_protocol() { |
| use nix::sys::socket::{ |
| sendto, socket, AddressFamily, MsgFlags, SockFlag, SockProtocol, |
| SockType, SockaddrIn, |
| }; |
| |
| require_capability!("test_icmp_protocol", CAP_NET_RAW); |
| |
| let owned_fd = socket( |
| AddressFamily::Inet, |
| SockType::Raw, |
| SockFlag::empty(), |
| SockProtocol::Icmp, |
| ) |
| .unwrap(); |
| |
| // Send a minimal ICMP packet with no payload. |
| let packet = [ |
| 0x08, // Type |
| 0x00, // Code |
| 0x84, 0x85, // Checksum |
| 0x73, 0x8a, // ID |
| 0x00, 0x00, // Sequence Number |
| ]; |
| |
| let dest_addr = SockaddrIn::new(127, 0, 0, 1, 0); |
| sendto(owned_fd.as_raw_fd(), &packet, &dest_addr, MsgFlags::empty()) |
| .unwrap(); |
| } |
| |
| // test contains both recvmmsg and timestaping which is linux only |
| // there are existing tests for recvmmsg only in tests/ |
| #[cfg_attr(qemu, ignore)] |
| #[cfg(target_os = "linux")] |
| #[test] |
| fn test_recvmm2() -> nix::Result<()> { |
| use nix::sys::{ |
| socket::{ |
| bind, recvmmsg, sendmsg, setsockopt, socket, sockopt::Timestamping, |
| AddressFamily, ControlMessageOwned, MsgFlags, MultiHeaders, |
| SockFlag, SockType, SockaddrIn, TimestampingFlag, Timestamps, |
| }, |
| time::TimeSpec, |
| }; |
| use std::io::{IoSlice, IoSliceMut}; |
| use std::os::unix::io::AsRawFd; |
| use std::str::FromStr; |
| |
| let sock_addr = SockaddrIn::from_str("127.0.0.1:6790").unwrap(); |
| |
| let ssock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::empty(), |
| None, |
| )?; |
| |
| let rsock = socket( |
| AddressFamily::Inet, |
| SockType::Datagram, |
| SockFlag::SOCK_NONBLOCK, |
| None, |
| )?; |
| |
| bind(rsock.as_raw_fd(), &sock_addr)?; |
| |
| setsockopt(&rsock, Timestamping, &TimestampingFlag::all())?; |
| |
| let sbuf = (0..400).map(|i| i as u8).collect::<Vec<_>>(); |
| |
| let mut recv_buf = vec![0; 1024]; |
| |
| let mut recv_iovs = Vec::new(); |
| let mut pkt_iovs = Vec::new(); |
| |
| for (ix, chunk) in recv_buf.chunks_mut(256).enumerate() { |
| pkt_iovs.push(IoSliceMut::new(chunk)); |
| if ix % 2 == 1 { |
| recv_iovs.push(pkt_iovs); |
| pkt_iovs = Vec::new(); |
| } |
| } |
| drop(pkt_iovs); |
| |
| let flags = MsgFlags::empty(); |
| let iov1 = [IoSlice::new(&sbuf)]; |
| |
| let cmsg = cmsg_space!(Timestamps); |
| sendmsg(ssock.as_raw_fd(), &iov1, &[], flags, Some(&sock_addr)).unwrap(); |
| |
| let mut data = MultiHeaders::<()>::preallocate(recv_iovs.len(), Some(cmsg)); |
| |
| let t = TimeSpec::from_duration(std::time::Duration::from_secs(10)); |
| |
| let recv = recvmmsg( |
| rsock.as_raw_fd(), |
| &mut data, |
| recv_iovs.iter_mut(), |
| flags, |
| Some(t), |
| )?; |
| |
| for rmsg in recv { |
| #[cfg(not(any(qemu, target_arch = "aarch64")))] |
| let mut saw_time = false; |
| let mut recvd = 0; |
| for cmsg in rmsg.cmsgs().unwrap() { |
| if let ControlMessageOwned::ScmTimestampsns(timestamps) = cmsg { |
| let ts = timestamps.system; |
| |
| let sys_time = nix::time::clock_gettime( |
| nix::time::ClockId::CLOCK_REALTIME, |
| )?; |
| let diff = if ts > sys_time { |
| ts - sys_time |
| } else { |
| sys_time - ts |
| }; |
| assert!(std::time::Duration::from(diff).as_secs() < 60); |
| #[cfg(not(any(qemu, target_arch = "aarch64")))] |
| { |
| saw_time = true; |
| } |
| } |
| } |
| |
| #[cfg(not(any(qemu, target_arch = "aarch64")))] |
| assert!(saw_time); |
| |
| for iov in rmsg.iovs() { |
| recvd += iov.len(); |
| } |
| assert_eq!(recvd, 400); |
| } |
| |
| Ok(()) |
| } |
| |
| #[cfg(not(target_os = "redox"))] |
| #[test] |
| fn can_use_cmsg_space() { |
| let _ = cmsg_space!(u8); |
| } |
| |
| #[cfg(not(any(linux_android, target_os = "redox", target_os = "haiku")))] |
| #[test] |
| fn can_open_routing_socket() { |
| use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; |
| |
| let _ = |
| socket(AddressFamily::Route, SockType::Raw, SockFlag::empty(), None) |
| .expect("Failed to open routing socket"); |
| } |