Skip to content

Commit d23e7c7

Browse files
committed
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>.
1 parent aaec214 commit d23e7c7

File tree

4 files changed

+198
-0
lines changed

4 files changed

+198
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
3434
(#[1503](https://github.com/nix-rust/nix/pull/1503))
3535
- Enabled `pwritev` and `preadv` for more operating systems.
3636
(#[1511](https://github.com/nix-rust/nix/pull/1511))
37+
- Added `Ipv4RecvErr` and `Ipv6RecvErr` sockopts and associated control messages.
38+
(#[1514](https://github.com/nix-rust/nix/pull/1514))
3739

3840
### Changed
3941

src/sys/socket/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,13 @@ pub enum ControlMessageOwned {
653653
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
654654
RxqOvfl(u32),
655655

656+
/// Socket error queue control messages read with the `MSG_ERRQUEUE` flag.
657+
#[cfg(any(target_os = "android", target_os = "linux"))]
658+
Ipv4RecvErr(libc::sock_extended_err, Option<sockaddr_in>),
659+
/// Socket error queue control messages read with the `MSG_ERRQUEUE` flag.
660+
#[cfg(any(target_os = "android", target_os = "linux"))]
661+
Ipv6RecvErr(libc::sock_extended_err, Option<sockaddr_in6>),
662+
656663
/// Catch-all variant for unimplemented cmsg types.
657664
#[doc(hidden)]
658665
Unknown(UnknownCmsg),
@@ -756,13 +763,41 @@ impl ControlMessageOwned {
756763
let drop_counter = ptr::read_unaligned(p as *const u32);
757764
ControlMessageOwned::RxqOvfl(drop_counter)
758765
},
766+
#[cfg(any(target_os = "android", target_os = "linux"))]
767+
(libc::IPPROTO_IP, libc::IP_RECVERR) => {
768+
let (err, addr) = Self::recv_err_helper::<sockaddr_in>(p, len);
769+
ControlMessageOwned::Ipv4RecvErr(err, addr)
770+
},
771+
#[cfg(any(target_os = "android", target_os = "linux"))]
772+
(libc::IPPROTO_IPV6, libc::IPV6_RECVERR) => {
773+
let (err, addr) = Self::recv_err_helper::<sockaddr_in6>(p, len);
774+
ControlMessageOwned::Ipv6RecvErr(err, addr)
775+
},
759776
(_, _) => {
760777
let sl = slice::from_raw_parts(p, len);
761778
let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(sl));
762779
ControlMessageOwned::Unknown(ucmsg)
763780
}
764781
}
765782
}
783+
784+
#[cfg(any(target_os = "android", target_os = "linux"))]
785+
unsafe fn recv_err_helper<T>(p: *mut libc::c_uchar, len: usize) -> (libc::sock_extended_err, Option<T>) {
786+
let ee = p as *const libc::sock_extended_err;
787+
let err = ptr::read_unaligned(ee);
788+
789+
// For errors originating on the network, SO_EE_OFFENDER(ee) points inside the p[..len]
790+
// CMSG_DATA buffer. For local errors, there is no address included in the control
791+
// message, and SO_EE_OFFENDER(ee) points beyond the end of the buffer. So, we need to
792+
// validate that the address object is in-bounds before we attempt to copy it.
793+
let addrp = libc::SO_EE_OFFENDER(ee) as *const T;
794+
795+
if addrp.offset(1) as usize - (p as usize) > len {
796+
(err, None)
797+
} else {
798+
(err, Some(ptr::read_unaligned(addrp)))
799+
}
800+
}
766801
}
767802

768803
/// A type-safe zero-copy wrapper around a single control message, as used wih

src/sys/socket/sockopt.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,10 @@ sockopt_impl!(Both, UdpGroSegment, libc::IPPROTO_UDP, libc::UDP_GRO, bool);
345345
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
346346
sockopt_impl!(Both, RxqOvfl, libc::SOL_SOCKET, libc::SO_RXQ_OVFL, libc::c_int);
347347
sockopt_impl!(Both, Ipv6V6Only, libc::IPPROTO_IPV6, libc::IPV6_V6ONLY, bool);
348+
#[cfg(any(target_os = "android", target_os = "linux"))]
349+
sockopt_impl!(Both, Ipv4RecvErr, libc::IPPROTO_IP, libc::IP_RECVERR, bool);
350+
#[cfg(any(target_os = "android", target_os = "linux"))]
351+
sockopt_impl!(Both, Ipv6RecvErr, libc::IPPROTO_IPV6, libc::IPV6_RECVERR, bool);
348352

349353
#[cfg(any(target_os = "android", target_os = "linux"))]
350354
#[derive(Copy, Clone, Debug)]

test/sys/test_socket.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,3 +1789,160 @@ fn test_recvmsg_rxq_ovfl() {
17891789
nix::unistd::close(in_socket).unwrap();
17901790
nix::unistd::close(out_socket).unwrap();
17911791
}
1792+
1793+
#[cfg(any(
1794+
target_os = "linux",
1795+
target_os = "android",
1796+
))]
1797+
mod linux_errqueue {
1798+
use nix::sys::socket::*;
1799+
use super::{FromStr, SocketAddr};
1800+
1801+
// Send a UDP datagram to a bogus destination address and observe an ICMP error (v4).
1802+
//
1803+
// Disable the test on QEMU because QEMU emulation of IP_RECVERR is broken (as documented on PR
1804+
// #1514).
1805+
#[cfg_attr(qemu, ignore)]
1806+
#[test]
1807+
fn test_recverr_v4() {
1808+
#[repr(u8)]
1809+
enum IcmpTypes {
1810+
DestUnreach = 3, // ICMP_DEST_UNREACH
1811+
}
1812+
#[repr(u8)]
1813+
enum IcmpUnreachCodes {
1814+
PortUnreach = 3, // ICMP_PORT_UNREACH
1815+
}
1816+
1817+
test_recverr_impl::<sockaddr_in, _, _>(
1818+
"127.0.0.1:6800",
1819+
AddressFamily::Inet,
1820+
sockopt::Ipv4RecvErr,
1821+
libc::SO_EE_ORIGIN_ICMP,
1822+
IcmpTypes::DestUnreach as u8,
1823+
IcmpUnreachCodes::PortUnreach as u8,
1824+
// Closure handles protocol-specific testing and returns generic sock_extended_err for
1825+
// protocol-independent test impl.
1826+
|cmsg| {
1827+
if let ControlMessageOwned::Ipv4RecvErr(ext_err, err_addr) = cmsg {
1828+
if let Some(origin) = err_addr {
1829+
// Validate that our network error originated from 127.0.0.1:0.
1830+
assert_eq!(origin.sin_family, AddressFamily::Inet as _);
1831+
assert_eq!(Ipv4Addr(origin.sin_addr), Ipv4Addr::new(127, 0, 0, 1));
1832+
assert_eq!(origin.sin_port, 0);
1833+
} else {
1834+
panic!("Expected some error origin");
1835+
}
1836+
return *ext_err
1837+
} else {
1838+
panic!("Unexpected control message {:?}", cmsg);
1839+
}
1840+
},
1841+
)
1842+
}
1843+
1844+
// Essentially the same test as v4.
1845+
//
1846+
// Disable the test on QEMU because QEMU emulation of IPV6_RECVERR is broken (as documented on
1847+
// PR #1514).
1848+
#[cfg_attr(qemu, ignore)]
1849+
#[test]
1850+
fn test_recverr_v6() {
1851+
#[repr(u8)]
1852+
enum IcmpV6Types {
1853+
DestUnreach = 1, // ICMPV6_DEST_UNREACH
1854+
}
1855+
#[repr(u8)]
1856+
enum IcmpV6UnreachCodes {
1857+
PortUnreach = 4, // ICMPV6_PORT_UNREACH
1858+
}
1859+
1860+
test_recverr_impl::<sockaddr_in6, _, _>(
1861+
"[::1]:6801",
1862+
AddressFamily::Inet6,
1863+
sockopt::Ipv6RecvErr,
1864+
libc::SO_EE_ORIGIN_ICMP6,
1865+
IcmpV6Types::DestUnreach as u8,
1866+
IcmpV6UnreachCodes::PortUnreach as u8,
1867+
// Closure handles protocol-specific testing and returns generic sock_extended_err for
1868+
// protocol-independent test impl.
1869+
|cmsg| {
1870+
if let ControlMessageOwned::Ipv6RecvErr(ext_err, err_addr) = cmsg {
1871+
if let Some(origin) = err_addr {
1872+
// Validate that our network error originated from localhost:0.
1873+
assert_eq!(origin.sin6_family, AddressFamily::Inet6 as _);
1874+
assert_eq!(
1875+
Ipv6Addr(origin.sin6_addr),
1876+
Ipv6Addr::from_std(&"::1".parse().unwrap()),
1877+
);
1878+
assert_eq!(origin.sin6_port, 0);
1879+
} else {
1880+
panic!("Expected some error origin");
1881+
}
1882+
return *ext_err
1883+
} else {
1884+
panic!("Unexpected control message {:?}", cmsg);
1885+
}
1886+
},
1887+
)
1888+
}
1889+
1890+
fn test_recverr_impl<SA, OPT, TESTF>(sa: &str,
1891+
af: AddressFamily,
1892+
opt: OPT,
1893+
ee_origin: u8,
1894+
ee_type: u8,
1895+
ee_code: u8,
1896+
testf: TESTF)
1897+
where
1898+
OPT: SetSockOpt<Val = bool>,
1899+
TESTF: FnOnce(&ControlMessageOwned) -> libc::sock_extended_err,
1900+
{
1901+
use nix::errno::Errno;
1902+
use nix::sys::uio::IoVec;
1903+
1904+
const MESSAGE_CONTENTS: &str = "ABCDEF";
1905+
1906+
let sock_addr = {
1907+
let std_sa = SocketAddr::from_str(sa).unwrap();
1908+
let inet_addr = InetAddr::from_std(&std_sa);
1909+
SockAddr::new_inet(inet_addr)
1910+
};
1911+
let sock = socket(af, SockType::Datagram, SockFlag::SOCK_CLOEXEC, None).unwrap();
1912+
setsockopt(sock, opt, &true).unwrap();
1913+
if let Err(e) = sendto(sock, MESSAGE_CONTENTS.as_bytes(), &sock_addr, MsgFlags::empty()) {
1914+
assert_eq!(e, Errno::EADDRNOTAVAIL);
1915+
println!("{:?} not available, skipping test.", af);
1916+
return;
1917+
}
1918+
1919+
let mut buf = [0u8; 8];
1920+
let iovec = [IoVec::from_mut_slice(&mut buf)];
1921+
let mut cspace = cmsg_space!(libc::sock_extended_err, SA);
1922+
1923+
let msg = recvmsg(sock, &iovec, Some(&mut cspace), MsgFlags::MSG_ERRQUEUE).unwrap();
1924+
// The sent message / destination associated with the error is returned:
1925+
assert_eq!(msg.bytes, MESSAGE_CONTENTS.as_bytes().len());
1926+
assert_eq!(&buf[..msg.bytes], MESSAGE_CONTENTS.as_bytes());
1927+
// recvmsg(2): "The original destination address of the datagram that caused the error is
1928+
// supplied via msg_name;" however, this is not literally true. E.g., an earlier version
1929+
// of this test used 0.0.0.0 (::0) as the destination address, which was mutated into
1930+
// 127.0.0.1 (::1).
1931+
assert_eq!(msg.address, Some(sock_addr));
1932+
1933+
// Check for expected control message.
1934+
let ext_err = match msg.cmsgs().next() {
1935+
Some(cmsg) => testf(&cmsg),
1936+
None => panic!("No control message"),
1937+
};
1938+
1939+
assert_eq!(ext_err.ee_errno, libc::ECONNREFUSED as u32);
1940+
assert_eq!(ext_err.ee_origin, ee_origin);
1941+
// ip(7): ee_type and ee_code are set from the type and code fields of the ICMP (ICMPv6)
1942+
// header.
1943+
assert_eq!(ext_err.ee_type, ee_type);
1944+
assert_eq!(ext_err.ee_code, ee_code);
1945+
// ip(7): ee_info contains the discovered MTU for EMSGSIZE errors.
1946+
assert_eq!(ext_err.ee_info, 0);
1947+
}
1948+
}

0 commit comments

Comments
 (0)