Skip to content

Commit c2bd3e9

Browse files
committed
feat(ip_recverr): Add ICMP error struct, start with linux
chore(ip_recverr): Testing with socket setup similar to cmsg in recv feat(ip_recverr): Initialised socket options for IPV4 and V6 feat(ip_recverr): Add error read queue function chore(ip_recverr): Add error handling after recieving normal packets test(ip_recverr): Start the test cases for IP_RECVERR test(ip_recverr): Complete the test cases for ip_recverr and ipv6 test(ip_recverr): handle expected send failure instead of panicking style: Format the files using rustfmt style(recv_err): Change the name of the unused function refactor(ip_recverr): change the ICMPErr to enum kind refactor(ip_recverr): COnversion from SockExtendedErr to ICMPErrKind refactor(ip_recverr): Remove ICMPErr from recv and add seperate recv_icmp_err test(ip_recverr): Change the test files following new method Apply suggestions from code review Co-authored-by: Thomas Eizinger <[email protected]> refactor: address code review feedback - Use early returns to reduce nesting - Replace match with unwrap in tests - Add non-Linux fallback implementation fix(ip_recverr): Continue to next control message instead of return test(ip_recverr): Fix the socket binding error from OS, now sends proper network error fix(ip_recverr): rename IcmpError for non linux platforms fix(pacing): allow ±0ns tolerance in computes_pause_correctly test on i386 -Used Duration::abs_diff() instead of manual difference for cleaner and more robust duration comparison. -added inline formatting as required. build(deps): bump rustls-platform-verifier from 0.6.1 to 0.6.2 Bumps [rustls-platform-verifier](https://github.com/rustls/rustls-platform-verifier) from 0.6.1 to 0.6.2. - [Release notes](https://github.com/rustls/rustls-platform-verifier/releases) - [Changelog](https://github.com/rustls/rustls-platform-verifier/blob/main/CHANGELOG) - [Commits](rustls/rustls-platform-verifier@v/0.6.1...v/0.6.2) --- updated-dependencies: - dependency-name: rustls-platform-verifier dependency-version: 0.6.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> refactor(ip_recverr): Change SockExtendedError as suggested fix(ip_recverr): Add IcmpError for non linux platforms refactor(ip_recverr): Change to SockExtendedError Kind refactored changes for structs
1 parent 1713c04 commit c2bd3e9

File tree

3 files changed

+274
-3
lines changed

3 files changed

+274
-3
lines changed

quinn-udp/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,18 @@ impl EcnCodepoint {
238238
})
239239
}
240240
}
241+
242+
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
243+
pub struct IcmpError {
244+
pub dst: SocketAddr,
245+
pub kind: IcmpErrorKind,
246+
}
247+
248+
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
249+
pub enum IcmpErrorKind {
250+
NetworkUnreachable,
251+
HostUnreachable,
252+
PortUnreachable,
253+
PacketTooBig,
254+
Other { icmp_type: u8, icmp_code: u8 },
255+
}

quinn-udp/src/unix.rs

Lines changed: 174 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ use std::{
1414

1515
use socket2::SockRef;
1616

17+
use crate::IcmpError;
18+
19+
#[cfg(target_os = "linux")]
20+
use crate::IcmpErrorKind;
21+
1722
use super::{
1823
EcnCodepoint, IO_ERROR_LOG_INTERVAL, RecvMeta, Transmit, UdpSockRef, cmsg, log_sendmsg_error,
1924
};
@@ -33,6 +38,66 @@ pub(crate) struct msghdr_x {
3338
pub msg_datalen: usize,
3439
}
3540

41+
#[cfg(target_os = "linux")]
42+
#[repr(C)]
43+
#[derive(Clone, Copy, Debug)]
44+
pub(crate) struct SockExtendedError {
45+
pub errno: u32,
46+
pub origin: u8,
47+
pub r#type: u8,
48+
pub code: u8,
49+
pub pad: u8,
50+
pub info: u32,
51+
pub data: u32,
52+
}
53+
54+
#[cfg(target_os = "linux")]
55+
impl SockExtendedError {
56+
fn kind(&self) -> IcmpErrorKind {
57+
const ICMP_DEST_UNREACH: u8 = 3; // Type 3: Destination Unreachable
58+
const ICMP_NET_UNREACH: u8 = 0;
59+
const ICMP_HOST_UNREACH: u8 = 1;
60+
const ICMP_PORT_UNREACH: u8 = 3;
61+
const ICMP_FRAG_NEEDED: u8 = 4;
62+
63+
const ICMPV6_DEST_UNREACH: u8 = 1; // Type 1: Destination Unreachable for IPv6
64+
const ICMPV6_NO_ROUTE: u8 = 0;
65+
const ICMPV6_ADDR_UNREACH: u8 = 1;
66+
const ICMPV6_PORT_UNREACH: u8 = 4;
67+
68+
const ICMPV6_PACKET_TOO_BIG: u8 = 2;
69+
70+
match (self.origin, self.r#type, self.code) {
71+
(libc::SO_EE_ORIGIN_ICMP, ICMP_DEST_UNREACH, ICMP_NET_UNREACH) => {
72+
IcmpErrorKind::NetworkUnreachable
73+
}
74+
(libc::SO_EE_ORIGIN_ICMP, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH) => {
75+
IcmpErrorKind::HostUnreachable
76+
}
77+
(libc::SO_EE_ORIGIN_ICMP, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH) => {
78+
IcmpErrorKind::PortUnreachable
79+
}
80+
(libc::SO_EE_ORIGIN_ICMP, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED) => {
81+
IcmpErrorKind::PacketTooBig
82+
}
83+
(libc::SO_EE_ORIGIN_ICMP6, ICMPV6_DEST_UNREACH, ICMPV6_NO_ROUTE) => {
84+
IcmpErrorKind::NetworkUnreachable
85+
}
86+
(libc::SO_EE_ORIGIN_ICMP6, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH) => {
87+
IcmpErrorKind::HostUnreachable
88+
}
89+
(libc::SO_EE_ORIGIN_ICMP6, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH) => {
90+
IcmpErrorKind::PortUnreachable
91+
}
92+
(libc::SO_EE_ORIGIN_ICMP6, ICMPV6_PACKET_TOO_BIG, _) => IcmpErrorKind::PacketTooBig,
93+
_ => IcmpErrorKind::Other {
94+
icmp_type: self.r#type,
95+
icmp_code: self.code,
96+
},
97+
}
98+
}
99+
}
100+
36101
#[cfg(apple_fast)]
37102
extern "C" {
38103
fn recvmsg_x(
@@ -112,11 +177,23 @@ impl UdpSocketState {
112177
solarish
113178
)))]
114179
if is_ipv4 || !io.only_v6()? {
115-
if let Err(_err) =
116-
set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON)
180+
if let Err(_err) = set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON)
181+
{
182+
crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {}", _err);
183+
}
184+
}
185+
186+
// Enable IP_RECVERR and IPV6_RECVERR for ICMP Errors
187+
#[cfg(target_os = "linux")]
188+
if is_ipv4 {
189+
if let Err(err) = set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVERR, OPTION_ON)
117190
{
118-
crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}");
191+
crate::log::debug!("Failed to enable IP_RECVERR: {}", err);
119192
}
193+
} else if let Err(err) =
194+
set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVERR, OPTION_ON)
195+
{
196+
crate::log::debug!("Failed to enable IPV6_RECVERR: {}", err);
120197
}
121198

122199
let mut may_fragment = false;
@@ -234,6 +311,10 @@ impl UdpSocketState {
234311
recv(socket.0, bufs, meta)
235312
}
236313

314+
pub fn recv_icmp_err(&self, socket: UdpSockRef<'_>) -> io::Result<Option<IcmpError>> {
315+
recv_err(socket.0)
316+
}
317+
237318
/// The maximum amount of segments which can be transmitted if a platform
238319
/// supports Generic Send Offload (GSO).
239320
///
@@ -507,6 +588,7 @@ fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) ->
507588
for i in 0..(msg_count as usize) {
508589
meta[i] = decode_recv(&names[i], &hdrs[i].msg_hdr, hdrs[i].msg_len as usize)?;
509590
}
591+
510592
Ok(msg_count as usize)
511593
}
512594

@@ -813,6 +895,95 @@ fn decode_recv(
813895
})
814896
}
815897

898+
#[cfg(target_os = "linux")]
899+
fn recv_err(io: SockRef<'_>) -> io::Result<Option<IcmpError>> {
900+
let fd = io.as_raw_fd();
901+
let mut control = cmsg::Aligned([0u8; CMSG_LEN]);
902+
903+
// We don't need actual data, just the error info
904+
let mut iovec = libc::iovec {
905+
iov_base: std::ptr::null_mut(),
906+
iov_len: 0,
907+
};
908+
909+
let mut addr_storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
910+
911+
let mut hdr: libc::msghdr = unsafe { mem::zeroed() };
912+
hdr.msg_name = &mut addr_storage as *mut _ as *mut _;
913+
hdr.msg_namelen = mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
914+
hdr.msg_iov = &mut iovec;
915+
hdr.msg_iovlen = 1;
916+
hdr.msg_control = control.0.as_mut_ptr() as *mut _;
917+
hdr.msg_controllen = CMSG_LEN as _;
918+
919+
let ret = unsafe { libc::recvmsg(fd, &mut hdr, libc::MSG_ERRQUEUE) };
920+
921+
if ret < 0 {
922+
let err = io::Error::last_os_error();
923+
// EAGAIN/EWOULDBLOCK means no error in queue - this is normal
924+
if err.kind() == io::ErrorKind::WouldBlock {
925+
return Ok(None);
926+
}
927+
return Err(err);
928+
}
929+
930+
let cmsg_iter = unsafe { cmsg::Iter::new(&hdr) };
931+
932+
for cmsg in cmsg_iter {
933+
const IP_RECVERR: libc::c_int = 11;
934+
const IPV6_RECVERR: libc::c_int = 25;
935+
936+
let is_ipv4_err = cmsg.cmsg_level == libc::IPPROTO_IP && cmsg.cmsg_type == IP_RECVERR;
937+
let is_ipv6_err = cmsg.cmsg_level == libc::IPPROTO_IPV6 && cmsg.cmsg_type == IPV6_RECVERR;
938+
939+
if !is_ipv4_err && !is_ipv6_err {
940+
continue;
941+
}
942+
943+
let err_data = unsafe { cmsg::decode::<SockExtendedError, libc::cmsghdr>(cmsg) };
944+
945+
let dst = unsafe {
946+
let addr_ptr = &addr_storage as *const _ as *const libc::sockaddr;
947+
match (*addr_ptr).sa_family as i32 {
948+
libc::AF_INET => {
949+
let addr_in = &*(addr_ptr as *const libc::sockaddr_in);
950+
SocketAddr::V4(std::net::SocketAddrV4::new(
951+
std::net::Ipv4Addr::from(u32::from_be(addr_in.sin_addr.s_addr)),
952+
u16::from_be(addr_in.sin_port),
953+
))
954+
}
955+
libc::AF_INET6 => {
956+
let addr_in6 = &*(addr_ptr as *const libc::sockaddr_in6);
957+
SocketAddr::V6(std::net::SocketAddrV6::new(
958+
std::net::Ipv6Addr::from(addr_in6.sin6_addr.s6_addr),
959+
u16::from_be(addr_in6.sin6_port),
960+
addr_in6.sin6_flowinfo,
961+
addr_in6.sin6_scope_id,
962+
))
963+
}
964+
_ => {
965+
crate::log::warn!(
966+
"Ignoring ICMP error with unknown address family: {}",
967+
addr_storage.ss_family
968+
);
969+
continue;
970+
}
971+
}
972+
};
973+
974+
return Ok(Some(IcmpError {
975+
dst,
976+
kind: err_data.kind(),
977+
}));
978+
}
979+
Ok(None)
980+
}
981+
982+
#[cfg(not(target_os = "linux"))]
983+
fn recv_err(_io: SockRef<'_>) -> io::Result<Option<IcmpError>> {
984+
Ok(None)
985+
}
986+
816987
#[cfg(not(apple_slow))]
817988
// Chosen somewhat arbitrarily; might benefit from additional tuning.
818989
pub(crate) const BATCH_SIZE: usize = 32;

quinn-udp/tests/tests.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,88 @@ fn ip_to_v6_mapped(x: IpAddr) -> IpAddr {
369369
IpAddr::V6(_) => x,
370370
}
371371
}
372+
373+
#[cfg(target_os = "linux")]
374+
#[test]
375+
fn test_ipv4_recverr() {
376+
use std::time::Duration;
377+
378+
let socket = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
379+
380+
// Create UdpSocketState (this should enable IP_RECVERR)
381+
let state = UdpSocketState::new((&socket).into()).expect("failed to create UdpSocketState");
382+
383+
// Send to an unreachable address in the documentation range (192.0.2.0/24)
384+
let unreachable_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 12345));
385+
386+
let transmit = Transmit {
387+
destination: unreachable_addr,
388+
ecn: None,
389+
contents: b"test packet to unreachable destination",
390+
segment_size: None,
391+
src_ip: None,
392+
};
393+
394+
// Send the packet
395+
let state_result = state.try_send((&socket).into(), &transmit);
396+
assert!(state_result.is_err(), "Expected to fail to transmit");
397+
398+
std::thread::sleep(Duration::from_millis(200));
399+
400+
match state.recv_icmp_err((&socket).into()) {
401+
Ok(Some(icmp_err)) => {
402+
eprintln!("icmp packet recieved");
403+
assert_eq!(unreachable_addr.ip(), icmp_err.dst.ip());
404+
}
405+
Ok(None) => {
406+
eprintln!("No ICMP Recieved");
407+
}
408+
Err(e) => {
409+
eprintln!("Error in reciveing icmp packet: {}", e);
410+
}
411+
}
412+
}
413+
414+
#[cfg(target_os = "linux")]
415+
#[test]
416+
fn test_ipv6_recverr() {
417+
use std::time::Duration;
418+
419+
let socket = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0)).unwrap();
420+
421+
let state = UdpSocketState::new((&socket).into()).expect("failed to create UdpSocketState");
422+
423+
// Send to unreachable IPv6 address
424+
let unreachable_addr = SocketAddr::V6(SocketAddrV6::new(
425+
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1),
426+
12345,
427+
0,
428+
0,
429+
));
430+
431+
let transmit = Transmit {
432+
destination: unreachable_addr,
433+
ecn: None,
434+
contents: b"test IPv6 packet",
435+
segment_size: None,
436+
src_ip: None,
437+
};
438+
439+
let state_res = state.try_send((&socket).into(), &transmit);
440+
assert!(state_res.is_err(), "Expected to fail to transmit");
441+
442+
std::thread::sleep(Duration::from_millis(200));
443+
444+
match state.recv_icmp_err((&socket).into()) {
445+
Ok(Some(icmp_err)) => {
446+
eprintln!("Recived ICMPV6 Packets");
447+
assert_eq!(unreachable_addr.ip(), icmp_err.dst.ip());
448+
}
449+
Ok(None) => {
450+
eprintln!("No ICMPV6 packets are recieved")
451+
}
452+
Err(e) => {
453+
eprintln!("Error in sending ICMP packets: {}", e);
454+
}
455+
}
456+
}

0 commit comments

Comments
 (0)