mirror of
https://gitee.com/openharmony/third_party_rust_nix
synced 2025-01-05 23:09:03 +00:00
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 <git@mcpherrin.ca>.
This commit is contained in:
parent
aaec214386
commit
d23e7c7575
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user