summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-09-07 00:03:59 +0000
committerGitHub <noreply@github.com>2021-09-07 00:03:59 +0000
commit2142ec3c27f1c6a275aecc502b7fd3ee3bdaf08a (patch)
tree55ede431ffe91cb0d00f9fd23da908142ddace4a
parentaaec2143867b64b4164985284ca21be54781e835 (diff)
parentd23e7c7575f331fd2263f2f80b7698c49abe7907 (diff)
downloadnix-2142ec3c27f1c6a275aecc502b7fd3ee3bdaf08a.zip
Merge #1514
1514: Add support for IP_RECVERR r=asomers a=cemeyer This pull request adds support for setting the `IP_RECVERR` (and corresponding `IPV6_RECVERR`) sockopt, and then decoding the resulting control messages from `recvmsg()`. It is a Linux-specific API. This PR extends #1161 by: * Making the address associated with a `IpvXRecvErr` control message optional (`recvmsg` documentation claims it is not provided for some kinds of error), per `@WGH-.` * Adding basic tests for both IPv4 and IPv6 (blat a UDP packet at a hopefully-unoccupied port on localhost and observe ICMP port unreachable). * Adding a Changelog entry. I added some trivial doc comments on the `ControlMessageOwned` variants, but I'm not sure exactly what documentation you had in mind earlier on #1161. Co-authored-by: Conrad Meyer <cem@FreeBSD.org>
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/sys/socket/mod.rs35
-rw-r--r--src/sys/socket/sockopt.rs4
-rw-r--r--test/sys/test_socket.rs157
4 files changed, 198 insertions, 0 deletions
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<sockaddr_in>),
+ /// 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<sockaddr_in6>),
+
/// 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::<sockaddr_in>(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::<sockaddr_in6>(p, len);
+ ControlMessageOwned::Ipv6RecvErr(err, addr)
+ },
(_, _) => {
let sl = slice::from_raw_parts(p, len);
let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(sl));
@@ -763,6 +780,24 @@ impl ControlMessageOwned {
}
}
}
+
+ #[cfg(any(target_os = "android", target_os = "linux"))]
+ unsafe fn recv_err_helper<T>(p: *mut libc::c_uchar, len: usize) -> (libc::sock_extended_err, Option<T>) {
+ 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::<sockaddr_in, _, _>(
+ "127.0.0.1:6800",
+ AddressFamily::Inet,
+ sockopt::Ipv4RecvErr,
+ libc::SO_EE_ORIGIN_ICMP,
+ IcmpTypes::DestUnreach as u8,
+ IcmpUnreachCodes::PortUnreach as u8,
+ // Closure handles protocol-specific testing and returns generic sock_extended_err for
+ // protocol-independent test impl.
+ |cmsg| {
+ if let ControlMessageOwned::Ipv4RecvErr(ext_err, err_addr) = cmsg {
+ if let Some(origin) = err_addr {
+ // Validate that our network error originated from 127.0.0.1:0.
+ assert_eq!(origin.sin_family, AddressFamily::Inet as _);
+ assert_eq!(Ipv4Addr(origin.sin_addr), Ipv4Addr::new(127, 0, 0, 1));
+ assert_eq!(origin.sin_port, 0);
+ } else {
+ panic!("Expected some error origin");
+ }
+ return *ext_err
+ } else {
+ panic!("Unexpected control message {:?}", cmsg);
+ }
+ },
+ )
+ }
+
+ // Essentially the same test as v4.
+ //
+ // Disable the test on QEMU because QEMU emulation of IPV6_RECVERR is broken (as documented on
+ // PR #1514).
+ #[cfg_attr(qemu, ignore)]
+ #[test]
+ fn test_recverr_v6() {
+ #[repr(u8)]
+ enum IcmpV6Types {
+ DestUnreach = 1, // ICMPV6_DEST_UNREACH
+ }
+ #[repr(u8)]
+ enum IcmpV6UnreachCodes {
+ PortUnreach = 4, // ICMPV6_PORT_UNREACH
+ }
+
+ test_recverr_impl::<sockaddr_in6, _, _>(
+ "[::1]:6801",
+ AddressFamily::Inet6,
+ sockopt::Ipv6RecvErr,
+ libc::SO_EE_ORIGIN_ICMP6,
+ IcmpV6Types::DestUnreach as u8,
+ IcmpV6UnreachCodes::PortUnreach as u8,
+ // Closure handles protocol-specific testing and returns generic sock_extended_err for
+ // protocol-independent test impl.
+ |cmsg| {
+ if let ControlMessageOwned::Ipv6RecvErr(ext_err, err_addr) = cmsg {
+ if let Some(origin) = err_addr {
+ // Validate that our network error originated from localhost:0.
+ assert_eq!(origin.sin6_family, AddressFamily::Inet6 as _);
+ assert_eq!(
+ Ipv6Addr(origin.sin6_addr),
+ Ipv6Addr::from_std(&"::1".parse().unwrap()),
+ );
+ assert_eq!(origin.sin6_port, 0);
+ } else {
+ panic!("Expected some error origin");
+ }
+ return *ext_err
+ } else {
+ panic!("Unexpected control message {:?}", cmsg);
+ }
+ },
+ )
+ }
+
+ fn test_recverr_impl<SA, OPT, TESTF>(sa: &str,
+ af: AddressFamily,
+ opt: OPT,
+ ee_origin: u8,
+ ee_type: u8,
+ ee_code: u8,
+ testf: TESTF)
+ where
+ OPT: SetSockOpt<Val = bool>,
+ TESTF: FnOnce(&ControlMessageOwned) -> libc::sock_extended_err,
+ {
+ use nix::errno::Errno;
+ use nix::sys::uio::IoVec;
+
+ const MESSAGE_CONTENTS: &str = "ABCDEF";
+
+ let sock_addr = {
+ let std_sa = SocketAddr::from_str(sa).unwrap();
+ let inet_addr = InetAddr::from_std(&std_sa);
+ SockAddr::new_inet(inet_addr)
+ };
+ let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None).unwrap();
+ setsockopt(sock, opt, &true).unwrap();
+ if let Err(e) = sendto(sock, MESSAGE_CONTENTS.as_bytes(), &sock_addr, MsgFlags::empty()) {
+ assert_eq!(e, Errno::EADDRNOTAVAIL);
+ println!("{:?} not available, skipping test.", af);
+ return;
+ }
+
+ let mut buf = [0u8; 8];
+ let iovec = [IoVec::from_mut_slice(&mut buf)];
+ let mut cspace = cmsg_space!(libc::sock_extended_err, SA);
+
+ let msg = recvmsg(sock, &iovec, Some(&mut cspace), MsgFlags::MSG_ERRQUEUE).unwrap();
+ // The sent message / destination associated with the error is returned:
+ assert_eq!(msg.bytes, MESSAGE_CONTENTS.as_bytes().len());
+ assert_eq!(&buf[..msg.bytes], MESSAGE_CONTENTS.as_bytes());
+ // recvmsg(2): "The original destination address of the datagram that caused the error is
+ // supplied via msg_name;" however, this is not literally true. E.g., an earlier version
+ // of this test used 0.0.0.0 (::0) as the destination address, which was mutated into
+ // 127.0.0.1 (::1).
+ assert_eq!(msg.address, Some(sock_addr));
+
+ // Check for expected control message.
+ let ext_err = match msg.cmsgs().next() {
+ Some(cmsg) => testf(&cmsg),
+ None => panic!("No control message"),
+ };
+
+ assert_eq!(ext_err.ee_errno, libc::ECONNREFUSED as u32);
+ assert_eq!(ext_err.ee_origin, ee_origin);
+ // ip(7): ee_type and ee_code are set from the type and code fields of the ICMP (ICMPv6)
+ // header.
+ assert_eq!(ext_err.ee_type, ee_type);
+ assert_eq!(ext_err.ee_code, ee_code);
+ // ip(7): ee_info contains the discovered MTU for EMSGSIZE errors.
+ assert_eq!(ext_err.ee_info, 0);
+ }
+}