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 . --- test/sys/test_socket.rs | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) (limited to 'test') 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