From e03e40095302b5e316aeb98d4fc02f19b1895965 Mon Sep 17 00:00:00 2001 From: Ritesh Khadgaray Date: Sat, 17 Oct 2020 19:28:22 -0700 Subject: test_af_alg_aead waits indefinitely Starting with kernel 4.9, the crypto 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. Thus, we have fewer bytes to read than the buffer size. Do not block on read. --- test/sys/test_socket.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index 1003598e..b4638485 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -583,6 +583,7 @@ pub fn test_af_alg_cipher() { #[test] pub fn test_af_alg_aead() { use libc::{ALG_OP_DECRYPT, ALG_OP_ENCRYPT}; + use nix::fcntl::{fcntl, FcntlArg, OFlag}; use nix::sys::uio::IoVec; use nix::unistd::{read, close}; use nix::sys::socket::{socket, sendmsg, bind, accept, setsockopt, @@ -660,6 +661,11 @@ pub fn test_af_alg_aead() { // 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, &mut decrypted).expect("read decrypt"); assert!(num_bytes >= payload_len + (assoc_size as usize)); -- cgit v1.2.3 From d23e7c7575f331fd2263f2f80b7698c49abe7907 Mon Sep 17 00:00:00 2001 From: Conrad Meyer Date: Mon, 6 Sep 2021 16:59:35 -0700 Subject: Add support for IP_RECVERR and IPV6_RECVERR Setting these options enables receiving errors, such as ICMP errors from the network, via `recvmsg()` with `MSG_ERRQUEUE`. Adds new `Ipv{4,6}RecvErr` variants to `ControlMessageOwned`. These control messages are produced when `Ipv4RecvErr` or `Ipv6RecvErr` options are enabled on a raw or datagram socket. New tests for the functionality can be run with `cargo test --test test test_recverr`. This commit builds on an earlier draft of the functionality authored by Matthew McPherrin . --- CHANGELOG.md | 2 + src/sys/socket/mod.rs | 35 +++++++++++ src/sys/socket/sockopt.rs | 4 ++ test/sys/test_socket.rs | 157 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dfa5946..bc93a76f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[1503](https://github.com/nix-rust/nix/pull/1503)) - Enabled `pwritev` and `preadv` for more operating systems. (#[1511](https://github.com/nix-rust/nix/pull/1511)) +- Added `Ipv4RecvErr` and `Ipv6RecvErr` sockopts and associated control messages. + (#[1514](https://github.com/nix-rust/nix/pull/1514)) ### Changed diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 33585bb9..50d8b6fb 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -653,6 +653,13 @@ pub enum ControlMessageOwned { #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] RxqOvfl(u32), + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + Ipv4RecvErr(libc::sock_extended_err, Option), + /// Socket error queue control messages read with the `MSG_ERRQUEUE` flag. + #[cfg(any(target_os = "android", target_os = "linux"))] + Ipv6RecvErr(libc::sock_extended_err, Option), + /// Catch-all variant for unimplemented cmsg types. #[doc(hidden)] Unknown(UnknownCmsg), @@ -756,6 +763,16 @@ impl ControlMessageOwned { let drop_counter = ptr::read_unaligned(p as *const u32); ControlMessageOwned::RxqOvfl(drop_counter) }, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::IPPROTO_IP, libc::IP_RECVERR) => { + let (err, addr) = Self::recv_err_helper::(p, len); + ControlMessageOwned::Ipv4RecvErr(err, addr) + }, + #[cfg(any(target_os = "android", target_os = "linux"))] + (libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => { + let (err, addr) = Self::recv_err_helper::(p, len); + ControlMessageOwned::Ipv6RecvErr(err, addr) + }, (_, _) => { let sl = slice::from_raw_parts(p, len); let ucmsg = UnknownCmsg(*header, Vec::::from(sl)); @@ -763,6 +780,24 @@ impl ControlMessageOwned { } } } + + #[cfg(any(target_os = "android", target_os = "linux"))] + unsafe fn recv_err_helper(p: *mut libc::c_uchar, len: usize) -> (libc::sock_extended_err, Option) { + let ee = p as *const libc::sock_extended_err; + let err = ptr::read_unaligned(ee); + + // For errors originating on the network, SO_EE_OFFENDER(ee) points inside the p[..len] + // CMSG_DATA buffer. For local errors, there is no address included in the control + // message, and SO_EE_OFFENDER(ee) points beyond the end of the buffer. So, we need to + // validate that the address object is in-bounds before we attempt to copy it. + let addrp = libc::SO_EE_OFFENDER(ee) as *const T; + + if addrp.offset(1) as usize - (p as usize) > len { + (err, None) + } else { + (err, Some(ptr::read_unaligned(addrp))) + } + } } /// A type-safe zero-copy wrapper around a single control message, as used wih diff --git a/src/sys/socket/sockopt.rs b/src/sys/socket/sockopt.rs index a937226f..195479b6 100644 --- a/src/sys/socket/sockopt.rs +++ b/src/sys/socket/sockopt.rs @@ -345,6 +345,10 @@ sockopt_impl!(Both, UdpGroSegment, libc::IPPROTO_UDP, libc::UDP_GRO, bool); #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] sockopt_impl!(Both, RxqOvfl, libc::SOL_SOCKET, libc::SO_RXQ_OVFL, libc::c_int); sockopt_impl!(Both, Ipv6V6Only, libc::IPPROTO_IPV6, libc::IPV6_V6ONLY, bool); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!(Both, Ipv4RecvErr, libc::IPPROTO_IP, libc::IP_RECVERR, bool); +#[cfg(any(target_os = "android", target_os = "linux"))] +sockopt_impl!(Both, Ipv6RecvErr, libc::IPPROTO_IPV6, libc::IPV6_RECVERR, bool); #[cfg(any(target_os = "android", target_os = "linux"))] #[derive(Copy, Clone, Debug)] diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index aceffccb..6e41207e 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -1789,3 +1789,160 @@ fn test_recvmsg_rxq_ovfl() { 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::( + "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::( + "[::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: &str, + af: AddressFamily, + opt: OPT, + ee_origin: u8, + ee_type: u8, + ee_code: u8, + testf: TESTF) + where + OPT: SetSockOpt, + 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); + } +} -- cgit v1.2.3 From b0b7beb2a8772dd492272c4896425cde46b8dce9 Mon Sep 17 00:00:00 2001 From: Conrad Meyer Date: Tue, 7 Sep 2021 00:32:38 -0700 Subject: Implement AsRawFd for PollFd Implement the trait on PollFd, providing an `as_raw_fd()` accessor to get the RawFd associated with the PollFd. Subsumes #1147, #1286. Closes #1146. Test: `cargo test --test test test_pollfd_fd` --- CHANGELOG.md | 2 ++ src/poll.rs | 8 +++++++- test/test_poll.rs | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc93a76f..a3b5b82a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). (#[1511](https://github.com/nix-rust/nix/pull/1511)) - Added `Ipv4RecvErr` and `Ipv6RecvErr` sockopts and associated control messages. (#[1514](https://github.com/nix-rust/nix/pull/1514)) +- Added `AsRawFd` implementation on `PollFd`. + (#[1516](https://github.com/nix-rust/nix/pull/1516)) ### Changed diff --git a/src/poll.rs b/src/poll.rs index e814337a..0eaf7e1d 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -3,7 +3,7 @@ use crate::sys::time::TimeSpec; #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] use crate::sys::signal::SigSet; -use std::os::unix::io::RawFd; +use std::os::unix::io::{AsRawFd, RawFd}; use crate::Result; use crate::errno::Errno; @@ -41,6 +41,12 @@ impl PollFd { } } +impl AsRawFd for PollFd { + fn as_raw_fd(&self) -> RawFd { + self.pollfd.fd + } +} + libc_bitflags! { /// These flags define the different events that can be monitored by `poll` and `ppoll` pub struct PollFlags: libc::c_short { diff --git a/test/test_poll.rs b/test/test_poll.rs index 0395512b..ee89c4a0 100644 --- a/test/test_poll.rs +++ b/test/test_poll.rs @@ -64,3 +64,11 @@ fn test_ppoll() { assert_eq!(nfds, 1); assert!(fds[0].revents().unwrap().contains(PollFlags::POLLIN)); } + +#[test] +fn test_pollfd_fd() { + use std::os::unix::io::AsRawFd; + + let pfd = PollFd::new(0x1234, PollFlags::empty()); + assert_eq!(pfd.as_raw_fd(), 0x1234); +} -- cgit v1.2.3